Redis复制

1.概述

Redis使用复制功能在主从服务器之间实现数据的同步。
Redis的复制功能分为两个版本:

  • 2.8版本之前的旧版复制功能
  • 2.8版本之后的新版复制功能

这两个版本的主要区别在与新版的复制功能支持部分重同步,可以有效的提升断线后重连的数据同步效率。

2.旧版复制功能

Redis复制功能都是分为两个步骤:

  • 同步
    将主服务器数据同步至从服务器
  • 命令传播
    数据同步完成后,当主服务器数据状态发生改变时(发生写命令等),将命令传播至从服务器从而使得主从服务器状态一致

2.1 同步

同步过程:

时间 主服务器 从服务器
T0 服务器启动 服务器启动
T1 执行 set k1 v1
T2 执行 set k2 v2
T3 执行 set k3 v3
T4 向主服务器发送SYNC命令
T5 接收到从服务发送的SYNC命令,执行BGSAVE命令,创建包含键k1、k2、k3的RDB文件,并使用缓冲区记录接下来执行的所有写命令
T6 执行 set k4 v4
T7 执行 set k5 v5
T8 BGSAVE命令执行完毕,向从服务器发送RDB文件
T9 接收并载入主服务器发来的RDB文件,获得k1、k2、k3三个键
T10 由缓冲区向从服务器发送命令set k4 v4、set k5 v5
T11 接收并执行主服务器发送来的两个set命令,获得键k4、k5
T2 同步完成,主从服务器数据一致,都包含k1、k2、k3、k4、k5 同步完成,主从服务器数据一致,都包含k1、k2、k3、k4、k5

2.2 命令传播

命令传播是在主从服务器数据完成同步之后,主服务器数据发生变化时,会将相应的命令传播至从服务器使得主从数据完成一致。

2.3 旧版复制功能的缺陷

旧版复制功能在从服务器首次进行数据复制时,没有任何问题,但是当从服务器发生断线进行重连时,有可能从服务器只是缺失了很小的一部分数据,但是进行复制时却要复制整个主服务器全部的数据,从而大大的浪费了资源。
缺陷具体示例如下:

时间 主服务器 从服务器
T0 主从服务器完成同步 主从服务器完成同步
T1 执行 set k1 v1
T2 执行 set k2 v2
。。。 。。。 。。。
T10001 执行并传播 set k10001 v10001 执行主服务器传来的set k10001 v10001
T10002 执行并传播 set k10002 v10002 执行主服务器传来的set k10002 v10002
T10003 主从服务器连接断开 主从服务器连接断开
T10004 执行 set k10003 v10003 断线,尝试重连主服务器
T10005 执行 set k10004 v10004 断线,尝试重连主服务器
T10006 执行 set k10005 v10005 断线,尝试重连主服务器
T10007 主从服务器重新连接 主从服务器重新连接
T10008 向主服务器发送SYNC命令
T10009 接收到从服务发送的SYNC命令,执行BGSAVE命令,创建包含键k1~k10005的RDB文件,并使用缓冲区记录接下来执行的所有写命令
T10010 BGSAVE命令执行完毕,向从服务器发送RDB文件
T10011 接收并载入主服务器发来的RDB文件,获得k1~k10005键
T10012 BGSAVE命令期间没有任何写命令,因此跳过缓冲区发送命令
T10013 同步完成,主从服务器数据一致 同步完成,主从服务器数据一致

如上示例,本来只需要进行set k10003 v10003、set k10004 v10004、set k10005 v10005三个命令的同步,使用旧版复制功能却需要进行k1~k10005整个RDB文件的恢复,带来了相当大的资源浪费。

3 新版复制功能

为了解决旧版复制功能中存在的断线重连导致的资源浪费的问题,Redis从2.8版本开始使用PSYNC代替SYNC执行进行复制时的同步操作。
PSYNC具有完整重同步、部分重同步两种模式:

  • 完整重同步:处理初次复制的情况,与SYNC执行步骤基本一样
  • 部分重同步:用于处理断线后重新复制的情况。当从服务器断线后,如果条件允许,主服务器可以将主从服务器断开期间执行的写命令发送给从服务器,从服务器只要执行这些命令即可完成数据同步。

PSYNC具体示例如下:

时间 主服务器 从服务器
T0 主从服务器完成同步 主从服务器完成同步
T1 执行 set k1 v1
T2 执行 set k2 v2
。。。 。。。 。。。
T10001 执行并传播 set k10001 v10001 执行主服务器传来的set k10001 v10001
T10002 执行并传播 set k10002 v10002 执行主服务器传来的set k10002 v10002
T10003 主从服务器连接断开 主从服务器连接断开
T10004 执行 set k10003 v10003 断线,尝试重连主服务器
T10005 执行 set k10004 v10004 断线,尝试重连主服务器
T10006 执行 set k10005 v10005 断线,尝试重连主服务器
T10007 主从服务器重新连接 主从服务器重新连接
T10008 向主服务器发送PSYNC命令
T10009 向从服务器发送+CONTINUE回复,表示执行部分重同步
T10010 接收+CONTINUE回复,准备执行部分重同步
T10011 向从服务发送set k10003 v10003、set k10004 v10004、set k10005 v10005三个命令
T10012 执行主服务器发送来的三个命令
T10013 同步完成,主从服务器数据一致 同步完成,主从服务器数据一致

3.1 部分重同步

