好久没写文章了,记录一下最近的工作。
背景:最近使用websocket发送消息到通知前端操作,在单节点情况下,前端与后端点对点是没有问题的,但是在分布式情况下后端是不知道前端连接到的是哪个节点的websocket。为了处理这种情况引用了中间件redis或者mq,下面要说的是redis实现。
一、redis消息监听
redis可以实现很多东西譬如缓存、分布式锁等等,基本很多项目都会接入redis,这里就用到redis的消息监听,来实现websocket消息的发送。
先写一个监听配置类,注册redis方法与websocket主题映射
package springbootredis;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.messaging.simp.SimpMessagingTemplate;/**
*
* 描述:redis 消息监听
* @author sakyoka
* @date 2024年3月3日 下午3:26:21
*/
@Configuration
public class RedisMessageListenerConfiguration {@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SocketTopic {/**socket主题*/
String value() default "";
/**名称*/
String name() default "";
}
public static class RedisTopic{
@SocketTopic(value = "/topic/socketTest", name = "前端websocket订阅主题")
public static final String REDIS_METHOD_TEST = "test";
}
@Bean
public RedisMessageListenerContainer redisContainer() {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
//注册所有的redis监听主题
Stream.of(RedisTopic.class.getFields())
.forEach(e -> {
SocketTopic socketTopic = e.getAnnotation(SocketTopic.class);
try {
Object v = e.get(null);
MessageListener messageListener = new MessageListener() {
@Override
public void onMessage(Message message, byte[] pattern) {
String value = new String(message.getBody(), StandardCharsets.UTF_8);
System.out.println("接收消息:"+ value);
simpMessagingTemplate.convertAndSend(socketTopic.value(), value);
}
};
System.out.println("注册监听reids方法:"+ v +",转发到websocket主题:"+ socketTopic.value());
container.addMessageListener(new MessageListenerAdapter(messageListener), new PatternTopic(v.toString()));
} catch (Exception e1) {
throw new RuntimeException("注册:"+ socketTopic.name() +"监听失败", e1);
}});
return container;
}
}
在需要添加发送消息的地方,发送redis消息
stringRedisTemplate.convertAndSend(RedisMessageListenerConfiguration.RedisTopic.REDIS_METHOD_TEST, "test message");
ok,通过reids在每个节点的方法监听转发带每个服务的websocket主题。
问题延伸
分布式websocket连接延伸问题,多个节点情况下,需要保证一次多次访问连接到同一个节点,不然会连接失败,这个大家是怎么处理的?