主从断开重连后数据同步 — redis源码分析

在redis 2.8之前,是没有日志追加的,主从每次断开之后,从服务器与主服务器再次连接的时候,主服务器是把自己的全部内容通过bgsave生成一个RDB文件,传递给从服务器,从服务器根据全量的文件初始化redis数据库。在 唐福林大神 给我们开redis培训的时候,就提到了redis的这个问题,一个TCP连接的崩溃就会导致大量数据的传输,以至于不得不hash打散,减小每个端口的数据量。

redis之后,有断开重连,追加日志的功能的,也就是redis的主从断开之后,主新加了300k的内容之后,从库再次连接,不用同步全量的3G内容,只需要同步这新加的300k数据就好。

我们先说一下这个部分重同步的实现机制,主服务器维护一个循环队列,从服务器维护一个offset变量,每次与从服务器同步之后,从服务器都会将同步的字节量加到offset中,表示目前从主服务器获得的全部日志量,主服务器也会将这些传递给从服务器的数据保存在循环队列中,维护两个变量server.repl_backlog_off 以及server.repl_backlog_histlen ,前者表示循环队列中保存的,日志量的最小值,小于该值的日志都不在循环队列中,后者表示队列中数据的长度。加起来就是循环队列的全部数据量。

sync请求的时候,从服务器将offset 传递过来,主服务器判断该offset值之后的数据,是否全部都在循环队列中,如果是,将这些数据全部传递给从服务器,完成数据同步,否则全量重传。

具体怎么做到的呢?故事要从 syncCommand(replication.c)开始说起。

void syncCommand(redisClient *c)

客户端发出sync的请求,事件调度器将对应的操作引导到了这里。

首先,可以进行这种操作的,必须是刚刚连接的客户端,不能是已经同步完毕的从服务器,也不能是哨兵服务器,必须是刚刚连接的从服务器,所以函数的开头判断了该客户端的身份。

if (c->flags & REDIS_SLAVE) return;

新连接的从服务器,但是与主服务器的连接还没有准备好,是不能够同步的,所以也要拒绝。
因为必须保证一个干净的回复缓冲区,为拷贝其他的从客户端准备,所以缓冲区有内容的客户端,也不能sync同步的。

syncCommand 根据 请求是psync,还是sync做了一个判断,psync,也就是只追加缺少部分的数据的命令,sync,是同步全量数据库数据的命令。如果客户端请求的是sync,那直接执行sync,如果请求的是psync,函数会调用masterTryPartialResynchronization(redisClient *c) 尝试进行部分重同步。

之所以说是尝试,是因为需要满足几个条件,才可以,不满足的话,只能是全量sync了。

1.服务器的runid和master_runid必须一致,每个服务器,无论主从,运行的时候,都有一个自己的id,从服务器之后跟之前的主服务器重连的时候,才可以部分重同步,否则只能全量重同步,所以从服务器传来的master_runid必须等于该服务器的id,也就是说这个服务器的之前的主服务器。

2.请求参数中,必须有psync_offset 参数,不是整数,或者超出int范围,都是不可以的。你不告诉我你还差多少,我怎么把你少的给你呢?

如果这两个条件满足,就可以传输追加的数据了,通过函数 addReplyReplicationBacklog(redisClient *c, long long offset)因为是循环队列,所以数据在队列中可能不是顺序存储的,而是分成两段的,所以可能会分两次传输。

传输完毕之后,主服务器将该客户端标记成从服务器,放在从服务器列表里面,成为再也不能sync的从服务器。

上述内容参考了《Redis设计与实现》

Leave a comment

Your email address will not be published.

*