springboot builds websocket cluster

Problems encountered

When the official website of Yideng (Personal WeChat Login Solution) first implemented the login function, it was implemented through HTTP polling. Later, as the number of users gradually increased, the disadvantages of this solution gradually emerged, with frequent requests for the back-end interface As a result, the server load increased. Without increasing server costs, the implementation solution was replaced with websocket technology.

The system was initially run in a stand-alone state, and there was no problem in implementing websocket. With the operation of the system, a service was later added to realize a dual-service cluster. But at this time, when logging into the system, I encountered a problem with websocket in a cluster environment. Sometimes the login by scanning the QR code is successful, but the system does not receive the login status, resulting in the inability to log in to the system.

Source of the problem

When the front end establishes a websocket connection with service A, service A records the session information of the websocket, but service B does not establish a websocket connection with the front end. At this time, if a message is sent to the front end through service B, the websocket message cannot be sent to the front end because service B has not established a websocket connection with the front end.

This is similar to the situation where user information will be lost between multiple services if the http session is not shared in a cluster environment.

problem solved

Now that the problem has been found, it is easy to solve. You can notify both service A and service B of the message that needs to be sent. Isn't it enough that whoever holds the session information can send the message?

Are you a little bit confused when you see this little friend? Isn't this a typical publish-subscribe model?

Both services subscribe to the same channel. As long as there is a message in this channel, both services will send the message. This ensures that the message can be sent.

 

solution

There are many solutions on the market that have implemented the publish-subscribe model, such as MQ framework, Redis, etc. Since the Yideng system has integrated Redis, there is no need to introduce the MQ framework to solve this problem.

Let's talk about how to use Redis's publish and subscribe function to solve this problem.

Solution implementation

  • Introduce  Jedisdependencies

    <dependency>
    	<groupId>redis.clients</groupId>
    	<artifactId>jedis</artifactId>
    	<version>3.7.0</version>
    </dependency>
    
  • Create a redis connection pool

    @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;
        }
    }
    
  • Customize your own redis subscription processing logic

    @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);
        }
    }
    
  • Subscribe to a channel when the service starts

@Component
@Slf4j
public class RedisSubscribeConfig {

    @Resource
    private ExecutorService executorService;

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

After the above processing, the initial problem is solved

 Original Link: Memory Journey

Guess you like

Origin blog.csdn.net/wuchenxiwalter/article/details/127342593