欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > 行业案例:携程异地多活-MySQL实时双向(多向)复制实践

行业案例:携程异地多活-MySQL实时双向(多向)复制实践

2025/4/2 15:14:01 来源:https://blog.csdn.net/liuguojiang1128/article/details/146765297  浏览:    关键词:行业案例:携程异地多活-MySQL实时双向(多向)复制实践

携程异地多活 - MySQL 实时双向(多向)复制实践

一、前言

携程内部的 MySQL 部署采用了多机房架构。机房 A 部署了一个主库和一个从库,机房 B 部署了一个从库,作为灾难恢复(DR,Disaster Recovery)切换使用。在当前的部署中,机房 B 部署的应用需要跨机房进行写操作;当机房 A 出现故障时,DBA 需要手动对数据库进行 DR 切换。

为了实现真正的异地多活,并且做到 MySQL 在同机房就近读写,且在机房故障时无需进行数据库 DR 操作,只需进行流量切换,需要引入数据实时双向(多向)复制组件。


二、DRC 介绍

DRC(Data Replicate Center)是携程框架架构研发部推出的一款用于数据双向或多向复制的数据库中间件。它服务于异地多活项目,帮助携程在全球化的部署背景下,提供高可用的业务支持,赋能业务全球化能力。


三、DRC 架构设计

DRC 采用了服务端集中化设计,结合另一数据库访问中间件 DAL(Data Access Layer)的本地读写功能,实现数据的就近访问。


模块介绍

Replicator Container

Replicator Container 负责管理 Replicator 实例。一个 Replicator 实例表示对一个 MySQL 集群的复制单元。该实例将自己伪装为 MySQL 的从库,执行 Binlog 的拉取并进行本地存储。

Applier Container

Applier Container 负责管理 Applier 实例。每个 Applier 实例连接到一个 Replicator 实例,拉取 Replicator 实例本地存储的 Binlog,解析出 SQL 语句并将其应用到目标 MySQL,从而实现数据的复制。

Cluster Manager

Cluster Manager 负责集群的高可用切换工作,包括 MySQL 主从切换所引起的 Replicator 实例和 Applier 实例的重启,并对 Replicator 实例与 Applier 实例的主从切换进行新实例启动通知。

Console

Console 提供了 UI 操作界面、外部系统交互 API 以及监控告警功能。


最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的
7701页的BAT大佬写的刷题笔记,让我offer拿到手软

四、DRC 详细设计

4.1 接入 DB 规范

DRC 的核心指标包括复制延迟和数据一致性。

为了实现数据复制的低延迟,Applier 需要快速应用 SQL,这就要求每个表至少包含主键或唯一键,以加速执行效率。同时,在保证数据准确的前提下,SQL 应该尽量并行复制,为此需要 MySQL 开启从 5.7.22 版本引入的 Writeset 功能。

为了保证数据复制的准确性,在主备切换时 Replicator 仍能准确定位 Binlog 位点,需要 MySQL 开启 GTID。当数据复制发生冲突时,为了具备自动解决冲突的能力,表中需要包含时间戳列,并且精确到毫秒。

接入 DRC 的 MySQL 数据库需满足以下要求:

  1. 使用 5.7.22 及以上版本
  2. 在 Master 上开启 Writeset 并行复制
  3. 开启 GTID
  4. 每个表包含时间戳列,精确到毫秒;
  5. 每个表至少包含主键或唯一键。

DRC 的复制依赖于 GTID(Global Transaction ID),在这里简单介绍一下 GTID 的概念。MySQL 5.6.5 版本新增了一种基于 GTID 的复制方式,强化了数据库的主备一致性、故障恢复和容错能力,取代了传统的基于 file 和 position 的主从复制。这使得 MySQL 主备切换时能够准确定位到 Binlog 位点。

GTID 的格式为:source_id:transaction_id,其中 source_id 表示 MySQL 服务器的 UUID,transaction_id 是在事务提交时系统顺序分配的一个序列号。


4.2 Binlog 复制

单向复制链路包含拉取 Binlog 并持久化到本地磁盘的 Replicator,和请求 Binlog 且并行应用到目标 MySQL 的 Applier。整个链路涉及的 I/O 操作包括网络传输和磁盘读写。


4.2.1 低复制延迟

为了降低复制延迟,复制链路中的每一环都必须尽可能高效。网络层通信模型使用异步 I/O;系统层尽可能使用操作系统提供的 Zero CopyPage Cache;应用层提高数据处理并行度,降低系统不可用时间。

监控显示,在生产环境中,业务双向复制延迟在 999 线 以下,延迟小于 1s。接下来介绍一下 DRC 在降低复制延迟方面所做的性能优化工作。


