前面介绍了Redis的持久化机制,那么现在就开始学习Redis的主从复制的功能,学习Redis的主从复制的底层实现原理,那么以后面试也可以多说说这部分知识了!!
在Redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制(replicate)另一个服务器,我们称被复制的服务器为主服务器(master)
,而对主服务器进行复制的服务器为从服务器(slave)
。
例如:现在有两个Redis服务器,地址分别为127.0.0.1:6379和127.0.0.1:6377,如果我们向服务器127.0.0.1:6377发送命令:
127.0.0.1:6377>SLAVEOF 127.0.0.1 6379
OK
那么服务器127.0.0.1:6377是从服务器,127.0.0.1:6379是主服务器。
进行复制中的主从服务器双方的数据库将保存相同的数据,概念上将这种现象称作为“数据库状态一致”。
另外对于复制功能,Redis在2.8版本是一个分界线,2.8之前是旧版的复制方法,2.8之后则采用了新的复制方法。
一、旧版
1、复制
Redis的复制功能分为同步(sync)
和命令传播(command propagate)
两个操作:
- 同步操作将用于将服务器的数据库状态更新至主服务器当前所处的数据库状态
- 命令传播操作则用于主服务器的数据库状态被修改,导致主从服务器的数据库状态不一致时,让主从服务器的数据库重新回到一致状态。
同步执行步骤:
- 从服务器向主服务器发送SYNC命令。
- 收到SYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
- 当主服务器的BGSAVE命令执行完毕时,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库更新至主服务器执行BGSAVE时的数据库状态。
- 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库更新至主服务器当前所处的状态。
2、命令传播
在同步操作执行完毕之后,主从服务器两者的数据库将达到一致状态,但这种一致并不是已成不变的,每当主服务器执行客户端发送的写命令时,主服务器的数据库可能会被修改,导致主从服务器数据库不一致。
为了让主从服务器数据库一致,主服务器需要对从服务器执行命令传播操作:
- 主服务器将自己执行的写命令(造成主从服务器数据库不一致的那个命令),发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器的数据库再次达到一致状态。
3、总结
这个旧版的复制功能还是存在一定的缺陷的:当处于传播阶段时,主从服务器因为网络原因中断了复制,但从服务器通过自动重连重新连接上了主服务器,并继续复制主服务器,但是断线重新复制这个功能会导致主服务器会重新发送一遍RDB文件,但是此时从服务器中可能已经存在了RDB中的数据,若重新执行RDB文件这是很耗资源的操作。
执行SYNC命令主从服务器需要执行的动作如下:
- 主服务器需要执行BGSAVE来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和IO资源
- 主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的事件产生影响
- 从服务器接收到RDB文件后,需要载入RDB文件,载入的期间,从服务器会因为阻塞而没法处理命令请求。
因此SYNC时非常耗费资源的操作,所以Redis有必要保证在真正有需要时才执行SYNC命令。
二、新版
为了解决旧版复制功能在处理断线重新复制的低效问题,Redis在2.8版本开始,使用PSYNC命令代替SYNC命令来执行复制时的同步操作。
PSYNC命令具有完整重同步(full resynchronization)
和部分重同步(partial resynchronization)
两种模式:
- 完整重同步:完整重同步用于处理初次复制情况(即从服务器以前从没有复制过任何主服务器),完成重同步的执行步骤和SYNC命令的执行步骤基本一样,他们都是通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步。
- 部分重同步:部分重同步则用于处理断线后重新复制的情况,当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将两者连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些命令,就可以将数据库更新至主服务器当前的数据库状态。
PSYNC命令的部分重同步模式解决了旧版复制功能在处理断线后重新复制时出现的低效问题。
1、部分重同步的实现
部分重同步功能由一下三个部分组成:
- 主服务的复制偏移量(replication offset)和从服务器的复制偏移量
- 主服务器的复制积压缓冲区(replication backlog)
- 服务器的运行ID(run ID)
1.1 复制偏移量
执行复制的双方分别维护一个复制偏移量:
- 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N
- 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N
例如:现在有一个主服务器和三个从服务器,他们的偏移量都是10086,如果这是主服务器向三个从服务器传播长度为33字节的数据,那么主服务器的偏移量就会更新为10086+33=10119,从服务器收到主服务器传播的数据之后,也会更新自己的偏移量为10119.
此时如果从服务器A断线了,那么从服务器B和从服务器C都会更新成功自己的偏移量为10119,但是从服务器A因为断线偏移量任然为10086,这就说明从服务器A和主服务器不一致。
如果从服务器A在断线之后重新连接主服务器,那么接下来从服务器将要向主服务器发送PSYNC命令,报告从服务器A的复制偏移量为10086,那么这时主服务器就需要对从服务器判断是执行完整重同步还是部分重同步。这个判断是根据复制积压缓冲区来判断的。
1.2 复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度先进先出的队列,默认大小为1MB.
当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里面。如下图:
因此主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录响应的复制偏移量。如下图:
当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:
- 如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区中,那么主服务器将对从服务器执行部分同步操作。(也就是以前的数据已经复制到从服务器,只要把断线期间产生的写命令复制到从服务器就可以)
- 如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整同步操作。(也就是主服务器中的复制积压缓冲区已经被同步出去了,从服务器已经丢失了那部分数据,就需要将数据全部同步到从服务器中。)
1.3 服务器运行ID
除了复制偏移量和复制积压缓冲区之外,实现部分重同步还需要用到服务器运行ID。、
- 每个Redis服务器,不论主服务器还是从服务器都会有自己的运行ID
- 运行ID在服务器启动时自动生成,由40个随机的十六进制字符组成。当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器,而从服务器则会将这个运行ID保存起来。当从服务器断线重连主服务器时,从服务器将向当前连接的主服务器发送保存的运行ID:
- 如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以继续执行部分重同步操作。
- 如果从服务器保存的运行ID和当前连接的主服务器运行ID不同,那么说明从服务器断线之前复制的主服务器并不是当前连接的这个服务器,主服务器将对从服务器执行完整重同步操作。