共计 3665 个字符,预计需要花费 10 分钟才能阅读完成。
1. 复制
2. 数据库状态一致
主从复制,服务器双方数据库将保存相同的数据,这种现象称为“数据库状态一致”
3. 执行方式
>>>slaveof 127.0.0.1 6379
4. 旧版复制功能的实现(2.8 以前的版本)
复制功能都分为两个基本步骤:同步和命令传播
同步:将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
命令传播:主服务器的数据库状态被修改,导致主从服务器的数据库状态不一致,让主从服务器数据库重新回到一致状态。
1. 同步
当客户端向从服务器发送 slaveof 命令,要求从服务器复制主服务器时,从服务器首先需要执行同步操作,也就是将从服务器的数据库状态更新至主服务器当前所处的数据库状态。而从服务器对主服务器的同步操作需要通过向主服务器发送 SYNC 命令来完成。
从服务器发送 SYNC 命令的执行步骤:
a. 从服务器向主服务器发送 SYNC 命令。
b. 收到 SYNC 命令的主服务器执行 BGSAVE 命令,在后台生成一个 RDB 文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
c. 当主服务器的 BGSAVE 命令执行完毕时,主服务器会将 BGSAVE 命令生成的 RDB 文件发送给从服务器,从服务器接收接收并载入这个 RBD 文件,将自己的数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态。
d. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器当前所处的状态。
2. 命令传播
在执行完同步操作以后,如果客户端又再次向主服务器发送写命令,如果此时该命令没有传播到从服务器,那么主从服务器的数据库状态必然会不一样,因此,在执行完同步操作以后,还必须得执行命令传播,用来传播主服务器接收到的新的命令请求。
为了让主从服务器再次回到一致状态,主服务器需要对从服务器执行命令传播操作:主服务器会将自己执行的那条写命令,发送给从服务器,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态。
3. 旧版复制存在的缺陷
从服务器对主服务器的复制分为以下两种:
初次复制:从服务器没有复制任何主服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不同。
断线后重复制: 处理命令传播阶段的主从服务器因为网络原因而中断了复制,但从服务器通过自动重连接重新连接上主服务器,并继续复制主服务器 。
当主从服务器断开以后,从服务器通过自动重连连上主服务器,然后从服务器向主服务器发送 SYNC 命令,进行同步操作,但是主服务器此时会将数据库状态写入到 RDB 文件中,如上述红色方框(重复复制了许多键值对),这部分就是旧版复制存在的缺陷。
4. 旧版复制问题的解决方案
为了解决旧版复制功能在处理断线重复复制情况时的低效问题,Redis 从 2.8 以后,使用 PSYNC 命令代替 SYNC 命令来执行复制时的同步操作。
psync 命令具有完整重同步和部分重同步两种模式。
完整重同步:用以解决初次复制的问题。执行操作与 sync 一模一样。
部分重同步:用于处理断线后重复制情况:当从服务器在断线后重新连上主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据更新至主服务器当前所处的状态。
PSYNC 命令的部分重同步解决了旧版复制功能在处理断线后重复复制时出现的低效情况。
主从服务器执行部分重同步的过程:
5. 部分重同步的实现
要实现部分重同步,必须解决以下三个问题:
1. 当前主从服务器各复制了多少数据??
2. 如果主从服务器断线以后,主服务器新接收到的命令请求,该如何处理?
3. 如果在一个集群系统中,如何找到上一次复制的那个主服务器呢?
部分重同步功能由以下三个部分构成:
a. 主服务器的复制偏移量和从服务器的复制偏移量
b. 主服务器的复制积压缓冲区
c. 服务器的运行 ID
typedef struct redisClient {
// 复制状态
int replstate; /* replication state if this is a slave */
// 用于保存主服务器传来的 RDB 文件的文件描述符
int repldbfd; /* replication DB file descriptor */
// 读取主服务器传来的 RDB 文件的偏移量
off_t repldboff; /* replication DB file offset */
// 主服务器传来的 RDB 文件的大小
off_t repldbsize; /* replication DB file size */
sds replpreamble; /* replication DB preamble. */
// 主服务器的复制偏移量
long long reploff; /* replication offset if this is our master */
// 从服务器最后一次发送 REPLCONF ACK 时的偏移量
long long repl_ack_off; /* replication ack offset, if this is a slave */
// 从服务器最后一次发送 REPLCONF ACK 的时间
long long repl_ack_time;/* replication ack time, if this is a slave */
// 主服务器的 master run ID
// 保存在客户端,用于执行部分重同步
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
// 从服务器的监听端口号
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
// 最后被写入的全局复制偏移量
long long woff; /* Last write global replication offset. */
} redisClient;
下面我们对上面三个部分一一解释一下:
复制偏移量
执行复制的双方 — 主从服务器都会维护一个复制偏移量。
主服务器每次向从服务器传播 N 个字节的数据时,就将自己的复制偏移量的值加上 N。
从服务器每次接收到主服务器传播来的 N 个字节的数据时,就将自己的复制偏移量加上 N。
通过对比主从服务器的复制偏移量,程序很容易知道主从服务器是否处于一致状态。
主从状态一致:
主从状态不一致:
假如从服务器 A 在断线后就立即重新连接主服务器,并且成功,那么接下来,从服务器将向主服务器发送 PSYNC 命令,报告从服务器 A 当前的复制偏移量为 10086,那么这时, 主服务器应该对从服务器执行完全重同步还是部分重同步?如果执行部分重同步的话,主服务器又如何补偿从服务器 A 在断线期间丢失的那部分数据呢?
复制积压区
复制积压区是由主服务器维护的一个固定长度的队列,默认大小为 1M。
当主服务器进行命令传播时,它不仅将写命令发送给所有从服务器,还会将写命令入列到复制积压区缓冲区里面。如下图:
因此,主服务器的复制积压区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。
当从服务器重新连上主服务器时,从服务器会通过 PSYNC 命令将自己的复制偏移量 offset 发送给主服务器,主服务器会根据这个复制偏移量来决定对主服务器进行何种复制操作:
如果 offset 偏移量之后的数据,仍然存在于复制积压区里面,那么主服务器将对从服务器执行部分重同步操作。
如果 offset 偏移量之后的数据,不在复制积压区里面,那么主服务器将会对从服务器进行完全重同步操作。
服务器允许 ID
每个 Redis 服务器,不论是主服务器还是从服务器都会有自己的运行 ID。这个 ID 在服务器启动时自动生成,由 40 个随机十六进制字符组成。
当从服务器对主服务器进行初次复制时,主服务器会将自己的运行 ID 传送给从服务器,而从服务器会将这个运行 ID 保存起来。
当从服务器断线并重连上一个主服务器时,从服务器将向当前连接的主服务器发送自己的之前保存的运行 ID:
如果 ID 一致,说明短线后重连的就是之前连接的服务器;
如果 ID 不一致,说明短信后重连的不是之前链接的服务器,那么主服务器将对从服务器进行完整重同步操作。
: