Redis2.6 - publish/subscribe的BUG

前一段时间由于业务需要,我们需要搬迁服务器到新机房。为了保证系统平滑过渡,我们分成多批搬迁。迁移工作总体还算顺利,期间也遇到了一点点小挫折。下面将介绍一个我印象最为深刻的BUG:redis2.6的publish/subscribe。

进入主题,先介绍下业务场景。

业务场景

我们生产环境使用的redis是2.6版本,redis集群只有两台服务器,一主(A)一从(B)。
制定的redis迁移方案:
1. 先将redis从服务器迁移到新机房;
2. 迁移从服务器完毕后,启动从服务器同步数据。待完成同步数据后,进行主从切换(即从->主,主->从);
3. 最后将redis主节点连接配置解析到新IP。

现有两个服务使用publish/subscribe方式进行通信,publish和subscribe端连接的都是主节点。在上述步骤完成后且未对A节点重启的情况下,subscribe端的服务始终无法接收到publish端推送的消息。

问题分析

通过查看服务日志及追踪,可以排除服务自身的问题。那么问题可能出现在redis。

由于subscribe使用的是长连接,当主从切换后,在原主服务器未重启的情况下,subscribe依旧连接的是A服务器,publish端则连接的是B服务器。难道在这种情况下B服务器收到publish命令后不会往A广播消息?

印象中记得主服务器收到publish命令后会将消息广播到集群其他节点。为了消除猜疑,于是本地模拟了一遍上述流程(注:redis3.0版本),结果显示subscribe端能正常收到消息。

时间一分一秒的过去,由于两个机房有一段的距离,没有办法做到快速回滚。于是拉上其他同事一起讨论。

经过一番讨论和分析,最终我们认为问题出在redis方面的可能性最大。

由于线上的redis版本是2.6,于是从官网下载了一份redis-2.6源码。在pubsub.c文件找到publishCommand代码:

void publishCommand(redisClient *c) {
    int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
    addReplyLongLong(c,receivers);
}

pubsubPublishMessage函数的功能:向订阅该频道的客户端推送消息。addReplyLongLong函数的功能:向客户端返回订阅该消息的客户端数。

果不其然,在redis2.6版本中,并没有向其集群的其他节点广播消息。

我们再看看redis-3.0是如何实现?
publishCommand代码:

void publishCommand(redisClient *c) {

    int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
    if (server.cluster_enabled)
        clusterPropagatePublish(c->argv[1],c->argv[2]);
    else
        forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
    addReplyLongLong(c,receivers);
}

与redis2.6源码对比,redis3.0的publishCommand命令内部多执行了一步。
在redis-cluster模式下系统会调用clusterPropagatePublish; 而我们的生产环境只是主从模式,则调用forceCommandPropagation函数。

void forceCommandPropagation(redisClient *c, int flags) {
    if (flags & REDIS_PROPAGATE_REPL) c->flags |= REDIS_FORCE_REPL;
    if (flags & REDIS_PROPAGATE_AOF) c->flags |= REDIS_FORCE_AOF;
}

这里给客户端增加REDIS_PROPAGATE_REPL标识。

当执行完publishCommand命令后,redis会继续执行propagate函数(注:call内部调用)。

void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,int flags)
{
    //...省略部分代码...
    // 传播到 slave
    if (flags & REDIS_PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}

此时,系统会执行replicationFeedSlaves函数。replicationFeedSlaves的功能就是向当前redis节点的从节点广播消息。

以上就是redis3.0的主节点的publishCommand向从节点广播流程。

总结

关于redis2.6版本的publish/subscribeBUG,在3.0版本已经修复,建议升级到3.0以上版本。
对于这个BUG的处理,我们可以重启相应的应用或者主从切换后重启原主节点。当然,我们也可以在应用监控“主从切换”状态判断是否需要重连。

RedisTemplate是Spring Data Redis提供的一个封装了Jedis客户端操作的工具类,它简化了对Redis的集成和高级操作,包括发布订阅(publish/subscribe)功能。在使用`RedisTemplate`实现pub/sub时,你需要做以下几个步骤: 1. **设置Topic或Channel**: 使用`StringRedisTemplate`的`publish`方法,第一个参数是频道名,第二个参数是要发布的消息。 ```java StringRedisTemplate stringRedisTemplate = ...; stringRedisTemplate.publish("channelName", "yourMessage"); ``` 2. **订阅者接收消息**: 创建一个监听器`MessageListener`,这个监听器实现了`MessageListener<String>`接口,当有消息到达指定频道时,会触发`onMessage`方法处理消息。 ```java @MessageListener("channelName") public void handleMessages(String message) { // 处理接收到的消息 } ``` 3. **注册监听器**: 将监听器注册到`RedisTemplate`中,通常在Spring配置文件或注解的方式下完成。 ```java @Bean public MessageListenerContainer messageListenerContainer(RedisConnectionFactory connectionFactory, ChannelTopicPublisher channelTopicPublisher) { SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); container.setConnectionFactory(connectionFactory); container.setPubSubMessageListener(channelTopicPublisher); return container; } ``` 4. **启用订阅**: 在启动应用时,需要开启订阅容器,如Spring Boot中可以放在`ApplicationRunner`或`CommandLineRunner`里。 ```java messageListenerContainer.start(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值