7

阿肝正传之Redis主从同步

 2 years ago
source link: https://tsmliyun.github.io/redis/%E9%98%BF%E8%82%9D%E6%AD%A3%E4%BC%A0%E4%B9%8BRedis%E4%B8%BB%E4%BB%8E%E5%90%8C%E6%AD%A5/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

本文是在深入了解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核心技术与实战》


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK