出海 | 携程《全球化技术架构与实战》一书火爆上市,看 DRC 如何支撑全球旅游业务无缝切换

作者简介

明冬,携程软件技术专家,关注分布式系统高可用设计、混合云场景下数据一致性等。

团队热招岗位:资深Java开发工程师(中间件)资深Java开发工程师(日志系统)资深研发工程师(分布式文件系统)

导读:本文来自携程刚刚上市的《全球化技术架构与实战》一书。携程集团自2016年起启动全球化战略,在全球化进程中,技术团队在挑战中学习,在磨砺中成长,积累了许多值得借鉴的经验。

全书以“全球化业务落地”为主线,深入剖析了从架构设计、流量调度、数据合规、基础设施,到体验优化、支付建设、客服系统及IT运维的完整技术实践,希望帮助出海企业规避技术陷阱,助力业务在海外平稳落地。

本月起,我们将推出4篇出海主题系列文章,带大家一起抢先看看新书中的部分精华内容。

点这里立即购买

  • 一、引言

  • 二、跨区域数据复制中心——DRC

  • 2.1 跨Region数据复制面临的核心问题与解决方案

  • 2.2 数据库二进制日志消息订阅

  • 2.3 稳定性和性能提升

  • 2.4 数据复制架构拓扑

  • 三、结语

一、引言

在全球化的软件架构方案中,全球在线旅游经常涉及用户远距离移动的场景,此时合规区内多Region间的数据复制将发挥重要作用,它可帮助用户实现合规区内跨Region服务无缝切换,保证用户享受高质量的服务。数据复制中心(DRC)通过零拷贝、并行复制等技术提供高性能的合规区内Region间数据复制能力,为众多跨地区业务场景提供支撑。

二、 跨区域数据复制中心——DRC

为了实现同一合规区内多 Region 间的流量切换能力,数据需要进行合规区内的跨 Region 复制。DRC是在 MySQL 的基础上构建的高可用数据库复制系统,用于合规区内的跨 Region 数据复制,可以支持多Region同时读写。DRC系统核心架构如图1所示,复制模块监听 MySQL 产生的二进制日志(Binlog)并将其传输给其他 Region 的同步模块,同步模块解析相关的二进制日志并将其转化为 SQL,最终写入目标数据库。

图1 DRC系统架构图

2.1 跨 Region 数据复制面临的核心问题与解决方案

作为一个数据复制系统,DRC 首先需要解决数据回环以及复制进度管理带来的挑战。同时,跨 Region 的多活数据库架构也会对系统带来更高的要求。后续内容将会对 DRC实现时面临的四个核心问题进行深入讨论。

2.1.1 数据回环与复制进度

在支持多主复制的架构下,数据会从任何一个 Region 写入,并同步到合规区内的其他 Region中。以图 2 为例,数据T1写入 MySQL1,经过 DRC 复制到 MySQL2,此时如果不作任何额外处理,对于 T1 的写入操作将会被回流复制到 MySQL1。为了避免这种情况,DRC需要对自身复制的数据操作进行标记,使得反向同步时能够识别并过滤这些操作,从而切断循环复制的路径。

图2 数据回环图

MySQL的全局事务标识符(GTID)可以用来标识数据。每个事务都会被分配一个GTID。每个GTID由源数据库的SERVER_UUID和一个事务ID组成。同步组件在写入目标数据库时,会通过set gtid_next将源数据变更时对应的GTID记录到目标库的gtid_executed变量中 (过程如图4-9所示)。当反方向的复制组件检测到事务的GTID中不包含自身的SERVER_UUID时,即可判定该操作是由同步组件产生的,从而将其过滤掉,避免数据回环。需要注意的是,这一机制要求同步组件具备目标数据库的root权限,以确保GTID能够被正确写入和读取。 

