性能
在性能方面,不存在一劳永逸的解决方案。许多因素都会影响性能,包括消息的大小和数量、应用程序方法是否执行需要阻塞的操作,以及外部因素(如网络速度和其他问题)。本节的目标是提供可用的配置选项概述,并就如何进行扩展性思考提出一些见解。
在消息应用程序中,消息通过通道进行异步传输,这些通道由线程池来支持。配置此类应用程序需要深入了解通道以及消息的流动机制。因此,建议阅读消息流相关文档。
显而易见的开始点是配置支持clientInboundChannel和clientOutboundChannel的线程池。默认情况下,这两个线程池的配置数量是可用处理器数量的两倍。
如果带注释方法中消息的处理主要依赖于CPU性能,那么clientInboundChannel的线程数应保持在接近处理器数量的水平。如果这些任务更依赖于I/O操作,并且需要阻塞或等待数据库或其他外部系统的响应,那么可能需要增加线程池的大小。
ThreadPoolExecutor有三个重要的属性:核心线程池大小(core thread pool size)、最大线程池大小(max thread pool size),以及用于存储没有可用线程的任务的队列容量(capacity for the queue to store tasks for which there are no available threads)。
一个常见的误解是,配置核心线程池大小(例如10)和最大线程池大小(例如20)会导致线程池有10到20个线程。实际上,如果队列容量保持其默认值Integer.MAX_VALUE,那么线程池的大小永远不会超过核心线程池的大小,因为所有额外的任务都会被放入队列中。
请参阅ThreadPoolExecutor的javadoc,以了解这些属性的工作原理,并理解各种排队策略。
在clientOutboundChannel方面,其主要任务就是向WebSocket客户端发送消息。如果客户端所连接的网络速度较快,那么线程的数量应该接近可用的处理器数量。但如果网络速度较慢或带宽较低,客户端处理消息所需的时间就会变长,这会给线程池带来负担。因此,增加线程池的大小就变得必要了。
虽然clientInboundChannel的工作负载是可以预测的——毕竟它是基于应用程序的行为来决定的——但如何配置“clientOutboundChannel”则更加困难,因为这涉及到应用程序无法控制的因素。因此,有两个额外的属性与消息发送有关:sendTimeLimit和sendBufferSizeLimit。你可以利用这些属性来配置消息发送允许花费的时间长度,以及在向客户端发送消息时可以缓冲的数据量。
总体思路是,在任何给定时间点,只能有一个线程用于向客户端发送消息。同时,所有额外的消息都会被缓冲起来,你可以利用这些设置来决定允许发送消息所花费的时间以及在此期间可以缓冲多少数据。有关重要的额外细节,请参阅Javadoc和XML模式的文档。
以下示例展示了一种可能的配置:
- Java
- Kotlin
- Xml
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024);
}
// ...
}
@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {
override fun configureWebSocketTransport(registration: WebSocketTransportRegistration) {
registration.setSendTimeLimit(15 * 1000).setSendBufferSizeLimit(512 * 1024)
}
// ...
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>
<websocket:transport send-timeout="15000" send-buffer-size="524288" />
<!-- ... -->
</websocket:message-broker>
</beans>
您还可以使用之前提到的WebSocket传输配置来设置允许传入的STOMP消息的最大大小。理论上,WebSocket消息的大小可以几乎是无限的。但实际上,WebSocket服务器会设定限制——例如,Tomcat的限制是8K,Jetty的限制是64K。因此,像stomp-js/stompjs这样的STOMP客户端会将较大的STOMP消息分割成16K大小的片段,并以多个WebSocket消息的形式发送,这样就需要服务器进行缓冲和重新组装。
Spring的STOMP-over-WebSocket支持实现了这一点,因此应用程序可以配置STOMP消息的最大大小,而不受WebSocket服务器特定消息大小的限制。需要注意的是,如果需要的话,WebSocket消息的大小会自动调整,以确保至少能够传输16K个WebSocket消息。
以下示例展示了一种可能的配置:
- Java
- Kotlin
- Xml
@Configuration
@EnableWebSocketMessageBroker
public class MessageSizeLimitWebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(128 * 1024);
}
// ...
}
@Configuration
@EnableWebSocketMessageBroker
class MessageSizeLimitWebSocketConfiguration : WebSocketMessageBrokerConfigurer {
override fun configureWebSocketTransport(registration: WebSocketTransportRegistration) {
registration.setMessageSizeLimit(128 * 1024)
}
// ...
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:message-broker>
<websocket:transport message-size="131072" />
<!-- ... -->
</websocket:message-broker>
</beans>
关于扩展的一个重要点是使用多个应用程序实例。目前,使用简单的代理(broker)是无法做到这一点的。然而,当你使用功能齐全的代理(如RabbitMQ)时,每个应用程序实例都会连接到该代理,从一个应用程序实例广播的消息可以通过代理被发送到通过其他任何应用程序实例连接的WebSocket客户端。