陌陌 IDC Redis 如何基于阿里云 DTS 实现云上容灾
导读
本文为阿里云解决方案团队为陌陌 IDC 机房 Redis 实例实现云上灾备的定制方案(方案已脱敏)。针对 Redis 跨机房数据同步,行业普遍有两个非常常见的问题需要解决:
1. Redis 的复制风暴问题:为了提升主从同步的性能,Redis 在主节点中引入了环形复制缓冲区机制。主节点向从节点传输数据的同时,将相同的数据副本写入环形缓冲区,当从节点因网络抖动或其他异常情况断开连接并在后续重新连接时,主节点通过复制缓冲区将断连期间产生的增量数据传递给从节点实现断点续传避免全量同步。但是内存复制缓冲区一般来说不会太大(默认只有 64M),从节点断点续传过程容易发生找不到 offset 而触发全量复制,从而触发主库所在宿主机复制风暴而 OOM,导致整个集群不可用。
2. 非幂等指令(如 incr)的数据同步的一致性问题:PSYNC 协议进行数据同步时,源节点通过 repl 字节流的 offset 值标识数据偏移,目标节点以 pipeline 方式写入数据,并定期(约 15 秒)持久化当前同步点位。然而,若任务因超时或重启中断,无法准确定位已完成的同步点位,可能导致点位回退。若回退范围内包含非幂等命令(如 incr 或 lpush),则可能造成目标端数据不一致。
本文从阿里云 DTS 的视角,通过结合阿里云在数据传输领域的技术积累和实践经验,针对上述两个问题提出了一套完整的解决方案,对企业 Redis 云上容灾有极大的参考价值。
1、陌陌 Redis 容灾需求调研
1.1、陌陌容灾项目背景
因为种种原因,陌陌在考虑基于云+IDC 构建 AZ 级的容灾能力。
基于陌陌当前的容灾机房规划,预计容灾建设完成后,将存在生产 IDC、灾备 IDC、灾备云三类机房。机房间的容灾关系如下:

其中【IDC-生产】和【IDC-灾备】是常态存在的,而【云-灾备】按照当前的设计仅在故障发生时才会临时拉起。拉起后,生产流量将切换到云上运行。
临时搭建云上灾备看似是一种敏捷且低成本的容灾手段,但需要考虑临时搭建的云环境是否能正常承接流量,是否能正常运转。这里可能存在代码版本、上下游依赖缺失等种种问题导致业务主链路无法正常运转的情况,因此建议考虑针对核心业务构建云上常态化的容灾实例和同步链路。架构如下:

进一步讲,如果【云-灾备】的建设成本与【IDC-灾备】相差不大,甚至可以考虑去掉【IDC-灾备】,由云来承接对应角色,这也是当下更主流的云上云下容灾架构。从架构复杂度和实际应用价值来看,【IDC-灾备】仅仅是切换过渡态临时使用,意义看起来不大。
1.2、数据同步工具选择考虑
针对 IDC 之间以及 IDC 到云上如何实现数据同步的问题,因考虑到现有管控体系对云上数据库
实例的统一纳管能力,陌陌在云上的产品选型以 IaaS+自建为主,初期考虑过基于原生复制
的同步方案,但考虑到 IDC 之间专线质量以及云上云下专线质量对 Redis 原生复制机制的影响
(RDB 风暴问题),因此当前在数据同步方案上会有两种选型:
●基于 DTS 实现跨云同步
●基于开源 XPipe 的实现跨云同步
两种选型各有利弊,对于日常生产,从数据一致性、运维成本等角度对比如下:
总结下来,DTS 需要解决如下几个关键问题,才能实现稳定可靠的跨云同步:
●问题 1:针对大规模云上云下同步,增强抗网络抖动能力,避免 RDB 风暴问题。
●问题 2:增强数据一致性保障能力,对齐和原生复制一样的数据位点保护机制。
2、DTS 如何实现陌陌 Redis 云上容灾
2.1、基于 DTS 同步的容灾架构

●当【IDC-生产】故障发生时,借助云上的弹性资源,基于公共云 DTS 临时拉取灾备实例的全量+增量数据到云上,构建云上的生产实例。如果针对核心业务要构建云上常态的容灾实例亦可采用此架构。
●同时阿里云在评估 DTS 是否可以在【IDC-生产】实现脱云输出,如果可行,则可以替换掉云下的 XPipe,实现【IDC-生产】->【IDC-灾备】和【IDC-灾备】->【云-灾备】的同步方案统一。
2.2、关键问题的技术方案
2.2.1 Redis Exactly Once
●背景问题:非幂等命令回退问题
使用数据同步工具同步社区 Redis 时,往往使用社区的 PSYNC 复制协议拉取源节点的数据,然后以客户端的形式写入目标节点。PSYNC 协议的增量阶段以 repl 字节流的 offset 值来表示当前的数据偏移,当同步任务进行时,一般对目的端以 pipeline 的方式写入数据,同步任务会定时(~15s)将当前同步的 offset 进行持久化。如果同步过程中任务写远端超时甚至任务重启,那么无法精确地知道目前已完成的点位,那么一旦出现点位回退,回退范围内存在非幂等的 Redis 命令(比如 incr/lpush 等)就会造成目的端数据不一致

●整体方案
为了解决上述问题,DTS 引入 Redis Exactly Once 技术来实现位点的实时推进和与库内数据库的一致性保障。
DTS 在消费增量数据时,通过将写操作包装到 Lua 脚本中来实现操作的原子性。脚本内增加记录实时 offset 和 offset 比较功能。同时会有任务状态的一系列判断操作。
同步的流程为:
在目的端 Redis 上 Load 一个 DTS 专用的 Lua 脚本,脚本内封装了任务状态判断、位点 记录、位点比较、命令执行等一系列操作。
开始使用 evalsha pipeline 发送请求即可。(当前 demo 版本对待同步的写操作未做幂等性区分,所有操作均走 lua 脚本执行,因此会有写放大。后续版本会仅对非幂等操作走 lua 脚本执行。)

●方案技术建议(可选):增强断点续传能力
同步前调大源节点的 repl-backlog-size 配置,以增强断点续传能力。
2.2.2 基于 Task Group 大规模迁移 RDB 协同方案
●背景问题:大规模 RDB 重传全局流控问题
当用户业务非常庞大时,所需要的 Redis 集群将会比较庞大,如果要迁移这种大规模集群,一个值得注意的问题是:当对源库执行 PSYNC 后,源 Redis 会 bgsave,需要 fork 出一个子进程去生成 RDB 快照(如下图所示),可能会导致 master 达到毫秒或秒级的卡顿抖动,以及大量内存占用,可能会对源库的业务造成影响。所以在 DTS 迁移时,就要控制对源库同时 PSYNC 的数量。

为了解决上述问题,利用 DTS Task Group 协调大规模 RDB 重传,方案的主要技术点如下:
●整体方案
基于 Task Group 大规模迁移 RDB 协同方案整体流程如下图所示。首先,判断是否存在可
用 slot,若存在则 DTS 向源 Redis 发送 PSYNC ?-1 命令,若 Redis 返回+FULLRESYNC,加入
running task,count+1,如果回应是+CONTINUE,count 不变。rbb 完成,进入增量阶段,
将任务移除 running task。

判断流程:

2.3、技术方案验证
●用例 1:ExactlyOnce 一致性验证
验证场景:
创建两个阿里云开源版 Redis5.0 标准版,在 DMS 控制台开启两个实例的 DTS 数据同步任务;将其中一个 Redis 作为源端数据库 Redis-source,将另外一个座位目的端数据库 Redis-target,在本地设备上登录源端数据库。在本地设备中编写脚本,每一秒钟往源端数据库中进行一次 incr 操作,运行脚本。主要验证以下两个场景。
DTS 数据同步:暂停数据同步任务 60s,重启数据同步任务,停止运行脚本,检查源端和 目的端数据库的数据一致性。
2. Redis Exacutly Once:修改数据同步配置,暂停数据同步任务 60s,重启数据同步任 务,停止运行脚本,检查源端和目的端数据库的数据一致性。
开启 ExactlyOnce 前结果:
可以看出,断点续传后,出现了点位回退,重复执行了 incr 操作,导致目的端 value 为 373,源端 value 为 364,目标端和源端数据不一致。

开启 ExactlyOnce 后结果:
修改 DTS 同步任务的配置,开启 Redis Exactly Once 功能,得到修改配置后的结果如下,断点续传后,目的端和源端的 counter 值均为 210,验证了 Redis Exactly Once 技术方案有效地保证了断点续传目标端和源端的数据一致性。