为方便读者理解,以图3为例详细介绍一下基于set gtid_next实现的数据回环阻断流程。假设两个MySQL集群,MySQL1的SERVER_UUID为 uuid1,MySQL2的SERVER_UUID为uuid2,MySQL1和MySQL2之间存在DRC双向同步。应用程序向MySQL1中写入一个事务T1(GTID为 uuid1:57810)后,MySQL1侧的复制模块识别到事务T1包含了MySQL1自身的SERVER_UUID,将事务T1传输到MySQL2的同步模块,并由MySQL2的同步模块执行 set gtid_next = uuid1:57810 命令,将事务T1写入MySQL2中。MySQL2写入事务T1后,MySQL2的复制模块在处理这条二进制日志时,可以识别到事务T1不包含MySQL2的SERVER_UUID,于是过滤掉T1,避免数据回环。

图3 root权限下数据标识

如果没有 MySQL 的 root 权限,则无法直接利用gtid_executed这个全局变量,此时需要一个类似的机制来保存 GTID 以实现同等的效果。DRC在实现的时候选择新增一张名为gtid_executed的表(DRC 事务表)来存储数据同步时候的GTID信息,用普通的数据更新代替set gtid_next命令。

下面详细介绍下工作原理,一条数据库事务的组成如图4所示。

图4 事务组成图

上述事务组成中不同的事件类型与含义如表1 所示。

表1 事件类型表

DRC 同步模块会从 gtid_log_event 中解析出表示本次事务的GTID,在同一个事务内将 GTID 持久化到 DRC 事务表,然后再执行业务的行变更。这样 MySQL 生成的二进制日志时就会包含 DRC事务表相关的事件,整体的事件流程如图5所示。

图5 事务表数据标识图

DRC 复制模块在拉取MySQL二进制日志时,如果事务中包含对 DRC 事务表的操作事件,则代表该事务为通过 DRC 复制的事务,无需再复制到其他 MySQL。

下面以图6为例详细介绍一下基于DRC事务表实现的数据回环阻断流程。假设两个MySQL集群MySQL1和MySQL2之间存在DRC双向同步。应用程序向MySQL1中写入一个事务T1(GTID为 uuid1:57810)后,MySQL1侧的复制模块识别到事务T1不包含事务表操作,将事务T1传输到MySQL2的同步模块。MySQL2的同步模块首先执行DRC事务表插入操作,然后将事务T1中的SQL写入MySQL2中,MySQL2生成事务T2。MySQL2的复制模块识别到事务T2包含DRC事务表操作,于是过滤掉T2,避免数据回环。

图6 事务阻断图

DRC 事务表的引入不仅解决了数据回环的问题,还可以作为复制位点持久化的存储介质。下面介绍DRC是如何持久化复制进度,实现同步中断自动恢复的。DRC 同步模块每复制一条数据,都会先将对应的 GTID 信息更新到 DRC 事务表中。同时DRC同步模块会启动一个定时任务进行 GTID 的合并操作,将独立的 GTID 合并为GTID集合(又称为GTID SET)标识已经完成了同步的事务,并将这个集合持久化在数据库中,用来表示整体的复制进度。整体的过程如图7所示。

图7 位点存储图

基于DRC事务表,DRC 同步组件可以做到无状态部署。在DRC同步模块作实例切换的时候,新启动的实例需要做一次 GTID 表合并,然后就可以基于之前的进度恢复复制。极端情况下,如果出现了两个DRC复制实例同时工作,重复执行复制的 SQL,此时在更新 DRC 事务表的时候就会产生冲突(DRC同步模块会在执行实际的业务 SQL 前更新 DRC 事务表,如果此时更新操作影响的行数为 0,则说明该事务已经被其他实例执行过),跳过该事务即可。

2.1.2 并发复制

为了保证复制的实时性,需要进行并发复制,MySQL 并发复制机制如表2所示。

表2 并发复制类型

DRC 采用了 MySQL 的 WRITESET 机制。WRITESET机制会在 MySQL 的二进制日志里对事务的依赖关系进行标识,DRC 同步组件在实际执行 SQL 时,可以通过这个标识,基于事务依赖性判断哪些事务可以并发执行,并最终对数据进行并行复制。