部分重同步由以下三个部分组成:

  • 主服务的复制偏移量 和 从服务器的复制偏移量
  • 主服务器的复制积压缓冲区
  • 服务器的运行ID

3.1.1 复制偏移量

执行复制的主服务器、从服务器会分别维护一个复制偏移量。
这个偏移量记录了执行命令的字节数。主服务器每次向从服务器传播N个字节时就会将自己的复制偏移量+N,从服务在接收到主服务器传送来的N个字节的命令时,就将自己的复制偏移量+N。


Redis复制_第1张图片
复制偏移量变化

3.1.2 复制积压缓冲区

复制积压缓冲区是一个由主服务器维护的固定长度的先进先出对接,默认大小为1M,可调整

固定长度的先进先出队列:出队、入队方式与普通先进先出队列一致,新元素由一边进入,旧元素由另一边弹出,不同的地方在于长度固定,例如长度为3的固定长度的先进先出队列,我们将1,2,3,4,5存入其中时,那么1,2,3会被放入队列中,当4放入时,1被弹出,5入队时,2被弹出,因此最终会存储:3,4,5

主服务在进行命令传播时会如下图所示:


Redis复制_第2张图片
主服务器向复制积压缓冲区和从服务器传播写命令

当从服务器重新连接上主服务器后,从服务器会通过PSYNC命令将自身的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器采用何种操作:

  • 如果offset偏移量之后的数据存在与复制积压缓冲区中,那么主服务器将对从服务器执行部分重同步
  • 如果offset偏移量之后的数据已经不存在于复制积压缓冲区中,那么主服务会对从服务器采用完整重同步

Redis复制积压缓冲区的大小可以设置,一般将其设置为 second * write_size_per_second * 2的大小

3.1.3 服务器运行ID

每个Redis 服务器无论主服务器还是从服务器都有自己的运行ID
运行ID在服务器启动时生成,由40个随机的十六进制字符组成
当从服务器对主服务器进行初次复制时,主服务器会将自身的运行ID回传给从服务器,从服务器会将这个运行ID保存下来。
当从服务器断线重连主服务器时,会将这个运行ID发送个主服务器,主服务器会检查这个运行ID是否为自己的运行ID,如果不是则会直接执行完成重同步,如果是自行运行ID,则根据具体情况来决定是采用部分重同步还是完整重同步。

3.2 PSYNC命令

执行过程


Redis复制_第3张图片
PSYNC命令执行

4 复制的实现过程

1.设置主服务器的地址和端口

SLAVEOF 127.0.0.7 6379

2.建立套接字连接
3.发送PING命令


Redis复制_第4张图片
发送PING命令

4.身份验证
从服务器向主服务器发送一条AUTH命令,将密码带给主服务器。

Redis复制_第5张图片
身份验证

5.发送端口信息
执行命令

REPLCONF listening-port 

向主服务器发送从服务器监听端口
6.同步
7.命令传播

5.心跳检测

发送命令:

REPLCONF ACK 

其中replication_offset是从服务器当前的偏移量
这个命令主要有三个作用:

  • 检测主从服务器的连接状态
  • 辅助实现min-slaves选项
  • 检测命令丢失

5.1 检测主从服务器网络连接状态

执行如下命令:

127.0.0.1:9001> INFO replication
# Replication
role:master
connected_slaves:1
# lag值表示1s前发送过REPLCONF ACK
slave0:ip=127.0.0.1,port=9004,state=online,offset=293566,lag=1
master_replid:1bb39e63541f367d670879f7be07f49855d96aad
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:293580
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:293580

一般情况下lag值应该在0秒或1秒之间跳动,如果超过了1秒那么说明主从服务器之间连接出了问题

5.2 辅助实现min-slaves选项

Redis的min-slaves-to-write 和 min-slaves-max-lag 这两个选项可以防止主服务器在不安全的情况下执行写命令。
例如我们进行如下配置:

min-slaves-to-write 3
min-slaves-max-lag 10

那么在从服务器数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10时,主服务器将拒绝执行写命令。

5.3 检测命令丢失

如果因为网络故障,主服务器传播给从服务器的写命令丢失,那么当从服务器向
主服务器发送REPLCONF ACK 命令时,主服务器会发现从服务器当前的复制偏移量少于自己的复制偏移量,然后主服务就会根据从服务器提交的复制偏移量在复制积压缓冲区中找到从服务器中缺少的数据,并将这些数据重新发送给从服务器。

这里有个问题不是很清楚:
Redis从服务器由主服务器同步命令时,假如从服务器复制偏移量为:10000,主服务器复制偏移量也为:10000,
此时,主服务器接受了新的写入命令:set msg 'aaa',此时假如主服务器复制偏移量变为:10020,在向从服务器传播这条命令时失败,此时从服务器的复制偏移量还是为:10000,之后主服务器接受命令:set msg1 'bbbbbb',此时假如主服务器复制偏移量变为:10050,此时命令传播成功,那么从服务器的复制偏移量变为:10030,而主从服务器之间的心跳检测时,发现两边复制偏移量不一致,主为:10050,从为:10030,此时如果主服务器将复制缓冲区中从10030开始的命令传播给从服务器的话,会存在两个问题:
1、10030~10050是否为一个有效的Redis命令
2、10030开始的命令不是从服务器缺失的命令,破坏了主从数据的一致性
???这个问题需要后续找寻下答案

6 参考资料

《Redis设计与实现》

你可能感兴趣的:(Redis复制)