一、说明
在Redis中, 用户可以通过执行SLAVEOF命令或者设置slaveof选项, 让一个服务器去复制( replicate) 另一个服务器, 我们称呼被复制的服务器为主服务器( master) , 而对主服务器进行复制的服务器则被称为从服务器( slave)。
数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
二、怎样实现主从复制
2.1、两种方式
- SYN
SYNC命令是一个非常耗费资源的操作,每次执行SYNC命令, 主从服务器需要执行以下动作:
1) 主服务器需要执行BGSAVE命令来生成RDB文件, 这个生成操作会耗费主服务器大量的CPU、 内存和磁盘I/O资源。
2) 主服务器需要将自己生成的RDB文件发送给从服务器, 这个发送操作会耗费主从服务器大量的网络资源( 带宽和流量) , 并对主服务器响应命令请求的时间产生影响。
3) 接收到RDB文件的从服务器需要载入主服务器发来的RDB文件, 并且在载入期间, 从服务器会因为阻塞而没办法处理命令请求。
因为SYNC命令是一个如此耗费资源的操作, 所以Redis有必要保证在真正有需要时才执行SYNC命令
- PSYN
PSYNC命令具有完整重同步( full resynchronization) 和部分重同步( partial resynchronization) 两种模式:
·其中完整重同步用于处理初次复制情况: 完整重同步的执行步骤和SYNC命令的执行步骤基本一样, 它们都是通过让主服务器创建并发送RDB文件, 以及向从服务器发送保存在缓冲区里面的写命令来进行同步。
·而部分重同步则用于处理断线后重复制情况: 当从服务器在断线后重新连接主服务器时, 如果条件允许, 主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器, 从服务器只要接收并执行这些写命令, 就可以将数据库更新至主服务器当前所处的状态。
为了解决旧版复制功能(SYN)在处理断线重复制情况时的低效问题, Redis从2.8版本开始, 使用PSYNC命令代替SYNC命令来执行复制时的同步操作。
2.2、部分重同步的实现
部分重同步的功能由以下三个部分构成:
- 复制偏移量: 执行复制的双方——主服务器和从服务器会分别维护一个复制偏移量,·主服务器每次向从服务器传播N个字节的数据时, 就将自己的复制偏移量的值加上N;·从服务器每次收到主服务器传播来的N个字节的数据时, 就将自己的复制偏移量的值加上N。
主从服务器数据一致,复制偏移量是相同的;相反,如果不一致则表示主从数据不一致; - 复制积压缓冲区: 复制积压缓冲区是由主服务器维护的一个固定长度( fixed-size) 先进先出( FIFO) 队列, 默认大小为1MB。
当从服务器重新连上主服务器时, 从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器, 主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:·如果offset偏移量之后的数据( 也即是偏移量offset+1开始的数据) 仍然存在于复制积压缓冲区里面, 那么主服务器将对从服务器执行部分重同步操;·相反, 如果offset偏移量之后的数据已经不存在于复制积压缓冲区, 那么主服务器将对从服务器执行完整重同步操作。 - 服务器运行ID: 每个redis服务器在启动时都会自动生成一个运行ID,由40个随机的十六进制字符组成,当从服务器对主服务器进行初次复制时, 主服务器会将自己的运行ID传送给从服务器, 而从服务器则会将这个运行ID保存起来。
当从服务器断线并重新连上一个主服务器时, 从服务器将向当前连接的主服务器发送之前保存的运行ID:
1.如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同, 那么说明从服务器断线之前复制的就是当前连接的这个主服务器, 主服务器可以继续尝试执行部分重同步操作。
2.相反地, 如果从服务器保存的运行ID和当前连接的主服务器的运行ID并不相同, 那么说明从服务器断线之前复制的主服务器并不是当前连接的这个主服务器,主服务器将对从服务器执行完整重同步操作。
2.3 PSYNC 命令的执行过程
- 1.如果从服务器以前没有复制过任何主服务器, 或者之前执行过SLAVEOF no one命令, 那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1命令, 主动请求主服务器进行完整重同步( 因为这时不可能执行部分重同步) 。
- 2.相反地, 如果从服务器已经复制过某个主服务器, 那么从服务器在开始一次新的复制时将向主服务器发送PSYNC 命令: 其中runid是上一次复制的主服务器的运行ID, 而offset则是从服务器当前的复制偏移量, 接收到这个命令的主服务器会通过这两个参数来判断应该对从服务器执行哪种同步操作。
根据情况, 接收到PSYNC命令的主服务器会向从服务器返回以下三种回复的其中一种:
·如果主服务器返回+FULLRESYNC 回复, 那么表示主服务器将与从服务器执行完整重同步操作: 其中runid是这个主服务器的运行ID, 从服务器会将这个ID保存起来, 在下一次发送PSYNC命令时使用; 而offset则是主服务器当前的复制偏移量, 从服务器会将这个值作为自己的初始化偏移量。
·如果主服务器返回+CONTINUE回复, 那么表示主服务器将与从服务器执行部分重同步操作, 从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了。
·如果主服务器返回-ERR回复, 那么表示主服务器的版本低于Redis 2.8, 它识别不了PSYNC命令, 从服务器将向主服务器发送SYNC命令, 并与主服务器执行完整同步操作。
流程图如下图所示:
2.4 复制的具体执行步骤
-
1.从服务器设置主服务器的地址和端口
-
2.建立套接字连接
-
3.从服务器向主服务器发送PING命令确认是否连接成功
-
4.从服务器身份验证
-
5.从服务器向主服务器发送监听端口信息,主服务器记录下从服务器的端口信息
-
6.从服务器向主服务器发送PSYN命令进行同步
-
7.主服务器进入命令传播状态
2.5、心跳检测
在命令传播阶段,从服务器会每隔 1S 向主服务器发送replication ack <replication_offset> 命令,作用是:
- 检测主从服务器的网络状态(保证主从服务器之间的连接正常)
- 辅助实现min_slaves选项
Redis的min-slaves-to-write和min-slaves-max-lag两个选项可以防止主
服务器在不安全的情况下执行写命令。
举个例子, 如果我们向主服务器提供以下设置:
min-slaves-to-write 3
min-slaves-max-lag 10
表示:从服务器的数量少于3个, 或者三个从服务器的延迟( lag) 值都大于或等于10秒时, 主服务器将拒绝执行写命令, 这里的延迟值就是上面提到的INFO replication命令的lag值。
- 检测命令丢失
如果因为网络故障, 主服务器传播给从服务器的写命令在半路丢失, 那么当从服务器向主服务器发送REPLCONF ACK命令时, 主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量, 然后主服务器就会根据从服务器提交的复制偏移量, 在复制积压缓冲区里面找到从服务器缺少的数据, 并将这些数据重新发送给从服务器。
三、总结
- Redis 2.8以前的复制功能不能高效地处理断线后重复制情况, 但Redis 2.8新添加的部分重同步功能可以解决这个问题。
- 部分重同步通过复制偏移量、 复制积压缓冲区、 服务器运行ID三个部分来实现。
-在复制操作刚开始的时候, 从服务器会成为主服务器的客户端, 并通过向主服务器发送命令请求来执行复制步骤, 而在复制操作的后期, 主从服务器会互相成为对方的客户端。 - 主服务器通过向从服务器传播命令来更新从服务器的状态, 保持主从服务器一致, 而从服务器则通过向主服务器发送命令来进行心跳检测, 以及命令丢失检测。
参考资料:《redis设计与实现》
不积跬步,无以至千里;不积小流,无以成江海!