一、背景
單機節點下,WebSocket連接成功後,可以直接發送消息。而多節點下,連接時通過nginx會代理到不同節點。
假設一開始用戶連接了node1的socket服務。觸發消息發送的條件的時候也通過nginx進行代理,假如代理轉到了node2節點上,那麼node2節點的socket服務就發送不了消息,因爲一開始用戶註冊的是node1節點。這就導致了消息發送失敗。
爲了解決這一方案,消息發送時,就需要一箇中間件來記錄,這樣,三個節點都可以獲取消息,然後在根據條件進行消息推送。
二、解決方案(springboot 基於 Redis發佈訂閱)
1、依賴
<!-- redis -->
org.springframework.boot
spring-boot-starter-data-redis
<!-- websocket -->
org.springframework.boot
spring-boot-starter-websocket
2、創建業務處理類 Demo.class,該類可以實現MessageListener接口後重寫onMessage方法,也可以不實現,自己寫方法。
importcom.alibaba.fastjson.JSON;
importcom.dy.service.impl.OrdersServiceImpl;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.data.redis.connection.Message;
importorg.springframework.data.redis.connection.MessageListener;
importorg.springframework.stereotype.Component;
importjava.util.HashMap;
/**
*@program:
*@description: redis消息訂閱-業務處理
*@author: zhang yi
*@create: 2021-01-25 16:46
*/
@Component
publicclassDemoimplementsMessageListener{
Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
publicvoidonMessage(Message message,byte[] pattern){
logger.info("消息訂閱成功---------");
logger.info("內容:"+message.getBody());
logger.info("交換機:"+message.getChannel());
}
}
3、創建PubSubConfig配置類
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.cache.annotation.EnableCaching;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.data.redis.connection.RedisConnectionFactory;
importorg.springframework.data.redis.core.StringRedisTemplate;
importorg.springframework.data.redis.listener.PatternTopic;
importorg.springframework.data.redis.listener.RedisMessageListenerContainer;
importorg.springframework.data.redis.listener.adapter.MessageListenerAdapter;
/**
*@program:
*@description: redis發佈訂閱配置
*@author: zhang yi
*@create: 2021-01-25 16:49
*/
@Configuration
@EnableCaching
publicclassPubSubConfig{
Logger logger = LoggerFactory.getLogger(this.getClass());
//如果是多個交換機,則參數爲(RedisConnectionFactory connectionFactory,
// MessageListenerAdapter listenerAdapter,
// MessageListenerAdapter listenerAdapter2)
@Bean
RedisMessageListenerContainercontainer(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container =newRedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 可以添加多個 messageListener,配置不同的交換機
container.addMessageListener(listenerAdapter,newPatternTopic("channel:demo"));
//container.addMessageListener(listenerAdapter2, new PatternTopic("channel:demo2"));
returncontainer;
}
/**
* 消息監聽器適配器,綁定消息處理器,利用反射技術調用消息處理器的業務方法
*@paramdemo 第一步的業務處理類
*@return
*/
@Bean
MessageListenerAdapterlistenerAdapter(Demo demo){
logger.info("----------------消息監聽器加載成功----------------");
// onMessage 就是方法名,基於反射調用
returnnewMessageListenerAdapter(demo,"onMessage");
}
/**
* 多個交換機就多寫一個
*@paramsubCheckOrder
*@return
*/
//@Bean
//MessageListenerAdapter listenerAdapter2(SubCheckOrder subCheckOrder) {
// logger.info("----------------消息監聽器加載成功----------------");
// return new MessageListenerAdapter(subCheckOrder, "onMessage");
//}
@Bean
StringRedisTemplatetemplate(RedisConnectionFactory connectionFactory){
returnnewStringRedisTemplate(connectionFactory);
}
}
4、消息發佈
@Autowired
privateRedisTemplate redisTemplate;
redisTemplate.convertAndSend("channel:demo","我是內容");
socket連接成功。
socket消息推送時,把信息發佈到redis中。
socket服務訂閱redis的消息,訂閱成功後進行推送。集羣下的socket都能訂閱到消息,但是隻有之前連接成功的節點能推送成功,其餘的無法推送。