前言
一门技术光停留在会用的层面远远不够,必须要深入的掌握其原理,然后加以理解和总结,才能叫“会”。
本文是在深入了解Redis主从同步原理时,记录的学习笔记。
目的
Redis提供了主从库模式,实现了Redis的高可用,保证多个实例的数据一致性。主从库之间采用的是读写分离的方式
读写分离的架构
读操作:主从都可以接受;
写操作:首先到主库执行,然后主操作将写操作同步给从库。
主从同步的原理
主从库如何实现第一次同步?
这里的方法是是replicaof
, Redis 5.0以前是 slaveof
. 命令如下:
replicaof 主库IP 端口
主要的操作步骤如下:
-
主从库建立连接、协商同步,主要是为了全量同步做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步;主库确认回复后,主从库间就可以开始同步了。
具体来说,从库给主库发送 psync命令,表示要进行数据同步;主库根据这个命令的参数来复制,主要包含的参数有:主库的runID和复制进度offset两个参数。
参数说明:
runID 每个redis实例启动时都会生成一个随机ID,用来唯一标记这个实例;当从库和主库第一次复制是,因为不知道主库的runID,所以传递?
offset此时设置为-1,表示第一次复制
主库收到
psync
命令后,会用fullresync
响应命令带上2个参数:主库的runid和主库目前的复制进度offset,返回给从库;从库收到响应后,会记录下这2个参数。这里需要注意的是
fullresync
表示的是第一次复制采用的全量复制。 -
主库将所有数据同步给从库,从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照生成的RDB文件。
具体而言,主库执行
bgsave
命令,生成RDB文件,将文件发送给从库。从库接受到RDB文件后,会先清空当前数据库,然后加载RDB文件。这里为什么要清空?
是因为从库在通过
replicaof
同步主库之前,可能会存在数据,为了避免影响,先清空较好。那么这个时候我们会产生疑问?在进行第一次全量同步时,同时写入到主库的数据不就没有同步到从库了吗?
为了保证数据的一致性,主库在内存中会有专门的
replication buffer
,记录rdb文件生成后的所有写操作。 -
这就来到了第三阶段,主库会将第二阶段执行过程中,接受到的新命令,再发给从库。
具体的操作就是,当主库完成RDB文件发送后,就会把此时的
replication buffer
中的修改操作发给从库,从库再重新执行这些操作。从而实现主从一致。
主从级联模式分担全量复制时的主库压力
那么这个时候,也会遇到一个问题,我们可以发现在一次全量复制过程中,对于主库来说,需要完成两个耗时的工作;生成RDB文件和传输RDB文件。如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于fork子进程生成RDB文件,进行数据同步。fork这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求变慢。此外,传输RDB文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。
那么面临这样的问题,有什么更好的解决方法呢?
“主-从-从”模式的产生,简单来说,就是当我们在部署Redis集群时,可以手动选一个从库(内部配置资源较高的),用于其他从库连接,建立主从关系。后续也会建立基于长连接的命令传播,可以避免频繁建立连接的开销。
主从库网络断了怎么办?
采取增量复制的方式,Redis 存在 repl_backlog_buffer
这个缓冲区。
当主从库断连后,主库会把断连期间收到的写操作命令写入relication_buffer
,同时也会将这些操作命令写入repl_backlog_buffer
这个缓冲区。
repl_backlog_buffer
是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置,
master_repl_offset
是对应的偏移量。
主从库的连接回复后,从库首先会给主库发送psync命令,并把自己当前的slave_repl_offset发给主库,主库会判断自己的
master_repl_offset
; 在网路断联阶段,主库可能会收到新的写操作命令,所以一般来说master_repl_offset
会大于slave_repl_offset
命令,
所以主库只需要将2个之间的操作同步给从库就可以。
增量复制的整体流程如下:
这里需要注意的地方是,repl_backlog_buffer
是一个环形缓冲区,所以在缓冲区写满之后,主库会继续写入,就会覆盖之前的操作记录。
如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的覆盖了,这样会产生数据不一致。
并且如果slave_repl_offset位置上的数据被覆盖掉了,从库和主库间将会进行全量复制。
这个问题我们可以如何避免呢?
关键参数 ` repl_backlog_size` ,会影响到缓冲区空间大小。
缓冲空间大小 = 主库的写入速度 * 操作大小 - 主从库网路传输命令的速度*操作大小。
通常情况下,考虑到一些突发的并发情况,我们会冗余,所以repl_backlog_size
= 缓冲空间大小 * 2.
举个例子,如果主库每秒写入 2000 个操作,每个操作的大小为 2KB,网络每秒能传输 1000 个操作,那么,有 1000 个操作需要缓冲起来,这就至少需要 2MB 的缓冲空间。否则,新写的命令就会覆盖掉旧操作了。为了应对可能的突发压力,我们最终把 repl_backlog_size
设为 4MB。
总结
关于这块理论知识比较多,需要反复的阅读查看资料。理解之后,就会觉得也没那么复杂了。
最大的感触就是,知道的越多,不知道的越多。
参考资料
极客时间《Redis核心技术与实战》