向2.8版本redis发送slaveof,将自己变成自己的slave(简称slaveof self)是会返回+OK的,因为响应slaveof命令时,只是设置下master,接下来的同步都是异步进行的。
127.0.0.1:6379> set key value
OK
127.0.0.1:6379> slaveof 127.0.0.1 6379
OK
127.0.0.1:6379> get key
"value"
(1.03s)
127.0.0.1:6379> get key
"value"
(7.95s)
slaveof self之后,发现redis仍然是可以服务的,但请求响应慢了很多,一个请求通常持续好几秒钟,为什么会这样?
step1. 当redis接收到slaveof后,将masterhost设置为自己,然后返回OK。
step2. redis在后台建立到master的连接,这条连接的两端都是这个redis自身,两端分别称为master、slave
step3. 连接建立后,slave调用syncWithMaster与master建立同步
step4. slave向master异步发送PING,master回复PONG
step5. slave向master同步发送REPLCONF,调用sendSynchronousCommand,这里会阻塞的等待5s,直到超时;而由于redis是单线程,此时请求发送给master(实际上是自己),而master又阻塞在这个请求上,所以最终导致REPLCONF一定会超时,slave日志里会打印
Master does not understand REPLCONF listening-port: -Reading from master: Connection timed out
step6. slave同步发送PSYNC命令给master,同样因为自己正阻塞等待,最后这个请求也一定会超时,日志里会打印
Unexpected reply to PSYNC from master: -Reading from master: Connection timed out
step7. slave将读事件处理handler设置为readSyncBulkPayload(6超时,slave上认为没有错误,个人觉得这里的实现是有问题的),然后进入下一次事件循环,此时redis作为master收到了5、6里自己发出的请求,回复了+OK;读事件接下来会触发slave执行readSyncBulkPayload,从master读取rdb文件的长度,而实际读到的是+OK,于是认为协议错了,就关闭了到master的连接,日志里会打印
protocol from MASTER, the first byte is not '$' (we received '+OK'), are you sure the host and port are right?
redis会不断重复1-7的步骤,当接受到正常的请求时,如果redis刚好阻塞在同步发送REPLCONF、PSYNC(两个超时各5s,共10s)的过程中,则需要等待这些请求超时,redis下一次进入事件循环才处理请求,所以用户看到redis的请求延时很大(0-10s不等)。
slaveof self会使redis进入循环尝试主备同步的状态,对服务影响非常大,所以运维redis时千万得小心。