1)网络层

Replicator 采用 GTID 复制方式,实现了 MySQL 复制协议,伪装成源 MySQL 的从库拉取 Binlog。网络层通信组件使用携程开源组件 XPipe(XPipe GitHub),实现网络交互异步化。

2)系统层

在接收 Binlog 时,从数据流中解析出不同类型的 Event,并直接保存在堆外内存中。

每个 Event 需要经过一组过滤器,进而决定是否需要持久化。对于 Heartbeat 类型的 Event 需要过滤丢弃;对于某些不需要进行数据同步的库和表,需要丢弃相应的 Event,减少存储量和传输量。

高性能 I/O 的要点

对于需要持久化的 Event,直接将堆外内存中的数据写入文件的 Page Cache 并定时刷入磁盘,从而减少数据复制和 I/O 操作,降低处理耗时,提高 Replicator 拉取效率。

发送 Binlog 时,当 Applier 进度落后于 Replicator,需要从磁盘读取,这时只解析 gtid_event 事件,其他需要发送的事件直接从磁盘读取到堆外内存进行发送,减少数据复制。

3)应用层

Applier 借鉴原生 MySQL 基于 Writeset 的并行复制,内嵌了基于水位的并行算法,高效地将 SQL 应用到目标数据库。

除了正常复制外,为了降低系统不可用时间,系统需要在异常情况下尽快恢复正常功能。例如在断网恢复时,为了避免一端使用老连接,就需要对连接进行空闲检测;为了应对断网导致的数据堆积而出现的流量突增,系统需要对流量进行控制。

4)空闲检测

Replicator 和 MySQL、Applier 通过 Netty 进行数据传输,当网络出现故障时,可能一端仍然使用老连接进行通信,从而导致数据复制中断。

针对网络故障,Replicator 对 MySQL 添加了读空闲检测,启动时设置 MySQL 空闲时间间隔为 10 秒发送一次 heartbeat_event,如果 30 秒内未收到 MySQL 任何事件,则认为 MySQL 出现问题并发起重连。

ReplicatorApplier 设置了写空闲检测,当没有 Event 需要发送给 Applier 时,间隔 10 秒发送一次 heartbeat_event,如果发送失败,则认为 Applier 出现问题并断开连接。

ApplierReplicator 设置了读空闲检测,如果 30 秒内没有收到 Replicator 的任何事件,则认为 Replicator 出现问题并发起重连。

5)流量控制

设计上,Replicator Container 使用物理机,其中会运行若干 Replicator 实例;Applier Container 使用虚拟机,这可能会造成发送和消费速率的不匹配。

尤其当 Applier 由于某些原因出现故障时,在 Replicator 端可能会堆积大量未消费的 Event。如果 Applier 重启后,堆积的 Event 全部发送过来,可能会直接打垮 Applier。因此,需要在 Replicator 实例上对 Applier 进行限流。

Replicator 发送端使用 Netty 提供的 WRITE_BUFFER_WATER_MARK 高低水位的变化来控制流控开关,从而动态调整发送速率,平滑流量。


最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的
7701页的BAT大佬写的刷题笔记,让我offer拿到手软

4.2.2 数据一致性

为了保证数据的一致性,需要满足以下要求:

  1. 数据拉取时保证时序
  2. 数据拉取不能遗漏,SQL 应用时不重复,或者即使重复,也要保证幂等操作,确保 At Least Once
  3. 数据冲突时,能够正确处理,保证数据最终一致。

接下来我们将介绍 DRC 如何保证以上三点要求。


1)时序保证

为了保证时序一致性,Replicator 将本地磁盘保存的 Binlog 使用原生的存储协议,顺序处理接收到的每一个 Event 事件。

存储协议兼容 MySQL 原生的 mysqlbinlog 命令,并根据 DRC 自身的需求,保存了自定义的一些辅助事件,比如 DDL 事件和表结构事件。在消费时,Replicator 会顺序发送 Binlog 文件中的事件给 Applier


2)At Least Once

为了实现 At Least Once 语义,需解决以下三个子问题:

  1. ReplicatorApplier 重启时,如何保证请求的 GTID 集准确体现当前的消费偏移?
  2. 双向(多向)复制如何解决循环复制?
  3. Applier 由于异常重复拉取时,如何保证幂等性?

下面逐一介绍每个子问题的解决方案。

断点重续

Replicator 重启时,会从本地磁盘中恢复已经拉取过的 GTID 集:

  1. 定位重启前使用的最后一个 Binlog 文件;
  2. 解析出 previous_gtids_event
  3. 遍历该文件中的所有 gtid_event,与 previous_gtids_event 解析出的 GTID 集取并集。

