redis哨兵集群,消息发布订阅问题

166 阅读2分钟

前言

最近在研究redis的发布订阅功能,在单机redis服务环境下,你好我好大家好,消息都能正常发布接收到,但是在哨兵集群环境下,在节点突然断网断电的情况下,就发生了消息无法订阅的问题(当然这个问题也不是必现,但是是很容易复现的),目前该问题得到了解决。这个问题研究了两天,现在特意将研究过程记录下来。

redis的发布订阅功能原理

翻阅查找了很多网站资料,都没有找到同样问题的介绍,后来没办法,只能硬着头皮去翻redis订阅和发布的源码,找发布订阅的原理。大家也可以去网站查找这个原理,大致就是:

每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。 

问题定位

看到redis的发布订阅原理后,结合我实际的操作情况,就开始猜想,问题是不是出在pubsub_channels信息上面,于是开始查看每个节点的pubsub_channels信息,发现哨兵集群模式下,每个节点的pubsub_channels信息不一致,集群会异步同步master的存储数据,但是没有同步这个pubsub_channels信息,原因也很简单,redis的发布订阅是一个个tcp长链接,就算同步过去了,也不会自主发起重连。突然断网断电,导致客户端跟master的tcp链接断掉,集群重新选举出新的master后,新的master的pubsub_channels信息会更新,但是客户端订阅的pubsub_channels信息还是老的tcp链接,所以才出现了订阅不到消息的问题。

问题解决

定位出这个问题后,由于项目用的是spring-data-redis连接驱动,redis重新选举后会自动重连,所以只需要刷新订阅容器的连接信息,具体代码如下

public void refreshRedisContainer(MessageListener redisMsgListener,RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    //redis 连接工厂,重新设置这个参数即可
    container.setConnectionFactory(connectionFactory);
    //设置运行任务池
    container.setTaskExecutor(iniTaskScheduler());
    //监听key过期事件
    container.addMessageListener(redisMsgListener, new PatternTopic("__keyevent@0__:expired"));
    container.setRecoveryInterval(0);

    //--------------开始监听-----------------------
    //定义监听器监听的Redis的消息
    container.addMessageListener(redisMsgListener,new ChannelTopic("test"));
    //--------------结束监听------------------------


}