● 用例 2:RDB 重传全局流控
简要说明:以下是 2 个实例使用 DTS 同步,将这 2 个实例关联为同一个任务组
momo_dts_group,并设置同时 RDB 的个数为 1
以下是具体测试情况:
可以看到第一个任务 2 首先 2025-01-19 23:38:06 开始 RDB 同步,在期间任务 1 等待,任务 2RDB 于 2025-01-19 23:39:47 完成后,任务 1 迅速于 2025-01-19 23:39:48 开始 RDB,于 2025-01-19 23:41:30 结束。另外从 Redis 数据库监控进一步验证主从复制的流量起始时间与 DTS 任务相吻合,符合预期,至此 DTS 实现了防止源库集群 RDB 风暴的能力,提升了任务的可控性。
●用例 3:RPS 性能压力测试
验证场景:
创建规格为 4xlarge(参考性能上限 68000RPS)的数据同步任务,利用 resp_benchmark 压测工具进行性能测试,往源端进行 SET 操作,不断提高源端写 QPS,主要验证以下场景:
目标端相对于源端的写放大效果
DTS 4xlarge 在低延迟情况下,最高 RPS。
开启 ExactlyOnce 是否存在存储空间放大。
验证结果:
当前 Demo 版本目标端相对于源端写 QPS 的写放大为 100%左右,后续对写入做幂等/非幂 等拆分后,可大幅收缩写放大比例(视业务中非幂等操作数量决定)。
DTS 4xlarge 的极限 RPS 大概在 44000 左右(如下图所示),这是因为当前版本 demo 存在写命令和打标命令,因此写放大为 100%,若对非幂等命令进行拆分,假设非幂等命令占比总操作数量的 10%,在实际业务使用过程中 DTS 4xlarge 的极限 RPS 可以提升到 83600。以下是 DTS RPS 的性能压测的具体测试结果,当 RPS 在 40000 左右,平均任务延迟为 219.5ms 左右,最大延迟为 387.6ms;当 RPS 为 44380 左右,平均任务延迟为 299.3ms,最大任务延迟为 552.2ms;当 RPS 超过 45000,延迟会显著增加。

特别说明(数据同步任务延迟与 DTS 心跳的关系):任务延迟以 DTS 控制台性能监控数据为准,而监控中的任务延迟是基于 DTS 心跳进行判断,DTS 心跳周期为 300ms,所有期间发生的增量数据都通过夹逼的方式近似计算,并以当前时间减去心跳时间作为延迟,故即使在真实无延迟状态下,延迟仍然会在 0~300ms 波动右的任务延迟是正常数据同步情况。实际同步延迟为 50ms,50ms 为攒批时间,该值可调。
源数据库写 QPS

目标数据库写 QPS

3. 针对开启 ExactlyOnce 后存储空间是否有放大的问题,ExactlyOnce 功能在目标实例上会保存一个 lua 脚本和一个用于记录 offset 的 key,单节点仅有一个 key,集群版最多有 16384 个 slot,每一个 slot 一个 key,因此即便是集群版,其存储放大不会超过 500KB,主备版不会超过 100KB。如下是压测前后的存储空间对比。
压测前存储大小:

压测后存储大小:

3、容灾切换方案
如果基于 DTS 构建容灾架构,在故障发生时的切换方案大致如下:
阶段一:切流前

1、生产 IDC 故障,决策要做机房切换
2、云上创建资源和空实例,通过 API 新建 DTS 任务进行数据迁移。(如云上资源是 常态存在的可省略此步)
3、生产 IDC 实施禁写,防止脑裂。 (Redis6.0 以下版本无禁写能力,需要应 用层面实现禁写)
4、检查 DTS 任务同步状态,确保要切换 的实例没有处于【全量】阶段的。(全量 阶段会 flushall 目标库,此时切流目标端数 据严重缺失)
阶段二:切流中

1、停止灾备实例从 Keeper 拉取增量。将 灾备实例提升为可写状态。业务需保持进 行状态。
2、停止 DTS 正向同步任务,避免极端情 况下 DTS 任务执行 RDB 拉取而 flush 目标实例
3、通过 API 建立 DTS 反向同步任务(云 - IDC 灾备,需要开启 RDB 流控)
4、将业务流量切换到云上
阶段三:切流后

1、生产 IDC 恢复后,新建 DTS 任务恢复 IDC 内数据库(云->IDC 生产,需要开启 RDB 流控)
2、云上业务实施禁写,准备进行回切
3、检查 DTS 同步状态,确保没有处于 【全量】阶段的
4、停止 DTS 同步任务(云->IDC-生产)
5、将业务流量回切到生产 IDC 6、灾备实例重新建立到 Keeper 的同步
4、跨云容灾需要构建的生态能力
DTS 围绕跨云容灾构建的能力概览如下,这些也是跨云容灾场景有必要建设的能力。

评论