恢复过程中,会校验文件的正确性,对于没有以 xid_event 结束的事务,Replicator 会对文件进行截断,重新请求对应的 GTID 事务。

Applier 重启时,Cluster Manager 会从目标数据库中查询出当前已经执行过的 GTID 集并发送给 ApplierApplierReplicator 发送 Binlog 拉取请求时带上该 GTID 集。Replicator 收到请求中的 GTID 集后,从本地磁盘中定位出第一个需要发送的 Event 所在的 Binlog 文件,依次遍历该文件中的每一个 Event,对于 gtid_event 事件,检查该 GTID 是否在 GTID 集中。如果包含,表示 Applier 已消费过,无需发送;否则,通过堆外内存直接将 Event 发送给 Applier

循环复制

在单向复制时,经过 DRC 复制到对端的 SQL 在执行后,会再次落到 MySQL 的 Binlog 中。在双向(多向)复制结构中,对端的 Replicator 实例在拉取到该条 Binlog 后,如果继续复制,就会出现循环复制的问题。

针对循环复制,业内常见的解决方案是在 Binlog 事务开头插入一条写操作,标识该事务是由 DRC 复制过来的,而非真实业务写入。这样对端的 Replicator 在发现 Binlog 事务开头包含 DRC 特殊标记时,就不会继续复制该事务。

通过分析 MySQL 自身主从复制,Slave 在收到 Master 同步过来的 Binlog 时,会通过 set gtid_next 将该事务的 GTID 设置为同步过来的 gtid_event 中的 GTID,实现主从 GTID 集的一致性。

如果将 Replicator 拉取 Binlog 类比为 Slave 的 I/O 线程,磁盘文件类比为 Relay logApplier 类比为 Slave 的 SQL 线程,那么 Applier 可以采用同样的方式,使用 set gtid_next 设置经过 DRC 复制到对端的事务 GTID,从而确保源数据库和目标数据库的 GTID 集保持一致,并标识该事务是经 DRC 复制过来的。这就是 DRC 采用的破解循环复制的方案。

在以下双向复制结构中:

  • Replicator Instance1 只同步源 MySQL 集群 uuidSet1 中的事务;
  • Replicator Instance2 只同步目标 MySQL 集群 uuidSet2 中的事务。

如果业务在源 MySQL 集群写入数据,Replicator Instance1 会从 gtid_event 中解析出 GTIDuuidSet1 的关系。如果 GTID 所属的 uuid 存在于 uuidSet1 中,事务会被持久化并发送给 Applier Instance1,然后通过 JDBC 将 SQL 写入目标 MySQL,完成单向复制。

Replicator Instance2 收到 gtid_event 后,解析出 GTID,但 uuid 并不属于 uuidSet2,因此该条事务会被过滤,从而避免循环复制。

幂等性

如果 Applier 重复接收到相同 GTID 的事务,由于 MySQL 会记录已经执行的 GTID 集,因此如果该 GTID 已经执行过,MySQL 会自动忽略该事务。这样即使 Applier 重复应用同一条事务,也不会对业务产生影响。


最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的
7701页的BAT大佬写的刷题笔记,让我offer拿到手软

小结

通过时序保证、At Least Once 语义的实现、循环复制的防范以及幂等性的保障,DRC 能够有效确保 MySQL 实时双向(多向)复制的高效性和一致性。


3)冲突解决

在设计上,首先要避免冲突的出现:

  1. 接入 Set 化 的业务在流量入口处就会根据 uid 进行分流,确保同一个用户的流量进入同一个机房;数据接入层中间件 DAL 同样会采用 local-2-local 的路由策略。这样同一条记录在两个机房同时被修改的情况很少发生;
  2. 对于使用自增 ID 的业务,通过不同机房设置不同的自增 ID 规则,或者采用分布式全局 ID 生成方案,避免双向复制后数据冲突。

如果数据确实出现冲突,两个机房对同一条数据进行的修改,需要根据冲突处理策略进行处理:

  1. Applier 根据默认的冲突处理策略进行处理,接入 DRC 的表都有一个精确到毫秒自动更新的时间戳,冲突时时间戳靠后的会被采用,进而实现数据一致;
  2. 冲突的 SQL 会被监控记录,连同数据库中的原始数据一起提供给用户,自助决定是否需要进行覆盖。

4.3 DDL 支持

DDL 操作会引起表结构的变更,在复制链路中,Applier 需要表结构信息来解析对应时刻的 Binlog Event。当 Applier 消费速率落后于 Replicator 的发送速率时,就需要历史版本的表结构信息才能正确解析 Binlog Event

这就引出了第一个问题:历史版本如何存储?

