集群方案
- 通过redis的发布订阅的方式,本机启动时订阅根据本机IP、Port的消息;
- 用户建立连接以后将用户名、通道ID、IP、PORT缓存到redis中;
- 消息通过Http接口发送,通过gateway路由到其中一个实例上,保存消息,该实例拿到用户所在的IP、Port,向订阅该服务消息的发布消息;
- 订阅消息的实例接收到消息判断用户是否在线或者是否在本地缓存中,在即发送;
- 用户建立在redis中的缓存过期时间为30分钟,ws中建立心跳,通过小于30分钟的心跳来刷新用户在redis中的缓存信息;超过时间没有心跳,自动删除,防止无效数据的产生;
消息发布/订阅
- 这里的消息订阅和发布主要是针对同一个topic即可;
- 消息只能被消费一次,获取以后就不能再获取已经消费的消息;
- ApplicationRunner 获取程序运行时的上下文信息;
消息发布订阅demo;
/**
* Ordered 因为可能有多个ApplicationRunner接口的实现类,所以先加上Order 防止以后出现冲突;
*/
@Slf4j
@Service
public class RidesTopicListener implements ApplicationRunner, Ordered {
private int order = 0;
@Resource
private RedissonClient redissonClient;
// 消息发布
public void pushMsg(Msg msg){
// 根据消息需要发送得到实例对象组装 topic
RTopic topic = redissonClient.getTopic(builderTopic(msg.getIp(),msg.getPort()));
topic.push(msg);
}
/**
* 消息订阅
*/
@Override
public void run(ApplicationArguments args) throws Exception {
RTopic topic = redissonClient.getTopic(builderTopic());
topic.addListener(Msg.class, (charSequence, mag) -> {
log.info("Redisson监听器收到发送给用户[{}]消息:{}", mag.getUserName(), mag.getContent());
});
}
// 构建topic;这里的topic 只要发送和接收的topic一致就可以收到消息了;
String builderTopic(String ip,int port){
return ip+"_"+port+"_"+"ws_topic";
}
@Override
public int getOrder() {
return ordered;
}
}
用户连接缓存,使用的map映射;(hash对象)
- 这一部分在不同的redis客户端中有不一样的体现
- spring-boot-starter-data-redis ->redisTemplate.opsForHash().put(“key”, “hashkey”, “object”); //解释的来说就是,第一个key比如是用户组,第二个hashkey是用户名,object是用户这个对象的具体信息;
- redisson -> RMapCache<Object,Object> map= redissonClient.getMapCache(“key”); // 获取关于这个键的信息;
- 剩下的操作和直接操作map一致; map.put(“key”, “object”);// 键名称 ,对象信息,
这里redisson 优于上面的一种方式是可以对map下面的具体每一个对象进行淘汰;其他方法用法参考 redisson文档
- 剩下的操作和直接操作map一致; map.put(“key”, “object”);// 键名称 ,对象信息,
key用的刷新和过期
- 常见的redis命令通过expire 实现;在redisson中也是如此;但是在上述针对 hash对象时,多了一个淘汰策略 map.put(“key”, “object”, 1, TimeUnit.MINUTES);// 键名称 ,对象信息,过期时间,过期时间单位;
- 这里淘汰策略会有很多的监听事件但是都不是针对元素的,是针对 redissonClient.getMapCache(“key”) 这个key的,所以需要对内部的对象进行进一步的处理的话需要自己处理;
- 同样由于没有提供针对性的刷新淘汰策略的方法,这里的刷新需要重新取出元素后再放入;