Redis源码剖析——主从复制(5)—共享复制缓冲区的方案

相关文章

Redis源码剖析——主从复制(1)
Redis源码剖析——主从复制(2)
Redis源码剖析——主从复制(3)
Redis源码剖析——主从复制(4)

目录

共享复制缓冲区的方案

一、Redis 复制缓存区相关问题分析

问题1.多从库时主库内存占用过多

问题2.OutputBuffer 拷贝和释放的堵塞问题

问题3.ReplicationBacklog 的限制

二、共享复制缓存区的设计与实现

​编辑

三、解决问题

1.多从库消耗内存过多的问题

2.OutputBuffer 拷贝问题

3.ReplicationBacklog 的限制


共享复制缓冲区的方案

本文将主要分析 Redis 主从复制中的内存消耗过多和堵塞问题,以及 Redis 7.0 的共享复制缓冲区方案是如何解决这些问题的。


一、Redis 复制缓存区相关问题分析

问题1.多从库时主库内存占用过多

对于 Redis 主库,当用户的写请求到达时,主库会将变更命令分别写入所有从库的缓存区(OutputBuffer),以及复制积压区 (ReplicationBacklog)。需要指出的是,全量同步时依然会执行该逻辑,所以在全量同步阶段经常会触发 client-output-buffer-limit,主库断开与从库的连接,导致主从同步失败,甚至出现循环持续失败的情况。

该实现一个明显的问题是内存占用过多,所有从库的连接在主库上是独立的,也就是说每个从库 OutputBuffer 占用的内存空间也是独立的,那么主从复制消耗的内存就是所有从库缓冲区内存大小之和。如果我们设定从库的 client-output-buffer-limit 为 1GB,如果有三个从库,则在主库上可能会消耗 3GB 的内存用于主从复制。另外,真实环境中从库的数量不是确定的,这也导致 Redis 实例的内存消耗不可控。

问题2.OutputBuffer 拷贝和释放的堵塞问题

Redis 为了提升多从库全量复制的效率和减少 fork 产生 RDB 的次数,会尽可能的让多个从库共用一个 RDB,当已经有一个从库触发 RDB BGSAVE 时,后续需要全量同步的从库会共享这次 BGSAVE 的 RDB,为了从库复制数据的完整性,会将之前从库的 OutputBuffer 拷贝到请求全量同步从库的 OutputBuffer 中。

copyClientOutputBuffer 看似只是一个简单的 buffer 拷贝,但可能存在堵塞问题,因为 OutputBuffer 链表上的数据可达数百 MB 甚至数 GB 之多,对其拷贝可能使用百毫秒甚至秒级的时间,而且该堵塞问题没法通 过日志或者 latency 观察到,但影响却很大。

同样地,当 OutputBuffer 大小触发 limit 限制时,Redis 就是关闭该从库链接,而在释放 OutputBuffer 时,也需要释放数百 MB 甚至数 GB 的数据,其耗时对 Redis 而言也很长。

问题3.ReplicationBacklog 的限制

我们知道复制积压缓冲区 ReplicationBacklog 是 Redis 实现部分重同步的基础,如果从库可以进行增量同步,则主库会从 ReplicationBacklog 中拷贝从库缺失的数据到其 OutputBuffer。拷贝的数据最多是 ReplicationBacklog 的大小,为了避免拷贝数据过多的问题,我们通常不会让该值过大,一般百兆左右。但在大容量实例中,为了避免由于主从网络中断导致的全量同步,我们又希望ReplicationBacklog容量大一些,这就存在矛盾了。

此外,我们在重新设置 ReplicationBacklog 大小时,会导致 ReplicationBacklog 中的内容全部清空,所以如果在变更该配置期间发生主从断链重连,则很有可能导致全量同步。

二、共享复制缓存区的设计与实现

每个从库在主库上单独拥有自己的 OutputBuffer,但其存储的内容却是一样的,一个最直观的想法就是主库在命令传播时,将这些命令放在一个全局的复制数据缓冲区中,多个从库共享这份数据,不同的从库对引用复制数据缓冲区中不同的内容,这就是『共享复制缓存区』方案的核心思想。

实际上,复制积压缓冲区(ReplicationBacklog)中的内容与从库 OutputBuffer 中的数据也是一样的,所以该方案中,ReplicationBacklog 和从库一样共享一份复制缓冲区的数据,也避免了 ReplicationBacklog 的内存开销。

借鉴了 Redis 客户端输出缓冲区的实现方案,『共享复制缓存区』方案中复制缓冲区 (ReplicationBuffer) 的表示也采用链表的表示方法,将 ReplicationBuffer 数据切割为多个 16KB 的数据块 (replBufBlock),然后使用链表来维护起来。为了维护不同从库的对 ReplicationBuffer 的使用信息,在 replBufBlock 中增加了如下字段:

  • refcount:block 的引用计数
  • id:block 的唯一标识,单调递增的数值
  • repl_offset:block 开始的复制偏移

ReplicationBuffer 是由多个 replBufBlock 组成的链表,当 ReplicatonBacklog 或从库对某个 block 使用时,便对正在使用的 replBufBlock 增加引用计数,可以看到,ReplicatonBacklog 正在使用的 replBufBlock refcount 是 1,从库 A 和 B 正在使用的 replBufBlock refcount 是 2。当从库使用完当前的 replBufBlock(已经将数据发送给从库)时,就会对其 refcount 减 1 而且移动到下一个 replBufBlock,并对其 refcount 加 1。

三、解决问题

1.多从库消耗内存过多的问题

针对问题1提到的多从库消耗内存过多的问题通过共享复制缓存区方案得到了解决

2.OutputBuffer 拷贝问题

针对问题2 OutputBuffer 拷贝问题,当前从库的 OutputBuffer 的描述只有对共享 ReplicationBuffer 的引用信息,如上代码所示,所以之前的数据深拷贝变成了更新引用信息,即对正在使用的 replBufBlock refcount 加 1,这仅仅是一条简单的赋值操作,非常轻量。OutputBuffer 释放问题呢?在当前的方案中释放从库 OutputBuffer 就变成了对其正在使用的 replBufBlock refcount 减 1,也是一条赋值操作,不会有任何阻塞。

3.ReplicationBacklog 的限制

针对问题3因为 ReplicatonBacklog 也只是记录了对 ReplicationBuffer 的引用信息,如上代码所示,对 ReplicatonBacklog 的拷贝也仅仅成了找到正确的 replBufBlock,然后对其 refcount 加 1。无需担心 ReplicatonBacklog 过大导致的拷贝堵塞问题。而且对 ReplicatonBacklog 大小的变更也仅仅是配置的变更,不会清掉数据。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值