部分复制是为了解决全量复制开销过大的一种优化措施,当从节点复制主节点数据过程中,如果出现网络中断或者命令丢失等异常情况,此时从节点可以向主节点要求补发丢失的命令数据,如果主节点的复制缓冲区中刚好存在这一部分数据,那就直接发送给从节点以此来保证主从一致性,在了解部分复制之前我们需要先知道三个概念:

runid

runid就是主节点的运行id,是redis启动时随机分配的一个40位的十六进制字符串,运行id用来唯一识别redis节点,从节点保存主节点的runid从而知道自己需要从哪个节点来复制数据。

之所以使用runid而不是使用host+port的方式是因为一旦aof或者rdb文件发生改变并重启了redis服务,那么从节点再基于偏移量(offset)去复制数据是不安全的。

那么已经构建的主从架构,如果要主节点出现故障需要重启怎么办呢?可以使用slaveof no one先将从节点升级为主节点,待真正的主节点重启完成后再使用slaveof重新创建主从,当然这是一种很low的做法,高级的一点做法可以使用哨兵或者集群等高可用方案。

offset

offset是复制偏移量,表示主节点向从节点传递的字节数,主节点每次向从节点传递N个字节数据时,主节点的复制偏移量增加N,从节点从主节点接收N个字节数据时,从节点的复制偏移量增加N。

复制偏移量可以用来判断主从节点的一致性,如果两者复制偏移量相同,那么就是主从一致,如果主节点偏移量大于从节点偏移量,且远远大于,那么此时可能出现了网络延迟或者命令阻塞,主节点的偏移量比从节点偏移量大的部分就存在于复制缓冲区中,当从节点请求部分复制时就从复制缓冲区中获取到对应偏移量的数据传递给从节点。

复制缓冲区

顾名思义,复制缓冲区就是一个缓冲区,它是保存在主节点上一个固定长度、先进先出的队列,默认大小时1M,当主节点存在从节点时,不管是几个从节点,主节点都会将写命令发送给从节点的同时缓存到复制缓冲区中,当缓冲区占满时就会将最先进入缓冲区的数据挤出缓冲区。

当主从节点断开重连时,从节点带着offset请求复制,主节点判断从节点的offset是否存在于缓冲区中,如果存在,那么就进行部分复制,将缓冲区中的数据直接返回给从节点,如果从节点传递的offset已经超过缓冲区中的offset的值,那么就需要开启全量复制。基于此,要根据具体的业务需求调整复制缓冲区的大小,尽可能地使用部分复制。

部分复制实例

我在两台机器上分别安装了一个redis,其中一个作为另外一个的从节点,从节点:
img
主节点:
img
为了验证部分复制,现在将两台机器之间的网络断开(简单粗暴的办法,拔网线),经过一会儿之后查看主从节点的日志,可以看到各自都出现了lost,如下所示:

# 主节点日志
# Connection with slave 10.18.30.178:6379 lost.

# 从节点日志
1:S # MASTER timeout: no data nor PING received...
1:S # Connection with master lost.
1:S * Caching the disconnected master state.
1:S * Connecting to MASTER 10.18.30.34:6379
1:S * MASTER <-> REPLICA sync started
1:S # Error condition on socket for SYNC: No route to host
1:S * Connecting to MASTER 10.18.30.34:6379

在主从节点各自丢失对方的连接时,在主节点上执行写操作(随便写入一些数据),如下所示:
img
经过一段时间以后,重新连通主从节点之间的网络,此时,在从节点的日志中可以看到如下所示的内容:

1:S * Connecting to MASTER 10.18.30.34:6379   # 连接上了主节点
1:S * MASTER <-> REPLICA sync started # 主从节点开始同步
1:S * Non blocking connect for SYNC fired the event.
1:S * Master replied to PING, replication can continue... # 主节点回复了ping,主从可以继续
1:S * Trying a partial resynchronization (request eb32e34c84e7123e7ddb6ae1fab5e348bc58af31:1234). # 开始部分复制,offset是1234
1:S * Successful partial resynchronization with master. # 部分复制成功

从上面的日志可以看出来,从节点连接上主节点之后开始尝试部分复制,并且最后部分复制成功。我们再看一下此时主节点的日志:

* Slave 10.18.30.178:6379 asks for synchronization # 从节点请求同步
* Partial resynchronization request from 10.18.30.178:6379 accepted. Sending 444 bytes of backlog starting from offset 1234. # 接受从节点的部分复制请求,从1234的offset位置发送444bytes的数据

也就是主节点此时判断这次的同步请求符合部分复制,那么就从对应的offset位置发送数据给从节点,如果此时offset不在复制缓冲区的范围内,那么开启的就是全量复制,而不是部分复制了。

另外,如果部分复制完成后aof文件达到了auto-aof-rewrite-min-size以及auto-aof-rewrite-percentage的要求,那么此时就会触发aof的重写。

总结一下重写的过程,就是如下几个步骤:

  • 主从节点网络中断,超过repl-timeout时间后,主节点认为从节点故障,打印lost日志;
  • 主从断开期间,主节点继续响应请求,会将写命令缓存到大小为1M的复制缓冲区;
  • 主从网络恢复后,从节点会再次连接上主节点,打印主从可以继续的日志;
  • 主从恢复后,从节点使用psync请求,带着保存好的主节点运行id以及自身已经复制的偏移量去请求进行复制;
  • 主节点接收到从节点的psync请求后,首先判断runID是否一直,不一致的话表示之前复制的不是当前主节点,需要重新开始全量复制,一致的话再查找请求offset是否存在于复制缓冲区中,不存在的话同样要开启全量复制;
  • runID和offset都符合部分复制的要求后,主节点会把复制缓冲区中相应的数据发送给从节点,保证主从进入正常复制;