前一段时间由于业务需要,我们需要搬迁服务器到新机房。为了保证系统平滑过渡,我们分成多批搬迁。迁移工作总体还算顺利,期间也遇到了一点点小挫折。下面将介绍一个我印象最为深刻的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/subscribe
BUG,在3.0版本已经修复,建议升级到3.0以上版本。
对于这个BUG的处理,我们可以重启相应的应用或者主从切换后重启原主节点。当然,我们也可以在应用监控“主从切换”状态判断是否需要重连。