本文主要介绍Redis集群中主从服务器复制功能的实现。
在Redis中,用户可以通过执行SLAVEOF
命令或设置slaveof选项,让一个服务器去复制另一个服务器,称被复制的服务器为主服务器(master),而对主服务器进行复制的服务器称为从服务器(slave)。
例如:
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
OK
则服务器127.0.0.1:12345就成为了127.0.0.1 6379的slave,127.0.0.1 6379成为了127.0.0.1:12345的master。
进行复制中的主从服务器双方的数据库保存同样的数据,概念上称之为数据库的一致性。
I、旧版复制功能的实现
Redis的复制功能分为同步(sync) 和命令传播(command propagate)两个操作:
· 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。
· 命令传播操作则用于主服务器的数据被修改后,让主从服务器的数据库状态重新回到一致。
1.1 同步
从服务器对主服务器的同步操作需要通过向主服务器发送SYNC
命令来完成,以下是SYNC
命令的执行步骤:
1、 从服务器向主服务器发送SYNC
命令。
2、收到命令之后的主服务器执行BDSAVE
命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
3、当主服务器BGSAVE
命令执行完毕时,主服务器会将生成的RDB文件发送给从服务器,从服务器load这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE
命令时数据库状态。
4、主服务器将记录在缓冲区中的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器当前所处状态。
下图展示了这一过程:
下图说明了一个同步实例:
1.2 命令传播
主服务器每次执行写名列会同时将命令发送给从服务器执行,从而实现命令传播,达到主从服务器的一致性。
II、旧版复制功能的缺点
从服务器在除了在初次复制主服务器之外,还会在断线后重复制是也进行赋值操作。而刚刚介绍的旧版复制功能对于在断线后重复制情况来说,效率很低。
这是由于需要重写执行SYNC
操作引起的(一旦执行就会发生RDB的复制等操作)。
如下图所示:
而要解决这个问题的关键就在于上图中红圈位置的复制操作,我们应该尽量不再重新使用SYNC
命令,而采用其他方式。
III、新版复制功能的实现
在Redis2.8版本之后,采用了PSYNC
命令的代替了SYNC
同步操作。
PSYNC
命令具有完整重同步和部分重同步两种模式:
· 完整重同步与旧版本的SYNC
命令一样。
· 部分重同步专门用于处理断线后重复制的情况: 当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据块更新至最新状态。
下图展示了相同情况下使用部分重同步的实例:
IV、部分重同步的实现
部分重同步功能由以下三个部分构成:
· 主服务器的复制偏移量和从服务器的复制偏移量;
· 主服务器的复制积压缓冲区;
· 服务器的运行ID。
4.1 复制偏移量
执行复制的双方都要维护一个复制偏移量:
· 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量+N;
· 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量+N;
复制偏移量具有两个作用:
· 通过对比主从服务器的复制偏移量,程序可以很容易的知道主从服务器的数据是否处于一致状态;
· 通过对比主从服务器的复制偏移量,可以知道从服务器丢失了哪些数据,从而执行部分重同步。
4.2 复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度的先进先出队列。
当服务器执行命令传播时,不仅会将写命令传播给从服务器,还会将写命令写入复制积压缓冲区中:
当从服务器重新连上主服务器时,从服务器会通过PSYNC
命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:
· 如果offset偏移量之后的数据仍然存在于复制积压缓冲区中,则主服务器将对从服务器执行部分重同步;
· 如果offset偏移量之后的数据已经不在复制积压缓冲区中,主服务器会对从服务器执行完整的重同步操作;
4.3 服务器运行ID
每个Redis服务器,都会有自己的运行ID,实现部分重同步需要复制偏移量,复制积压缓冲区,运行ID一起完成。
当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID发送给从服务器,从服务器将主服务器的运行ID保存起来。
当从服务器断线并重写连接上一个主服务器之后(这个主服务器可能已经不是之前的主服务器,可以参见Redis中的sentinel),从服务器向当前连接的主服务器发送自己之前保存的运行ID:
· 如果从服务器保存的运行ID和当前主服务器的运行ID相同,则说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以尝试执行部分重同步操作;
· 如果主服务器发现与自己的运行ID不同,则只能执行完整重同步操作;
V、PSYNC命令的实现
PSYNC
命令的调用方法有两种:
1、 如果从服务器以前没有复制过任何主服务器,或者之前执行过SLAVEOF no one
命令,则从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1
命令,主动请求主服务器进行完整重同步。
2、 如果从服务器已经复制过某个主服务器,则从服务器在开始一次新的复制时会向主服务器发送PSYNC <runid> <offset>
命令,runid为上一次保存的主服务器运行ID,offset为从服务器维护的复制偏移量。
主服务器根据接收到的PSYNC
命令,会执行下面的三种回复:
1、 主服务器返回+FULLRESYNC <runid> <offset>
回复,表示主服务器与从服务器执行完整重同步,runid为主服务器自己的运行ID,offset为主服务器自身的复制偏移量,从服务器将用这个值初始化自己的偏移量。
2、 主服务器回复+COUTINUE
,表示主服务器将与从服务器执行部分重同步,根据接收到的offset,已经主服务器自己的复制积压缓冲区来完成。
3、 主服务器回复-ERR
,表示主服务器版本低于Redis2.8,无法识别PSYNC
命令,之后从服务器将发送SYNC
,执行低版本的完整重同步。
下图完整描述了这一过程:
VI、复制的实现
6.1 步骤1:设置主服务器的地址和端口
客户端向从服务器发送:
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
OK
从服务器首先要做的是将客户端给定的主服务器IP地址127.0.0.1以及端口6379保存到服务器状态的masterhost和masterport属性中:
struct redisServer {
//主服务器地址
char *masterhost;
//端口
int masterport;
};
6.2 步骤2: 建立套接字连接
从服务器根据命令的IP地址和端口,创建向主服务器的套接字连接:
创建套接字成功,则从服务器会专门为这个套接字关联一个用于复制工作的文件事件处理器,负责执行后续的复制工作。
主服务器在accept从服务器的套接字连接之后,将为该套接字创建相应的客户端状态,并将从服务器看作是一个连接到主服务器的客户端来对待,这时从服务器将同时具有服务器和客户端两个身份。
因为复制工作接下来的几个步骤都会以从服务器向主服务器发送命令请求的形式进行,所以理解从服务器是主服务器的客户端相当重要。
6.3 步骤3: 发送PING命令
从服务器成为主服务器的客户端之后,向主服务器发送PING
命令:
这个PING
命令有两个作用:
1、检查套接字的读写状态是否正常;
2、检查主服务器是否能正常处理请求。
而从服务器会根据主服务器的相应回复状态判断下一步的动作:
1、如果主服务器向从服务器回复一个命令,但从服务器在规定时间内无法读取出命令回复的内容,则表示主从服务器之间的网络连接状态不佳。
从服务器会断开并重新创建连向主服务器的套接字;
2、如果主服务器返回了一个错误,表示主服务器暂时无法处理从服务器的命令请求,从服务器同样断开并重新创建连向主服务器的套接字;
3、从服务器读取到了PONG
命令,表示主从服务器网络连接正常,继续执行下面的任务。
6.4 步骤4: 身份验证
身份验证步骤可以通过下图来说明:
6.5 步骤5: 发送端口信息
在执行完身份验证步骤之后,从服务器会向主服务器发送从服务器的监听端口号(用于后续的命令传播等操作),主服务器将端口号记录在从服务器所对应的客户端状态中:
typedef struct redisClient {
//从服务器的监听端口号
int slave_listening_port;
} redisClient;
6.6 步骤6: 同步
从服务器向主服务器发送PSYNC
命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前状态。
在同步操作执行之前,只有从服务器是主服务器的客户端,但在执行同步操作之后,主服务器也会成为从服务器的客户端:
· 如果要执行完整重同步操作,主服务器需要成为从服务器的客户端,才能将保存在缓冲区中的写命令发送给从服务器执行;
· 如果执行的部分重同步操作,主服务器需要成为从服务器客户端,才能将复制积压缓冲区的内容发送给从服务器执行。
因此,在同步操作执行之后,主从服务器都是双方的客户端,它们可以互相向对方发送命令请求,或者互相向对方返回命令回复:
6.7 步骤7: 命令传播
主服务器将自己的写命令发送给从服务器执行,进入命令传播阶段,这一操作的基础也是主服务器时从服务器的客户端。
VII、心跳检测
在命令传播阶段,从服务器会以每秒一次的频率向主服务器发送心跳检测命令:
REPLCONF ACK <replication_offset>
replication_offset是从服务器当前的复制偏移量。
REPLCONF ACK
命令对主从服务器有三个作用:
· 检测网络连接状态;
· 辅助实现min-slaves选项(min-slaves表示最小从服务器数量,可以防止主服务器在不安全的状态执行写命令,不安全的状态指从服务器数量少于某个数值);
· 检测命令丢失(根据复制偏移量的对比);
【参考】
[1] 《Redis设计与实现》
欢迎转载,转载请注明出处wenmingxing Redis之主从服务器的复制