最近两日在调试redis代码时遇到两个问题:
1.主从复制过程中,slave端的readSyncBulkPayload会读到错误的信息(主服务器发来的PING命令的一部分),导致slave端一直在向主服务器发送同步请求。
2.主从同步结束后,进入命令传播阶段。主服务器向从服务器传播命令后,如果命令执行成功,从服务器会返回+OK\r\n,主服务器不能识别这个命令,导致失败。
经过两天的调试后发现问题的发生原因如下:
1.如下图:
主服务器在发送PING时,从服务器会有一段时间处于readSyncBulkPayload事件处理阶段,如果这时读到了PING就会出错。
而redis源码是这样避免这个问题的:
int prepareClientToWrite(redisClient *c)
{
if ((c->flags & REDIS_MASTER) && !(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;
if (c->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||c->replstate == REDIS_REPL_ONLINE) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
return REDIS_OK;
}
可以看到它在这才判断client的状态,如果他的状态处于NONE或者ONLINE才会绑定写事件。而它在ONLINE时已完成了同步,进入readQueryFromClient函数处理的阶段了。
redis在这里设计的思想是,slave如果处于END之后的阶段,主服务器的命令对它来说就是新的命令了,而当它处于ONLINE阶段时,才能正式的从主服务器读取命令。
2.从服务器向主服务器发送+PONG时,进入perpareClientToWrite()。结果发现它是主服务器而又没有REDIS_MASTER_FORCE_REPLY,所以不会向主服务器发送PONG。
而之所以从服务器向主服务器发送ACK会成功,原因如下:
void replicationSendAck(void) {
redisClient *c = server.master;
int optVal = 0;
int optLen = sizeof(optVal);
getsockopt(c->fd, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
if (c != NULL) {
c->flags |= REDIS_MASTER_FORCE_REPLY;
addReplyMultiBulkLen(c, 3);
addReplyBulkCString(c, "REPLCONF");
addReplyBulkCString(c, "ACK");
// 发送偏移量
addReplyBulkLongLong(c, c->reploff);
c->flags &= ~REDIS_MASTER_FORCE_REPLY;
}
}
因为设置了 REDIS_MASTER_FORCE_REPLY,就会成功绑定sendReplyToClient