springboot搭建websocket集群

遇到的问题

易登(个人微信登录解决方案) 官网在最初做登录功能的时候,是通过HTTP轮询的方式实现的,后来随着用户逐渐增加,这种方案的弊端逐步展现了,频繁的请求后端接口导致服务器负载增加,在不想增加服务器成本的情况下,将实现方案替换成了websocket技术。

系统最初是在单机状态下运行的,websocket实现起来没有什么问题。随着系统的运行,后来增加了一台服务实现了双服务的集群。但这时在登录系统的时候就遇到了websocket在集群环境下的问题。有时候明明扫码登陆成功了,但系统并未接收到登录状态,导致无法登录系统。

问题根源

前端与服务A建立websocket连接时,服务A会记录websocket的session会话信息,但是服务B并未与前端建立websocket连接。这时如果通过服务B发送消息给前端的时候,由于服务B未与前端建立websocket连接,导致websocket消息无法发送到前端。

类似于集群环境下如果http session会话不进行共享,用户信息在多个服务之间会丢失的情况。

问题解决

既然问题已经找到了,那就好办了,可以将需要发送的消息同时通知服务A和服务B,谁持有session会话信息就由谁发消息不就行了吗?

看到这个小伙伴是不是有些眉目了,这不就是典型的发布订阅模型嘛。

两个服务都订阅同一个渠道,只要这个渠道里有消息,两个服务都去发送这个消息。这样就可以保证消息是可以被发送出去的。

 

解决方案

市面上有许多已经实现了发布订阅模型的方案,比如说MQ框架、Redis等。由于易登系统已经集成了Redis,就没有再引入MQ框架来解决这个问题。

下面说一下如何使用Redis的发布订阅功能来解决这个问题。

解决方案实现

  • 引入 Jedis依赖

    <dependency>
    	<groupId>redis.clients</groupId>
    	<artifactId>jedis</artifactId>
    	<version>3.7.0</version>
    </dependency>
    
  • 创建redis连接池

    @Slf4j
    @Configuration
    @Data
    public class JedisConfig {
    
        @Value("${redis.host}")
        private String host;
        @Value("${redis.port}")
        private Integer port;
        @Value("${redis.user}")
        private String user;
        @Value("${redis.password}")
        private String password;
    
        @Bean
        public JedisPool jedisPool() {
            JedisPool jedisPool = new JedisPool(this.host, this.port, StringUtils.isEmpty(user) ? null : this.user, StringUtils.isEmpty(this.password) ? null : this.password);
            log.info("jedis init success.");
            return jedisPool;
        }
    }
    
  • 自定义自己的redis订阅处理逻辑

    @Slf4j
    public class WsSubscriber extends JedisPubSub {
    
        // 当有消息发布到名称为 ws-channel 的渠道时会被该方法会监听到,服务A和服务B都是可以将听到这个方法内容的,我们需要在这里实现自己的逻辑
        @Override
        public void onMessage(String channel, String message) {
            log.info("jedis Subscriber channel={}, message={}", channel, message);
            // do sth... 这里可以调用websocket发送消息的方法就可以了,这时服务A和服务B都会去发送ws消息
        }
    
        // 当有订阅操作时会被该方法监听到
        @Override
        public void onSubscribe(String channel, int subscribedChannels) {
            log.info("jedis Subscriber channel={}, subscribedChannels={}", channel, subscribedChannels);
            super.onSubscribe(channel, subscribedChannels);
        }
    }
    
  • 在服务启动的时候先订阅一个渠道

@Component
@Slf4j
public class RedisSubscribeConfig {

    @Resource
    private ExecutorService executorService;

    @PostConstruct
    public void config() {
	// 这里另起一个线程来完成订阅操作是为了不影响服务的其他配置的初始化
        executorService.execute(() -> {
	    // 订阅 ws-channel 渠道,该渠道有发布消息时,用我们自定义的订阅类处理
            jedisPool.getResource().subscribe(new WsSubscriber(), "ws-channel");
        });
    }
}

经过上面的处理,最初的问题就被解决了

 原文链接:记忆旅途

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记忆旅途

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值