主要问题
集群模式下,WebSocket Session共享问题,例如有A,B两台服务器,与客户端建立WebSocket连接的是A服务器,但是处理和结果推送是在B服务器上执行的,因为B服务器上没有保存与客户端WebSocket连接的Session, 这就导致了数据是无法推送到客户端的。
设计方案
1.第一种是nginx基于ip_hash的会话保持,nginx可以基于客户端ip进行负载均衡,在upstream里设置ip_hash,这样可以基于同一个C类地址段中的同一个客户端,选择同一个服务器,除非服务器宕机才会替换
2.WebSocket连接Session是无法序列化的,只能保存在本地服务器中,因此服务端在推送消息的时候就需要找到保存对应Session的服务器,然后通过Session将数据推送到前端。利用中间件特性例如Redis的消息订阅发布机制。
实现思路
后端服务启动时订阅Redis的topic,当服务器推送数据时,先将消息推送到Redis对应的topic上,然后所有订阅这个topic的服务器都能接收到要推送的消息,如果本地WebSocket上存在要发送的Session会话,则将消息发送出去
后端设计架构图
以下代码经过测试
1.使用Jedis客户端
引入依赖
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
Redis配置信息
#redis配置文件
# Redis服务器地址
spring.redis.host=10.100.159.157
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=50
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=1000
# 连接超时时间(毫秒)
spring.redis.timeout=2000
Jedis客户端
/**
* @Description
* @Author yjf44568
* @Date 2021/7/22 11:07
*/
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.jedis.pool.max-wait}")
private int maxWaitMillis;
@Bean
public JedisPool jedisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
jedisPoolConfig.setMaxTotal(maxActive);
return new JedisPool(jedisPoolConfig, host, port, timeout);
}
}
WebSocket配置
/**
* 配置类的作用是要注入ServerEndpointExporter,这个备案会自动注册使用@ServerEndPoint注解声明的Websocket endpoint
* 如果是使用独立的servlet容器,而不是使用springboot的内主容器,不要注入ServerEndpointExporter,因为它将由容器自己提供和管理
* @Description
* @Author yjf44568
* @Date 2021/7/30 14:53
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
WebSocket连接与消息处理
package com.perf.ws;
import com.google.gson.JsonObject;
import