以实际场景为例,假设源端数据库存在客户表、订单表这两张表,且依次执行了三个事务:事务A修改客户1的余额并新增订单1,事务B修改客户2的余额并新增订单2,事务C修改客户1的姓名。WRITESET会分析:事务A和B修改不同客户,可以并行执行;事务A和C都修改客户1,必须串行;执行事务B和C修改不同客户,可以并行执行。传统复制必须按顺序执行这三个事务,启用WRITESET后,A和B可以同时执行,C等A完成后可以和B一起执行,提高了复制并发度。

2.1.3 DDL

DDL(Data Definition Language, 数据定义语言)用于表结构变更,执行后会在二进制日志中产生事件。DRC数据同步场景下,由于表结构变更耗时可能较长,若由同步模块直接执行DDL,将阻塞该数据库的正常数据同步。为避免数据复制延迟,DRC不同步DDL,由DBA直接在两端数据库执行DDL。

由于复制两端的 MySQL 实例执行 DDL 的过程存在时间差,目标端数据库的表结构与源端数据库的表结构可能存在差异,此时待复制的MySQL二进制日志可能和目标端表结构不匹配(举个例子,源端数据库在 T1 时刻执行完 DDL,目标端数据库在 T2 时刻执行完 DDL,则 T1 ~ T2 期间源端 源数据库产生的二进制日志对应的表结构与目标端数据库的表结构不一致),导致同步模块执行二进制文件中的SQL时报错。

在实际的运维情况下,大部分 DDL 变更主要是在原表上新增一个列,在 DDL 全部完成后,业务应用才会进行逻辑变更,对新创建的列进行写入。针对这样的场景,DRC的思路是尽量保证数据的最终一致性,基于二进制文件中的表列名信息,对原二进制日志中的SQL进行修改后再作执行。同步模块在保证数据一致的前提下,会对新增列的值进行比较,如果二进制日志中解析出的值和该列的默认值一致,则会剔除该列,继续数据复制。这样在另一侧补上 DDL 变更后,两侧的数据最终仍然一致。

为了实现上述的方案,DRC 同步模块在向目标数据库作写入操作时需要能够获得当前的表结构信息。但MySQL(MySQL 8以下以及 8 版本默认配置) 的二进制日志中,元信息不包含表列名。为了解决此问题,DRC复制模块引入了一个本地数据库,整体的变更流程如图8所示。复制模块在拉取到的二进制日志中发现 DDL 事件时,复制模块会把对应的 DDL 应用到本地数据库从而获得变更后的表结构,然后将对应的信息同步到目标端的 DRC 同步模块,保证 DRC 同步模块在向目标写入时,获取到的表结构信息是对的。

图8 DDL变更

2.1.4 数据并发写冲突识别、处理

首先要在源头上尽量避免冲突的出现。流量入口层和 DAL 层要实现一定的分发、限制机制,避免在不同的 Region 并发写同一份数据的情况。对于使用自增 ID 的业务,通过不同机房设置不同的自增 ID 规则,或者采用分布式全局 ID 生成的方案,可以从 ID 的角度避免复制后产生的数据冲突。

即使在流量入口和 DAL 层做了各种各样的分发、限制,最终可能仍然无法避免数据并发写冲突。携程的数据库规范是每张表都必须加一列 updatetime 来标记每行记录的最后一次更新时间。写冲突的识别、处理依赖此字段。真正出现写冲突,DRC 默认会按照时间戳留下最新写的数据,保证数据的最终一致性。如果遇到的是更新和删除操作的冲突,因为执行删除操作后数据就不存在了,无法用时间戳标记,DRC 默认使用“更新操作胜出”的策略,最终留下更新操作后的数据。

同时DRC也会将数据冲突报警给用户,并为用户提供了完善的冲突查看、冲突处理系统,保证线上业务的正常运行。

2.2 数据库二进制日志消息订阅