为了存储表结构,必须首先获取表结构。如果直接从源 MySQL 抓取表结构,由于 Binlog 是异步发送的,这样抓取到 DDLBinlog 时刻,与 MySQL 上的表结构未必一一对应,从而可能导致 Applier 解析错误,进而引起数据不一致。引出了第二个问题:表结构从何处抓取?

业界通用的解决方案是基于独立的第三方数据库进行表结构单独存储管理。数据库本身作为存储工具,Snapshot 表和 DDL 表分别保存表结构快照和 DDL 变更记录,这样任意时刻的表结构就等于 Snapshot 及其后 DDL 变更的集合,从而解决了第一个问题。独立数据库会镜像源数据库的库表结构,每次从 Binlog 接收到 DDL Event 后,将解析出的 DDL 语句直接应用到镜像数据库,随即抓取相应的表结构,解决了第二个问题。

独立数据库解决方案的缺点是引入外部依赖,降低了系统的可用性并提高了运维成本。


4.3.1 表结构存储和计算

针对 DDL 功能中的问题一:

从数据库中查询 SnapshotDDL 记录的好处是时间顺序容易确定,能够简单准确地恢复表结构。那么是否有其他存储介质能够在保存表结构快照和 DDL 操作的同时,保证时序呢?有的,保存 Binlog 的文件就具有这种特性,DRC 采用了基于 Binlog 的表结构文件存储方案。

针对 DDL 功能中的问题二:

镜像数据库用于实时计算 DDL 变更后的最新表结构信息。为了避免使用独立部署的数据库,DRC 引入了嵌入式轻量级数据库,降低了外部依赖和系统运维成本。

整体设计方案如下图所示:

Binlog 文件头将保存自定义的表结构快照事件,当从接收的 Event 事件检测到 DDL 后,保存为自定义的 DDL 事件。这样当 Applier 连接上 Replicator 后,总是根据 GTID set 定位到需要的第一个历史版本表结构所在的文件,从而实时恢复表结构历史,用于后续 Binlog Event 的解析。

我们将数据库最小依赖打包成独立的 Jar 服务,每个 Replicator 实例启动时会一并启动一个独立的嵌入式数据库,在恢复 GTID set 的同时,根据表结构快照事件和 DDL 事件重建嵌入式数据库中的表结构。


4.3.2 DDL 入口

携程内部发布 DDL 是通过 gh-ost 进行变更,gh-ost 会在影子表中执行 DDL 操作,等影子表中的数据同步完成后,在业务低峰期进行原表和影子表的切换。

针对 gh-ost,需要追踪其变更过程中内部表(如 _xxx_gho)的所有 DDL 操作,最终执行切换时检测到 rename 操作,保存对应表结构的最新信息并发送给 Applier

对于数据库直接进行的 DDL 操作,直接检测出 DDL 类型的 Event 即可。


4.3.3 DDL 异常处理

对于接入 DRC 的数据库,在进行 DDL 变更时,可能会出现两边数据库变更不同步的情况。例如,单侧进行了 DDL 变更,另一侧未进行变更。针对新增列这种场景,Applier 在保证数据一致的前提下,会对新增列的值进行比较。如果 Binlog 中解析出的值与该列的默认值一致,则会剔除该列,继续进行数据复制。这样在另一侧补上 DDL 变更后,最终数据仍然一致。


4.4 监控告警

DRC 的核心指标包括复制延迟和数据一致性,除此之外,我们还提供了基于 BU、应用和 IDC 维度的监控:

  1. 流量和 TPS 监控告警;
  2. BU、应用和 IDC 维度的监控告警;
  3. DDL 变更监控;
  4. 表结构一致性监控告警;
  5. 数据冲突监控;
  6. GTID set GAP 监控。

五、总结

本次分享围绕 DRC 的核心指标复制延迟和数据一致性,介绍了复制过程中对性能的优化,以及如何在不同场景下保证数据的一致性。针对 DDL,我们支持 gh-ost 和直接 DDL 操作,实现了在线表结构变更且不影响数据复制。

未来,DRC 的工作将集中在高可用性、海外支持和外围设施的建设上,为携程的国际化战略提供数据层面的支撑。


【作者简介】
Roy,携程软件技术专家,负责 MySQL 双向同步 DRC 和数据库访问中间件 DAL 的开发演进,特别关注分布式系统的高可用设计和数据一致性领域。


最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的
7701页的BAT大佬写的刷题笔记,让我offer拿到手软

本文,已收录于,我的技术网站 cxykk.com:程序员编程资料站,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激!

真的免费,如果你近期准备面试跳槽,建议在cxykk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题、简历模板、算法刷题

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词