阿肝正传之Redis主从同步


前言

一门技术光停留在会用的层面远远不够,必须要深入的掌握其原理,然后加以理解和总结,才能叫“会”。

本文是在深入了解Redis主从同步原理时,记录的学习笔记。

目的

Redis提供了主从库模式,实现了Redis的高可用,保证多个实例的数据一致性。主从库之间采用的是读写分离的方式

读写分离的架构

Redis读写分离架构

读操作:主从都可以接受;

写操作:首先到主库执行,然后主操作将写操作同步给从库。

主从同步的原理

Redis主从同步的原理

主从库如何实现第一次同步?

这里的方法是是replicaof, Redis 5.0以前是 slaveof. 命令如下:

replicaof 主库IP 端口

主要的操作步骤如下:

  1. 主从库建立连接、协商同步,主要是为了全量同步做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步;主库确认回复后,主从库间就可以开始同步了。

    具体来说,从库给主库发送 psync命令,表示要进行数据同步;主库根据这个命令的参数来复制,主要包含的参数有:主库的runID和复制进度offset两个参数。

    参数说明:

    runID 每个redis实例启动时都会生成一个随机ID,用来唯一标记这个实例;当从库和主库第一次复制是,因为不知道主库的runID,所以传递?

    offset此时设置为-1,表示第一次复制

    主库收到psync命令后,会用fullresync响应命令带上2个参数:主库的runid和主库目前的复制进度offset,返回给从库;

    从库收到响应后,会记录下这2个参数。这里需要注意的是 fullresync表示的是第一次复制采用的全量复制

  2. 主库将所有数据同步给从库,从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照生成的RDB文件。

    具体而言,主库执行bgsave命令,生成RDB文件,将文件发送给从库。从库接受到RDB文件后,会先清空当前数据库,然后加载RDB文件。

    这里为什么要清空?

    是因为从库在通过replicaof同步主库之前,可能会存在数据,为了避免影响,先清空较好。

    那么这个时候我们会产生疑问?在进行第一次全量同步时,同时写入到主库的数据不就没有同步到从库了吗?

    为了保证数据的一致性,主库在内存中会有专门的replication buffer,记录rdb文件生成后的所有写操作。

  3. 这就来到了第三阶段,主库会将第二阶段执行过程中,接受到的新命令,再发给从库。

    具体的操作就是,当主库完成RDB文件发送后,就会把此时的replication buffer中的修改操作发给从库,从库再重新执行这些操作。

    从而实现主从一致。

主从级联模式分担全量复制时的主库压力

那么这个时候,也会遇到一个问题,我们可以发现在一次全量复制过程中,对于主库来说,需要完成两个耗时的工作;生成RDB文件和传输RDB文件。如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于fork子进程生成RDB文件,进行数据同步。fork这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求变慢。此外,传输RDB文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。

那么面临这样的问题,有什么更好的解决方法呢?

“主-从-从”模式的产生,简单来说,就是当我们在部署Redis集群时,可以手动选一个从库(内部配置资源较高的),用于其他从库连接,建立主从关系。后续也会建立基于长连接的命令传播,可以避免频繁建立连接的开销。

Redis主从从模式

主从库网络断了怎么办?

采取增量复制的方式,Redis 存在 repl_backlog_buffer 这个缓冲区。

当主从库断连后,主库会把断连期间收到的写操作命令写入relication_buffer,同时也会将这些操作命令写入repl_backlog_buffer这个缓冲区。

repl_backlog_buffer是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置,

master_repl_offset 是对应的偏移量。

Redis主从复制缓冲区.jpg

主从库的连接回复后,从库首先会给主库发送psync命令,并把自己当前的slave_repl_offset发给主库,主库会判断自己的

master_repl_offset; 在网路断联阶段,主库可能会收到新的写操作命令,所以一般来说master_repl_offset会大于slave_repl_offset命令,

所以主库只需要将2个之间的操作同步给从库就可以。

增量复制的整体流程如下:

Redis增量复制的流程图

这里需要注意的地方是,repl_backlog_buffer是一个环形缓冲区,所以在缓冲区写满之后,主库会继续写入,就会覆盖之前的操作记录。

如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的覆盖了,这样会产生数据不一致。

并且如果slave_repl_offset位置上的数据被覆盖掉了,从库和主库间将会进行全量复制。

这个问题我们可以如何避免呢?

关键参数 ` repl_backlog_size` ,会影响到缓冲区空间大小。

缓冲空间大小 = 主库的写入速度 * 操作大小 - 主从库网路传输命令的速度*操作大小。

通常情况下,考虑到一些突发的并发情况,我们会冗余,所以repl_backlog_size = 缓冲空间大小 * 2.

举个例子,如果主库每秒写入 2000 个操作,每个操作的大小为 2KB,网络每秒能传输 1000 个操作,那么,有 1000 个操作需要缓冲起来,这就至少需要 2MB 的缓冲空间。否则,新写的命令就会覆盖掉旧操作了。为了应对可能的突发压力,我们最终把 repl_backlog_size 设为 4MB。

总结

关于这块理论知识比较多,需要反复的阅读查看资料。理解之后,就会觉得也没那么复杂了。

最大的感触就是,知道的越多,不知道的越多。

参考资料

极客时间《Redis核心技术与实战》