大量系统依赖外部缓存来避免频繁地数据库读取。如果数据在 Region1 内进行了更新,Region1 的业务应用可以主动将Region1的缓存设置为过期状态以保证 Region1 内缓存的新鲜度。但此时其他Region应该如何感知到这个事件并更新本 Region 的缓存呢?最直观的想法,Region1 的应用可以通过调用其他 Region 的服务,告知其他 Region 缓存失效的信息,或者也可以通过 Redis 的双向复制来进行缓存同步。但是这两种解决方案都可能会因为服务调用/Redis有延迟而导致数据更新操作和DRC不同步,进而产生问题,导致其他 Region 缓存的依然不是最新数据。

考虑到数据变更本质上来源于数据库,缓存的更新可以通过订阅 MySQL 的二进制日志来触发。为了解决这个问题,DRC 支持了 MySQL 的跨 Region 变更消息订阅,业务在 Region2 可以只订阅 Region1 的数据变更日志来触发缓存的更新。DRC 的消息投递架构参考图9。

图9 消息投递架构图

2.3 稳定性和性能提升

数据复制延迟是 DRC 系统的核心指标,也是 DRC 系统性能及可用性的整体体现。延迟复制最终可能影响数据的一致性和正确性。

影响一条复制链路延迟的因素有:

  • 网络质量:延迟、吞吐量、丢包率等

  • 复制数据量、并发度:由源端数据库数据写入速率、写入逻辑决定

  • 目标端数据库写入性能

  • DRC 核心组件(DRC 复制模块、DRC 同步模块)性能

DRC复制的基本原理如图10所示,DRC 复制模块和同步模块都基于 Netty 实现高性能网络通信。复制模块拉取到MySQL二进制日志后,保存在本地磁盘中。二进制日志同步线程基于事件通知机制,感知到有数据写入后,DRC复制模块会异步读取数据并异步发送相关事件给目标端的DRC同步组件。

图10 数据复制原理图

对任意一条同步链路,延迟和数据写入TPS的关系如图11所示。

图11 延迟和数据写入TPS的关系图

2.3.1 高效可靠的复制机制

为了得到高效可靠的数据库复制机制,DRC核心组件在以下几方面做了考虑。

  • DRC 复制模块采用了 GTID 复制方式,通过实现 MySQL 复制协议,可以伪装成源 MySQL 的从节点直接拉取主节点的二进制日志。

  • DRC 的网络通信层都使用异步化的方式实现。

  • DRC复制模块解析MySQL二进制日志事件后,直接将数据存入堆外内存以降低管理开销。对于某些不需要进行数据同步的事件直接丢弃,减少无效存储与无效传输。对于需要进行持久化的事件则将数据直接写入文件系统的页缓存并异步定时刷盘,减少IO 操作。同时利用零拷贝机制提高数据拉取效率。

  • DRC 同步组件借鉴 MySQL 的 WRITESET 机制进行并行复制,系统内嵌了基于水位的并行算法,高效地将 SQL 应用到目标数据库中。

  • DRC 各组件间持续保持空闲检测,通过心跳机制探活,在遇到异常状态时候及时重连。

2.3.2 提升系统吞吐

为了提升系统吞吐,DRC做了以下优化。

1)提高单条同步链路吞吐性能:通过优化 DRC 组件性能(Event 过滤、发送)实现。如图12所示,DRC 复制模块在本地的二进制日志中新增了自定义的事件(filter_log_event),支持根据库名跳过整个事务,较大提高了过滤性能,也为后续切换至数据库复制粒度打下基础。

图12 本地事务存储图

2)提高复制并发度:通过拆分同步链路、降低单条链路的同步粒度实现。通过提高DRC复制模块一对多同步性能以及DRC同步模块的数据写入性能,并将 DRC 从最初的MySQL实例复制粒度,平滑切换至数据库复制粒度,将整体延迟的95线降低87.5%(从8s降低至1s),整体结构如13所示。

图13 复制模式切换图

