主从复制原理剖析
1 )配置
- 通过下面的从节点的配置项可以开启主从之间的复制功能
slaveof 192.16.10.101 6379
- 这里的复制包含全量复制和增量复制
2 )主节点的主从配置信息解析
- 查看主从之间的信息,在主节点上 $
info replication
打印出来的细节# Replication role: master connected_slaves: 2 slave0:ip=192.168.10.102,port=6379,state=online,offset=2184,lag=1 slave1:ip=192.168.10.103,port=6379,state=online,offset=2184,lag=1 master_replid:0e233bbb3b38aada4764b58c2dc9e74ddfc85ccc master_replid2:0000000000000000000000000000000000000000 master_repl_offset:2184 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen: 2184
- 从节点 offset 是指读取命令的偏移量
- 相当于: 从节点现在已复制的偏移量的长度
- 主节点实际上会把所有的命令转成字节,然后写到一个队列里边
- 然后它写入了多少会记录下最终的值,也就是对应下面的
master_repl_offset
- 两者相等,表示主从数据一致
- lag 延迟时间,lag=1 延迟时间是1s,主从同步数据会有延迟
- master_repl_offset 主节点已写入的命令偏移量
- master_replid 和 master_replid2
- master_replid 表示主节点的一个 replicationID, 它是40个16进制的字符串,随机生成的
- master_replid2
- 表示主节点的状态发生改变之后
- 新的主节点ID会存放在 master_replid 中
- 旧的主节点ID会存放在 master_replid2 中
- 下面的5项都是在 Redis 2.8 之后, 出现的特性
- 这里既然涉及到了主从复制,肯定会有一个全量复制,增量复制这样一个概念在里边
- 全量复制是指:从节点把主节点的数据据全部都拷贝过去
- 这种一般发生在环境初始化和从节点扩展以及主节点故障在从节点选举新的主节点场景中
- 在主节点故障重新选举的过程中,run_id 会出现变化, 在 $
info server
命令中就有这个 run_id - 从节点根据这个 run_id 来判断是全量复制还是增量复制
- 在2.8版本之后出现 second_repl_offset 这个配置,还有一个缓冲区的概念
- 关于 second_repl_offset 这个配置
- 2.8 之后,全量增量复制多了一个命令,叫 psync, 之前是建立一个 sync 的操作
- 比如说, 现在主节点故障重启之后,它发现 run_id 变了,二话不说,走一个全量复制
- 再有,比如说,主节点故障了,重新选取主节点,会把上一次主节点已写入队列的偏移量 master_repl_offset 记录下来,寄存到这个 second_repl_offset
- 比如现在 master_repl_offset 已经是 2590 了,在存的时候 second_repl_offset 一般做一个 +1 的操作,也就是变成了 2591,这样,从节点再重新跟主节点建立连接的时候,会拿到这个 second_repl_offset
- 根据上面的情况 second_repl_offset 比 master_repl_offset 多了一个字节,就没有必要做全量复制了
- 只需要继续保持现状,跟它持续连接,每十秒 ping 一下,监听着它就行了, 继续做增量操作
- 所以,second_repl_offset 的作用就是为了避免每一次主从的角色改变/故障重启等场景带来可能的全量复制操作而浪费性能,这样增量操作就能解决问题
- 关于缓冲区的配置
- repl_backlog_active: 1 表示缓冲区开启
- repl_backlog_size:1048576 表示缓冲区的大小, 这里是 1M的大小
- repl_backlog_first_byte_offset:1 表示从1的位置开始写
- repl_backlog_histlen: 2184 表示当前缓冲区的长度
- 从节点 offset 是指读取命令的偏移量
3 )从节点的主从配置信息解析
- 在从节点上 $
info replication
打印出来的细节# Replication role:slave master_host:192.168.10.101 master_port:6379 master_link_status:up master_last_io_seconds_ago:5 master_sync_in_progress:0 slave_repl_offset:2842 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:0e233bbb3b38aada4764b58c2dc9e74ddfc85ccc master_replid2:0000000000000000000000000000000000000000 master_repl_offset:2842 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:2842
- 上面 master_* 是主节点的一系列信息,主要看下
- master_last_io_seconds_ago:5 表示从库和主库最后一次同步数据的时间在五秒之前
- master_sync_in_progress:0 表示和主库的同步状态,0是未同步,1是正在同步
- master_repl_offset:2842 表示主节点写入的偏移量
- 上面 slave_* 是从节点的一些列配置
- slave_repl_offset 表示从节点复制的偏移量,和上面主节点写入偏移量一致,说明同步了
- slave_priority:100 表示从节点在选举时成为主节点的一个几率
- 比如主节点宕机,剩下的2个从节点开始参与选举
- 谁能竞选成功,就看上面这个数值的大小,越大则优先级越高,竞选越容易成功
- 如果这个值为 0,则这个从节点永远不会变成主节点
- slave_read_only:1 从节点只读模式,1表示开启,0表示关闭
- connected_slaves:0 表示连接到从节点的信息
- 作为从节点,也是可以去连其他的从节点的
- 其他选项不再赘述
4 ) Master复制日志查看
-
master/slave 进行主从进行复制的时候,在日志中也是可以体现出来
-
下面通过查看日志的方式,把主从的一个复制流程走一遍
-
在主节点查看日志:$
tail -f -n 800 /usr/local/redis/log/redis.log
, 我们从下面位置来看 -
Ready to accept connections
表示随时等待其他节点的连接 -
Replica 192.168.10.102:6379 asks for synchronization
表示 102 从节点开始过来复制了- 这里可见,它发起了 sync 的请求
-
Full resync requested by replica 192.168.10.102:6379
表示 102 的复制请求是全量的复制请求 -
Replication backlog created, my new replication IDs are '0e233bbb3b38aada4764b58c2dc9e74ddfc85ccc'and '0000000000000000000000000000000000000000'
- 刚把环境起起来,这里主节点开始创建缓冲区并生成新的 replication IDs
- 这里有2个ID,就是上文说的 master_replid 和 master_replid2
-
下面是RDB的操作
1468:M 10 Nov 2020 14:21:28.324 * Starting BGSAVE for SYNC with target: disk 1468:M 10 Nov 2020 14:21:28.324* Background saving started by pid 1474 1474:C 10 Nov 2020 14:21:28.326 * DB saved on disk 1474:C 10 Nov 2020 14:21:28.326 * RDB: 4 MB of memory used by copy-on-write 1468:M 10 Nov 2020 14:21:28.332 * Background saving terminated with success
- 首先,通过 BGSAVE 把数据写入磁盘,可以看到它是后台的写入进程
- 之后,通过 copy-on-write 把内存的 4M 数据写入磁盘
- 最后,保存结束,终止
-
Synchronization with replica 192.168.10.102:6379 succeeded
- 这里,提示 102 机器的复制成功了
-
下面是 103 重复RDB的复制操作
1468:M 10 Nov 2020 14:21:30.858 * Replica 192.168.10.103:6379 asks for synchronization 1468:M 10 Nov 2020 14:21:30.858 * Full resync requested by replica 192.168.10.103:6379 1468: M 10 Nov 2020 14:21:30.858 * Starting BGSAVE for SYNC with target: disk 1468:M 10 Nov 2020 14:21:30.859 * Background saving started by pid 1475 1475:C 10 Nov 2020 14:21:30.861 * DB saved on disk 1475:C 10 Nov 2020 14:21:30.861 * RDB: 4 MB of memory used by copy-on-write 1468:M 10 Nov 2020 14:21:30.952 * Background saving terminated with success 1468:M 10 Nov 2020 14:21:30.952 * Synchronization with replica 192.168.10.103:6379 succeeded
- 不再赘述
5 )画图来看复制流程(全量复制)
- 环境搭建好之后,slave节点就发起了一个 sync 的请求,这个请求是一个 全量复制
- 其实增量无非就是在环境构建完成之后,每次你写入一个,我就复制一个写入一个复制一个
- sync 的命令到主节点,主节点这边执行 BGSAVE 执行BGsave之后,生成RDB快照
- 然后,主节点会把RDB的快照发送给 slave 节点
- slave 节点拿到RDB快照之后,会把它节点上旧的数据全部删掉,加载RDB的文件
- 在上述过程中,我们看下
- master主节点,在执行 BGSAVE 的时候,它是一个非阻塞的
- 就是说, 在生成RDB执行BGSAVE 期间, 仍然是可以对外提供服务的
- 也就是说它仍然是可以读写的,如果说这时候有一些命令,它就会把命令写到缓冲区里边去
- 就是我们的backlog里边,它为什么要这么做呢?
- 一方面,为了提高性能,无阻塞可提供反馈
- 另一方面,写到缓冲区里边,master 已经生成RDB快照发给 slave节点了
- 后续的命令从节点就拿不到了,所以,给它放到这个缓冲区里边
- 等 slave 这边RDB加载完之后,它再从缓冲区里边去拿走后续的那些命令
- 相当于就是把整个的数据全部复制过去了
- 所以,我们的 slave 除了加载 RDB 之外,它还会去缓冲区里边继续接受这些命令,最终完成一个init
- 所以,这里面包含:RDB的加载完成和缓冲区命令的复制都完成了
6 )再来看下增量复制
- 增量复制更多的是 Slave 初始化完成后,环境已经稳定了,这个时候,就会做增量的复制
- 增量复制就是主服务器那边发生了写操作,它就会同步到从服务器的一个过程
- 复制的过程就是
- 主服务器执行一个命令,从服务器就会发送一个相同的写命令
- 从服务器接收到之后就开始执行
- 我们可以演示一下
- 在从服务器中执行 $
sync
先建立连接 - 在主服务器中,进行写操作 $
set age 18
- 在从服务器终端中输入
"PING" "SELECT","0" "set","age","18" "PING"
- 可以看到,每10s就会PING一下,得到新的同步的命令这样保持心跳
- 在从服务器中执行 $
- 在2.8之前都是全量复制,之后便可以增量复制了
7 )主从复制的异步性
- 主从复制这个过程,主节点是非阻塞的,在复制的这个流程中
- 它是开启的一个后台子守护进程去做这件事情的,比如 BGSAVE 和 生成 RDB快照发送等
- 当前服务器仍然是可以对外提供读写这样的一些请求的,这个就是非阻塞,体现异步性
- 从节点也是一样的,比如说正在复制主节点的数据,这个SYNC的操作也是非阻塞的
- 复制的过程中, 它可能就会有一点问题, 比如,正在复制时,一个查询过来
- 那我可能查到的就是比如说一些老数据,这里边就会有脏读,数据不一致等等的问题
- 这块在故障解决中有一些方案来处理
8 )过期 key 的处理
- 实际上从节点是不会让 key 过期的,从节点它没有 key 过期的概念
- 它会等待接收主节点delete的命令,可以看下面的演示
- 从节点:$
sync
先监控下 - 主节点:$
set age 18 ex 10
- 查看从节点输出
"PING" "set","age","18","ex","10" "PING" "DEL","age" "PING"
- 可以看到,它在等主节点发DEL 命令
- 从节点:$
- 也就是,当Master让key到期时,会合成一个 DEL 命令传输到所有 Slave
9 )加速复制
- 上面看日志可知,每一次的复制都会生成RDB,然后把RDB的快照文件发给从节点
- 如果说你的磁盘性能比较差,每一次的主从复制都要写入磁盘,如后再生成RDB文件发送给从节点,性能就会被降低,因为磁盘性能差
- 可以不写入磁盘,直接生成RDB文件发给从节点就可以了,在 Redis@2.8.18这个版本之后,加入了这个功能,可以设置无需写入磁盘,直接把这个RDB的快照文件发给从节点
- 修改配置:
repl-diskless-sync yes
默认值是 no 不开启- 不开启的情况下,BGSAVE 先写磁盘,然后把生成RDB快照再发送
- 设置为 yes 开启之后,就直接把RDB快照发给从节点
- 不会写磁盘操作,这样就加速了复制