Redis的主从复制原理
redis的主从复制原理
1、从库向主库发送sync命令,也就是从库向主库发送同步请求;
2、当主库接受到sync命令后,会执行bgsave命令(保存此刻主库的一个快照),创建一个RDB文件,创建RDB文件期间主库上的执行过的命令都会被保存到缓冲区中;
3、当主库执行完bgsave时,会向从库发送RDB文件,从库接受该文件并加载该文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态;
4、主库将缓冲区的所有写命令发给从库执行;
5、至此可以认为redis主从建立成功,之后主库的每一个写命令都会传到从库上执行。
复制原理说明:
master创建RDB文件是通过一个子进程进行的,所以master依然可以处理客户端发来的请求。但这也导致了在保存RDB文件期间,“键空间”可能发生变化(譬如接收到一个客户端请求,执行”set name diaocow”命令),因此为了保证数据同步的一致性,master会在保存RDB文件期间,把接受到的这些可能变更数据库“键空间”的命令保存到缓冲区中。
下图比较完整地反映出redis的主从建立过程示意图:
Redis主从建立过程
说明:
redis目前的复制是异步的,只保证最终一致性,而不是强一致性。具体的可以理解为:主库上的写命令操作后传到从库再重现一遍,从库执行完后主从是完全同步的,这是最终一致性;如果主库上的写命令传到从库上执行成功后,主库上才会反馈该命令执行成功,那么这是强一致性。
命令传播
在上面的同步操作执行完毕后,主从库的状态就是一致的了,但这种一致并不是一致不变的。每当主服务器接收到一个写命令时,主服务器的数据就会发生改变,而从服务器并没有接收到该命令。所以为了保证主从的一致,需要主服务器将写命令传播到从服务器上,在从服务器上实现一遍,这样主从就始终一致了。
主从断线后重连
在redis2.8版本之前,主从断线后,实现主从重同步的方式就是从库向主库发送sync命令,主库生成RDB文件传到从库,从库应用该文件实现同步。这样的实现方式和最开始的主从建立方式一模一样。其实现方式可以用下图表示:
redis2.8前的主从断线重连示意图
但是这样实现重连的方式太低效了,从库实际上只需要将断开期间主库变化的数据同步起来即可,为了让从服务器补足一小部分缺失的数据,却要让主从重新执行依次sync命令,这种做法过于低效。
为了解决旧版复制功能在处理断线重复制情况时的低效问题,redis从2.8开始使用PSYNC命令代替SYNC命令来执行复制时的同步操作。
PSYNC命令具有完整重同步和部分重同步两种模式:
·完整重同步用于处理初次复制情况:完整重同步的执行步骤和sync的执行步骤基本是一样的。
·部分重同步则用于处理断线后重复制情况:当从服务器重新连接上主服务器时,如果条件允许,主服务器可以将连接断开期间执行过的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以达到主服务器当前所处状态。
下图可以形象地反映PSYNC命令下重复制过程:
PSYNC命令下的重复制过程
部分重同步的实现原理
部分重同步功能主要由以下三个部分构成:
·主服务器的复制偏移量和从服务器的复制偏移量
·主服务器的复制积压缓冲区
·服务器的运行ID
下面分别介绍这三个部分
1、复制偏移量
在复制过程中,主从服务器都会分别维护一个复制偏移量:
·主服务器每次向从服务器传播N个字节的数据时,就会将自己的复制偏移量的值加上N;
·从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N。
例如下图中,主从服务器的偏移量都为10086:
image
如果主服务器发送33字节的数据,那么主从的复制偏移量都更新为10119,如下图:
image
如果在服务器发送33字节数据之前,从服务器A断线了,那么主服务器传播的数据只有B和C能够接收到,主服务器和从服务器B、C的复制偏移量都为10119,而A的偏移量为10086,如下图:
image
假设从服务器A在断线之后就立即重新连接主服务器,并且成功。那么接下来A会向主服务器发送psync命令,报告A当前的复制偏移量为10086,那么这时,主服务器应该对从服务器执行完整重同步还是部分重同步呢?如果执行部分重同步的话,主服务器又如何补偿从服务器A在断线期间丢失的那部分数据呢?以上问题的答案都和复制积压缓冲区有关。
2、复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度先进先出队列,默认大小为1MB。当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里。复制缓冲区里保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量,大致如下图所示:
image
当从服务器重新连上主服务器时,从服务器会通过psync命令将自己的复制偏移量发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:
·如果偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面,那么服务器将对从服务器执行部分重同步操作;
·相反,如果偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作。
所以,复制积压缓冲区的大小似乎相当重要,正确估算和设置复制积压缓冲区的大小非常重要。复制缓冲区的最小大小可以根据下面的公式估算:
second * write_size_per_second
second为从服务器断线后重连上主服务器所需的平均时间;write_size_per_second则是主服务器平均每秒产生的写命令数据量。为了安全起见,可以将复制积压缓冲区的大小设为:
2 * second * write_size_per_second
复制积压缓冲区的大小与参数repl-backlog-size有关。
3、服务器运行ID
除了复制偏移量和复制积压缓冲队列,实现重同步还需要服务器运行ID(run
ID)。每个redis服务器,不论主服务器还是从服务器都有自己的运行ID,运行ID在服务器启动时自动生成,由40个随机的十六进制字符组成。
当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器。当从服务器断线并重新连上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID:
·如果从服务器保存的ID和当前连接的主服务器的运行ID相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以尝试执行部分重同步操作;
·如果从服务器保存的ID和当前连接的主服务器的运行ID不同,则从服务器断线之前复制的不是当前连接的这个主服务器,主服务器就将对从服务器执行完整重同步操作。
PSYNC命令的具体实现
PSYNC命令的调用方法有两种:
·如果从服务器以前没有复制过任何主服务器,或者之前执行过slaveof no one命令,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1命令,主动请求主服务器进行完整重同步(因为这时不可能执行部分重同步);
·如果从服务器之前已复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC 命令。runid是上一次复制的主服务器ID,而offset则是从服务器当前的复制偏移量。接收到该命令的主服务器通过这两个参数来判断执行何种同步操作。
根据情况,接收到PSYNC命令的主服务器会向从服务器返回以下三种回复之一:
·主服务器返回:+FULLRESYNC 。那么表示主服务器将与从服务器执行完整重同步操作,runid是这个主服务器的运行ID,从服务器会将这个ID保存下来,在下次发送PSYNC命令时使用;而offset则是主服务器当前的偏移量,从服务器会将这个值作为自己的初始化偏移量。
·主服务器返回:+CONTINUE。那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来即可。
·主服务器返回:-ERR。表示主服务器版本低于2.8,它识别不了PSYNC命令。之后从服务器将发送sync命令,与主服务器执行完整重同步操作。
注意:如果是从库宕机了,那么恢复后直接就是完全同步;如果仅是主从掉线,从库还在线,那么则视情况而定。