如何使用携程海外版 MySQL 实现毫秒级数据复制?
1)架构升级
DRC中有2个核心功能需要跨公网传输数据:
-
业务Binlog数据复制
-
DRC内部延迟监控探针
① 数据复制
以单向复制为例,在Binlog拉取模块Replicator和解析应用模块Applier之间引入Proxy,负责在TCP层将内网/公网流量转发到公网/内网。Proxy绑定公网IP,采用TLS协议加密传输内网流量。鉴于公网质量不稳定特性,Proxy使用BBR拥塞控制算法,优化丢包引起的卡顿。
Proxy作为公网数据传输携程内部统一的解决方案,开源地址:https://github.com/ctripcorp/x-pipe,欢迎关注。
② 延迟监控
延迟监控探针从业务流量同侧机房的Console写入到业务数据库延迟监控表(初始化时新建),经过双向复制链路,从异侧机房接收延迟探针,从而计算差值得到复制延迟。为了提升Proxy间隔离性,数据复制和延迟监控可以分别配置不同的Proxy实例实现数据传输。
③ Proxy Client
由于Applier和Console都需要对接Proxy,如何降低Proxy对DRC系统的侵入性就成为一个需要解决的问题。为此我们借助Java Agent技术,动态修改字节码,实现了可插拔的接入方式。接入方只需要引入proxy-client独立Jar包,业务层按需实现Proxy的注册和注销。
2)网络优化
公网网络丢包和拥塞频发,为了在弱网环境下实现平稳复制,就需要快速地异常检测恢复机制。除了在系统层将Proxy拥塞控制算法优化为BBR外,DRC在应用层额外增加:
-
心跳检测,实现连接自动切换
-
流量控制,避免突增流量引起资源耗尽进而影响数据复制
-
2条互备海外出口运营商线路,随机切换
① 心跳检测
Binlog生产方Replicator定时对下游消费方进行心跳检测,消费方接收到心跳检测需回复响应,Replicator根据最后一次接收时间检测并自动关闭长期没有响应的连接。
这里有一种场景需要特别处理,当下游消费方比较忙,主动关闭连接auto_read属性时,由于应用层无法读取暂存在缓冲区的心跳包,从而造成无法响应。这就需要消费方在auto_read改变时,主动上报生产方自身的auto_read状态。
② 流量控制
公网网络质量下降导致复制延迟变大,数据堆积在发送端Proxy,进而引起Replicator和Proxy触发流控;MySQL性能抖动,应用Binlog速度减缓,数据堆积在Applier,进而引起Applier触发流控并逐层反馈到Replicator。
③ 运营商线路
针对Proxy出口IP,分别配置移动和联通两条运营商线路,当Binlog消费方由于触发空闲检测出现超时重连时,Proxy会随机选择一个运营商出口IP,从而实现运营商线路的互备。
3)事务表复制
国内机房间数据复制时,DBA可以给予DRC拥有root权限的账号,以实现Applier模拟原生Slave节点set gtid_next工作方式应用Binlog,从而将一个事务变更从源机房复制到目标机房,并且在两端分配到同一个gtid下。但是公有云上RDS出于安全原因是无法开放root权限,直接从原理上否定了原有的复制方案。
为了找到合理的替换方案,我们首先从MySQL服务端视角分析下set gtid_next的效果:
-
事务在提交后会被分配指定的gtid值,否则MySQL服务端会自动分配一个gtid值;
-
gtid值加入MySQL服务端全局变量gtid_executed中。
其根本性作用在于将DRC指定的gtid值保存到MySQL系统变量。既然无法利用MySQL系统变量,那么从业务层增加一个复制变量保存gtid信息即可实现同等效果。
其次,转换到DRC复制视角,set gtid_next起到如下作用:
-
记录Applier复制消费位点,并以此向Replicator请求Binlog;
-
解决循环复制,Replicator根据gtid_event中的uuid判断是否是DRC复制产生的事件。
综上分析,新的替代方案需要引入持久化变量,记录复制位点并且能够提供循环阻断信息功效,为此DRC引入基于事务表的同步方案解决了海外复制难题。
① 位点记录
海外复制业务集群需要新增复制库drcmonitordb,其中新建事务表gtid_executed。
CREATE TABLE `drcmonitordb`.`gtid_executed` (
`id` int(11) NOT NULL,
`server_uuid` char(36) NOT NULL,
`gno` bigint(20) NOT NULL,
`gtidset` longtext,
PRIMARY KEY (`id`,`server_uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
server_uuid:源端数据库UUID号。
-
gno:事务id。该列值为0的行为汇总行。
-
gtidset:对于gno=0的汇总行,该列批量存储gno编号,例如server_uuid:1-10:20:30。
当Applier应用SQL到目标数据库前,需要先更新事务表,记录gtid,然后再执行事务中变更语句,完整的复制流程如下图所示。事务表中gno=0行中gtidset等效MySQL系统变量gtid_executed,Applier执行过程中定时汇总非0行事务gno,从而达到记录位点功能。
② 循环阻断
针对Binlog中第一个写事件是事务表gtid_executed操作的事务,Replicator将其判断为DRC复制数据,从而阻断循环复制,否则一条数据会在双向复制环内无限死循环。