2.3.3 流量控制

DRC 需要通过一定的流量控制手段来保障系统持续稳定地运行。

对于数据复制模块到数据同步模块这条链路,假如数据同步模块出现了异常,会导致数据复制模块积压大量的未消费的事件。在数据同步模块恢复后,如果此时没有做任何流量控制,所有积压的事件都突然发送过来,可能会直接使数据同步模块过载,从而导致系统无法正常提供服务。数据复制模块需要具备一定的流量控制能力来避免这样的情况发生。数据复制模块在实现上使用了 Netty 提供的 WRITE_BUFFER_WATER_MARK 机制,基于流量高低的变化来动态调整发送速率,整形平滑流量。

对于数据同步模块到目标数据库这条链路,在实际运维过程中,我们发现数据复制的瓶颈往往在对端数据库性能。在将数据应用至对端数据库时,并发度(数据同步模块与数据库的连接数)以及平均事务耗时这两个指标会显著影响该链路的复制速率。可以使用每秒事务数(TPS)来衡量复制速率。对任意同步链路,假设并发度为 n,平均事务耗时为 t(单位为秒),那么 TPS 可以表示为:

TPS = n/t

如图14所示,在未达到并发度上限(并发上限由事务依赖性决定),且未达到数据库瓶颈的情况下,TPS与数据库连接数成正比。但当数据库到达瓶颈后,继续提高数据库连接数,会导致数据库性能下降,平均事务耗时 t 上升,甚至影响正常业务读写。DRC 支持手动调节复制链路的数据库最大连接数,来调节流量以达到最佳复制性能。

图14 TPS与数据库连接数关系图

2.3.4 稳定性

除了性能原因导致的复制延迟,网络异常、数据库异常、实例异常等外部异常也可能导致复制中断,最终导致复制延迟。DRC 通过自身的高可用模块,支持实例主备秒级切换,确保了机房级别灾备能力。高可用模块的部署如图15所示。若遭遇Region级别异常,发生同步中断,DRC 也可基于 GTID 机制在异常恢复后从断点处自动恢复同步。

图15 模块部署图

同时,DRC实现了完善的自动运维能力,通过优化系统功能、系统可观测性,自动化处理日常业务需求及异常情况,在外部故障概率不变的情况下,自动运维能力可以从以下两个方面提高系统的稳定性:

  • 提高异常场景的响应速度,减少整体故障时间

  • 减少降低人工运维复杂度,减少运维故障的概率

2.4 数据复制架构拓扑

数据复制可以分为两类:全连通架构和星型架构。以同一个合规区内的三个 Region 为例,全连通架构和星型架构如图16所示。

图16 数据复制架构图

表3从几个维度对两种架构进行对比。对于3个及以下Region的情况,推荐使用全连通架构;对于3个及以上Region情况的,推荐使用星型架构。当合规区内的 Region 比较多时,往往会选择1个中心 Region,将一些全局性的任务放在中心 Region。

表3 全连通和星型架构对比表

三、结语

本文介绍了跨Region间数据复制的基本架构,分析了数据复制设计中需要解决的核心技术问题,数据回环和复制进度确保数据的正确性,并行复制和DB粒度复制提升复制效率,降低业务因延迟读取脏数据引发逻辑错误的频率。

针对数据库表结构变更,在全部Region完成变更前,自动处理表结构不一致中间状态。对于业务使用过程中,常见的更新数据库后刷新缓存操作,通过消息投递支持跨Region的缓存更新。对于同一条数据多Region写引发的数据复制冲突,优先根据时间戳自动处理并记录冲突告警给用户。

有了 DRC 在数据层的支撑,能够让集团出海时更平稳的落地业务。

【有奖互动】你所在的企业在出海过程中,遇到过哪些技术挑战?欢迎在评论区留言,点赞最高的评论将获得新书一本,截止12月12日24点。

 点这里立即购买

【推荐阅读】

 “携程技术”公众号

  分享,交流,成长

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值