06 | 数据同步:主从库如何实现数据一致?


Redis核心技术与实战

基础篇

06 | 数据同步:主从库如何实现数据一致?

Redis 具有高可靠性,有两层含义:一是数据尽量少丢失,二是服务尽量少中断。AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。

Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

  • 读操作:主库、从库都可以接收;
  • 写操作:首先到主库执行,然后,主库将写操作同步给从库。

在这里插入图片描述

疑问:为什么要采用读写分离的方式呢?

如果客户端对同一个数据(例如 k1)前后修改了三次,每一次的修改请求都发送到不同的实例上,在不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了(分别是 v1、v2 和 v3)。
如果非要保持这个数据在三个实例上一致,就要涉及到加锁、实例间协商是否完成修改等一系列操作,但这会带来巨额的开销。

主从库模式一旦采用了读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。

主从库间如何进行第一次同步?

当启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。

例如,现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),在实例 2 上执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:

replicaof  172.16.19.3  6379

主从库第一次同步的流程

在这里插入图片描述

第一阶段,是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。 在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。
从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。

psync 命令包含了主库的 runID 和复制进度 offset 两个参数。

  • runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为 “?”。
  • offset,此时设为 -1,表示第一次复制。

如果主库重启,runID 就会发生变化,从库会根据 runID 区分,runID 不同就做全量复制。

主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。

FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。

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

主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。 这是因为从库在通过 replicaof 命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。

在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis 的服务会被中断。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。

第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。

当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。

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

一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。

如果从库数量很多,而且都要和主库进行全量复制的话,就会导致主库忙于 fork 子进程生成 RDB 文件,进行数据全量同步。fork 这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输 RDB 文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。

通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。

手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。

replicaof  所选从库的IP 6379

在这里插入图片描述

一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。

主从库间网络断了怎么办?

在 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。

从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。增量复制会把主从库网络断连期间主库收到的命令同步给从库。

  • 在主从库断连前,主库的所有写命令除了记录在 replication buffer 之外,也会在这个 repl_backlog_buffer 中记录一份,缓存起来。
  • 当主从库断连后,主库会把断连期间收到的写操作命令,写入 repl_backlog_buffer 这个缓冲区,而 replication buffer 没了。
  • 在 repl_backlog_buffer 中找主从差异的数据后,将这部分差异数据同步到 replication buffer 后,最后 replication buffer 将这部分数据发送给从库(由定时任务刷到 slave socket)。
  • 每个从库都有一个对应的 replication buffer,而 repl_backlog_buffer 是所有从库共享。

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

随着主库不断接收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置。通常用偏移量来衡量这个偏移距离的大小,对主库来说,对应的偏移量就是 master_repl_offset

主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距。

在这里插入图片描述

因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。可以调整 repl_backlog_size 这个参数来避免这一情况(这个参数和所需的缓冲空间大小有关)。

缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违の欢喜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值