复制
通过执行SLAVEOF命令、设置slaveof选项,让一个服务器去复制另一个服务器。被复制的服务器称为主服务器(master),对主服务器复制的服务器称为从服务器(slave)。
进行复制中的主从服务器双方的数据库将保存相同的数据,概念上将这种现象称作"数据库状态一致",或简称"一致"。
1. 旧版复制功能的实现
Redis复制功能分为同步(sync)、命令传播(command propagate)两个操作:
- 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
- 命令传播操作用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态。
1.1 同步
向从服务器发送SLAVEOF命令,要求从服务器复制主服务器,从服务器首先需要执行同步操作。
从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令完成,以下是SYNC命令的执行步骤:

1.2 命令传播
同步操作执行完之后,主从服务器两者的数据库达到一致状态。当主服务器执行新的写命令后,主服务器数据库可能被修改,并导致主从服务器状态不再一致。
为了让主从服务器再次回到一致状态,主服务器会将自己执行的那条写命令,发送给从服务器执行,执行后,主从服务器会再次回到一致状态。
2. 旧版复制功能的缺陷
从服务器对主服务器的复制分为以下两种情况:

对于断线后重复制,旧版复制功能虽然也能让主从服务器重新回到一致状态,但效率却非常低。
旧版的复制功能在断线后重复制时,会让主从服务器重新执行一次SYNC命令,主服务器会重新生成RDB文件,包含主服务器所有的键值,发送给从服务器。

3. 新版复制功能的实现
Redis 2.8开始,使用PSYNC命令代替SYNC命令执行复制时的同步操作。
PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)两种模式:

下面是主从服务器执行部分重同步的过程。

4. 部分重同步的实现
部分重同步由以下三个部分构成:
- 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量
- 主服务器的复制积压缓冲区(replication backlog)
- 服务器的运行ID(runID)
4.1 复制偏移量
执行复制的双方——主从服务器都分别维护一个复制偏移量:
- 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N。
- 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N。
4.2 复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小为1MB。
当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区中,如下图所示。

下图是复制积压缓冲区的构造。

从服务器断线重连主服务器后,会将自己的复制偏移量(offset)发送给主服务器,如果offset偏移量后的数据仍然在复制积压缓冲区中,那么主服务器对从服务器执行部分重同步操作,否则执行完整重同步操作。
正确估算和设置复制积压缓冲区的大小非常重要。
复制积压缓冲区的最小大小可以根据公式second * write_size_per_second来估算:
- second为从服务器断线重连上主服务器所需的平均时间(以秒计算)。
- write_size_per_second是主服务器平均每秒产生的写命令数据量(协议格式的写命令的长度总和)。
为安全起见,可以将复制积压缓冲区的大小设置为2 * second * write_size_per_second。
4.3 服务器运行ID
每个Redis服务器,都会在启动时自动生成运行ID,由40个随机的十六进制字符构成。
当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器。断线重连后,从服务器会发送之前保存的复制的主服务器的运行ID给重连后的主服务器,主服务器判断这个ID是否是自己的ID,如果相同,会继续尝试执行部分重同步操作,如果不同,会执行完整重同步操作。
5. PSYNC命令的实现
PSYNC的调用方式有两种:

接收到PSYNC命令的主服务器会向从服务器返回以下三种回复的其中一种,如下图所示。

6. 复制的实现
- 
设置主服务器的地址和端口 SLAVEOF 127.0.0.1 6789从服务器将客户端给定的主服务器IP地址、端口保存到masterhost、masterport中。 struct redisServer{ // ... // 主服务器的地址 char *masterhost; // 主服务器的端口 int masterport; }SLAVEOF是一个异步命令,完成masterhost、masterport属性设置后,从服务器将向客户端返回OK,实际的复制工作在OK返回之后才真正开始执行。 
- 
建立套接字连接 执行完SLAVEOF命令后,从服务器会根据IP、端口创建连向主服务器的套接字连接。 如果从服务器创建的套接字成功连接到主服务器,那么从服务器将为这个套接字关联一个专门用于处理复制工作的文件事件处理器。 主服务器在接受从服务器的套接字连接后,将为该套接字创建相应的客户端状态,并将从服务器看作是一个连接到主服务器的客户端,这时从服务器拥有服务器、客户端两种身份。 
- 
发送PING命令 这个PING命令有两个作用。 PING命令作用
- 
身份验证 从服务器在收到主服务器返回的PONG后,下一步要做的是决定是否进行身份验证,如果从服务器设置了masterauth,进行身份验证,否则不进行身份验证。 在身份验证的情况下,从服务器将向主服务器发送一条AUTH命令,参数为masterauth值。 如果主服务器没有设置requirepass选项,但从服务器设置了masterauth选项,那么主服务器将返回no password is set错误。 所有的错误都会让服务器中止目前的复制工作,并从创建套接字开始重新执行复制,直到身份验证通过,或者从服务器放弃执行复制为止。 
- 
发送端口信息 在身份验证步骤之后,从服务器将执行命令REPLCONF listening-port <port-number>,向主服务器发送从服务器的监听端口号。 主服务器收到这个命令后,会将端口号记录在从服务器所对应的客户端状态的slave_listening_port属性中。 struct redisClient{ // 从服务器监听的端口号 int slave_listening_port; } redisClient;slave_listening_port目前唯一的作用是从服务器在执行INFO replication时打印从服务器的端口号。 
- 
同步 在这一步,从服务器将向主服务器发送PSYNC命令,执行同步操作。 
- 
命令传播 从服务器只要一直接收并执行主服务器发来的写命令即可。 
7. 心跳检测
在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:
REPLCONF ACK <replication_offset>
其中,replication_offset是从服务器当前的复制偏移量。
发送REPLCONF ACK对于主从服务器有三个作用:
- 检测主从服务器的网络连接状态
- 辅助实现min-slaves选项
- 检测命令丢失
7.1 检测主从服务器的网络连接状态
通过向主服务器发送INFO replication,在列出的从服务器列表的lag中,可以看到响应的从服务器最后一次向主服务器发送REPLCONF ACK命令距离现在的时间。
7.2 辅助实现min-slaves选项
min-slaves-to-write、min-slaves-max-lag可以防止主服务器在不安全的情况下执行写命令。
7.3 检测命令丢失
如果因为网络故障,主服务器传播给从服务器的写命令丢失,那么当从服务器向主服务器发送REPLCONF ACK命令时,主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量,然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区中找到从服务器缺失的数据,并将这些数据重新发送给从服务器。
在Redis 2.8以前,即使命令在传播过程中丢失,主服务器、从服务器都不会注意到。
