提供“透明分布式”能力,为用户提供近似单机数据库的使用体验,是PolarDB-X孜孜不倦的追求。本篇将要介绍的是PolarDB-X在Data Replication领域的最新力作——全局Binlog,它是“透明分布式”能力的一个典型代表,解决了诸多痛点问题,下面展开介绍。
故事起源
故事要从Binlog说起,Binlog是MySQL数据库引入的一个概念,其因数据复制而生,随MySQL分布式能力的演进而不断进化,经历了异步复制(ASynchronous)、半同步复制(Semi-Synchronous)和全同步复制(MGR)几个发展阶段,目前已经是非常成熟的技术。Binlog及其数据复制能力,不仅满足了MySQL体系内的数据复制需求,其更是实现各种数据架构和支撑各种业务场景的“数据驱动器”,简单总结如下:
- EDA [Event Driven Architecture]
- CQRS [Command Query Responsibility Segregation)
- 事件溯源 [Event Sourcing]
- 数据恢复《PolarDB-X 是如何拯救误删数据的你》
- 缓存刷新
- 数据集成
- 流式计算
- 灾难备份
- 异地多活《真·异地多活架构怎么实现?使用PolarDB-X!》
- … , …
场景不胜枚举,此处不再展开介绍,显而易见,Binlog的存在,让很多事情变得简单、变得美好!!
揭开面纱
用户体感
那么,全局Binlog又是一个怎样的存在呢?下面揭开它的面纱,我们通过两个例子来看看它的模样。
首先连接上PolarDB-X 2.0,比如mysql -hpxc-xxxxx -uxxx -pxxx
登录之后,可以直接进行MySQL Binlog的相关操作,如下所示:

将对应的Binlog文件进行dump下载,可基于mysqlbinlog工具直接解析

上面是通过执行show master status、show binlog events和mysqlbinlog命令后,输出的全局Binlog的结构,什么?这不就是MySQL Binlog吗?是的,全局Binlog就是Binlog,PolarDB-X Global Binlog is just Binlog,您可以像使用单机MySQL的Binlog一样,使用分布式数据库的事务日志,无需感知到分布式系统的任何复杂性和内部细节,更重要的是它完全兼容MySQL的Binlog格式和Dump协议,所以
- 您可以在MySQL上执行一条"Change Master …"命令,快速构建一条以PolarDB-X为Master的主从同步链路
- 您可以使用Alibaba Canal来消费PoarDB-X的Binlog,快速构建一个Data Pipeline
- 您可以使用阿里云DTS服务,将PolarDB-X的数据实时同步到其它存储介质中
- 您如果准备将MySQL或兼容MySQL的数据库迁移到PolarDB-X,之前围绕Binlog打造的技术体系可无缝对接
- … , …
是的,这就是透明分布式,全局Binlog的使命和初心。所谓透明,就是用户不会、也无需感知到系统内部的复杂性,对于全局Binlog来说,可以从两个方面来体现:一是对“接入”透明,我们在Binlog格式上屏蔽了各种内部细节、提供单机事务使用体验,在接入方式上高度兼容MySQL Dump协议;二是对“变化”透明,当系统内部发生HA切换、增加或移除节点、执行分布式DDL等操作时,用户都不必担心是否会影响到基于全局Binlog的消费链路,系统内部设计了一系列的协议和算法来保证全局Binlog的服务能力不受各种变更的影响。如前所述,完全把PolarDB-X看作一个单机MySQL即可。
Demo演示
事实胜于雄辩,插播一个视频,来展示一下:
polardb-x cdc demo-audio-1080p.mp4
架构原理
Global Binlog is just Binlog更像是一句广告语,后续篇幅,将正式从技术实现的角度来剖析全局Binlog的全貌。
整体架构
首先,来看一下全局Binlog组件在PolarDB-X整体架构中的位置,PolarDB-X作为一款云原生分布式数据库,其基本架构如下图所示(如果想更全面的了解PolarDB-X的产品架构,可参考我们的另外一篇文章《PolarDB-X 简介》以及PolarDB-X推出的其它系列文章)。

如上图所示,整个集群由四个子集群组成:
- CN : Compute Node (计算节点)
- DN : Data Node (存储节点)
- CDC : Change Data Capture (CDC节点)
- GMS : Global Meta Service (全局元数据服务)
本文主要介绍的全局Binlog是CDC的核心组件,关于CDC后面将有专栏来进行介绍(预告:它是一个兼具数据“流处理”和“批处理”能力的生态设施),下图展示的是全局Binlog组件的运行时状态图

上图中展示了几种类型的链路,分别是:
- SQL链路:CN节点接收SQL请求,并进行全局事务控制,最终在各个DN节点进行commit,并产生分片局部的原始Binlog
- 全局Binlog生产链路:全局Binlog组件负责将DN节点分片局部的原始Binlog转化为全局Binlog,并对外提供消费能力
- Dump注册链路:CN节点接收Dump请求,然后将请求转发给全局Binlog组件的Dumper Leader节点
- 全局Binlog消费链路:Dumper Leader节点接收到Dump请求后,将全局Binlog数据源源不断的推送给消费者
基本原理
其次,来了解一下全局Binlog的基本原理。全局Binlog又称为逻辑Binlog,它是以TSO为排序基准,将多个DN节点的原始Binlog中的局部事务进行排序、归并和合并,对数据辅以过滤和整形,最终把分布式事务转化为单机事务日志格式,满足数据强一致要求,并兼容MySQL Binlog文件格式和Dump协议的Binlog。基本原理如下图所示

下面对基本原理展开介绍:
-
什么是TSO
TSO是Timestamp Oracle的简称,它是一个全局逻辑时钟,PolarDB-X的分布式事务便采用了TSO策略,来保证正确的线性一致性和良好的性能,具体可参考文章《PolarDB-X 强一致分布式事务原理》和《PolarDB-X 全局时间戳服务的设计》。PolarDB-X的分布式事务在提交之后,会在事务所涉及到的每个DN节点中产生Binlog Event,除了有常见的XA Start Event、XA Prepare Event和XA Commit Event之外,还会附带一个CTS(commit timestamp) Event来标识事务的Commit时间,这个CTS Event保存的是一个具体的TSO时间戳,全局Binlog会基于此TSO对事务进行排序。 -
排序和归并
其是全局Binlog最基础的能力。通过以TSO为排序基准,首先对各个DN节点的Binlog(相对于逻辑Binlog的命名,我们称DN节点的Binlog为原始Binlog或物理Binlog)进行局部排序来构建"偏序"集合,然后通过归并排序将“偏序”集合归并为一个“全序”集合,即:保证了事务的有序性。
如果对偏序和全序这两个名词比较陌生,建议可以先阅读一下分布式领域的经典论文《Time, Clocks, and the Ordering of Events in a Distributed System》进行科普。先来了解全局Binlog中的偏序,每个DN节点都有自己独立的物理Binlog文件,全局Binlog系统会针对每个DN构造一个Binlog Event Stream,这个Stream中事务的顺序并不是天然按照CTS有序的,所以系统会针对每个DN先进行一次排序,得到若干个局部有序的集合,称之为“偏序”集合(如下图所示)。再来看全序就很好理解了,对于每个分布式事务,参与到该事务的每个DN节点的物理Binlog中记录的事务ID和CTS都是相同的,只需要将所有DN的偏序集合进行多路归并,便可以得到一个全局有序的全序集合。

-
合并、过滤和整形
其是全局Binlog在排序和归并基础上的能力进阶。一个涉及多个DN节点的分布式事务提交以后,会在每个DN节点分别产生物理Binlog,物理Binlog中包含大量XA类型的Event和PolarDB-X定制化的Private Event。合并模块会以事务ID为基准对原始Binlog进行聚合,对来自各个DN节点的Event进行事务内的排序,然后剔除XA- Event和Private Event的痕迹,最终构建出只保留了单机事务特性的Binlog文件,即:保证了事务的完整性,并剔除了复杂性。
实例展示
上面介绍的基本原理看上去会比较抽象,接下来为大家展示一个具体的例子,来直观的感受一下,一个分布式事务是如何被转化为了一个单机事务。
-
准备一个PolarDB-X实例,本例中准备的是一个两节点的实例,如下所示

-
在PolarDB-X上准备一个库和一张表,并向表中插入一些数据,如下所示

//

-
开启并执行一个事务,如下所示

-
登录DN节点,查看物理Binlog情况,会发现有大量XA特性的Event,一共有3个分片参与了该事务,并可以留意一下,这3个分片的局部事务在提交时的TSO是完全一致的,都是6793455909061452032,如下所示

//

-
回到PolarDB-X控制台,执行"show binlog events"命令,我们会发现分布式事务的物理Binlog已经被合并为了只有单机事务特性的逻辑Binlog,如下所示,注意图中最后一个Rows_query Event的内容,其前19位代表的是这个单机事务所对应的原始分布式事务的CTS,其值也是6793455909061452032,和步骤4中的CTS吻合,另外也可以发现,物理Binlog中的物理库表名称在这里已经被转化为了逻辑库表名称

-
将步骤3中的SQL进行调整,反复执行几次,看一下在全局Binlog文件中,事务是否是按CTS有序的,如下所示

深入介绍
本章节将借助几个典型场景,来剖析一下全局Binlog的技术内幕,看它解决了哪些复杂性问题,带来了哪些收益。
数据强一致
如果对“数据一致性”或“数据强一致”不太了解,这里推荐两篇技术文章:《分布式数据库中的一致性与时间戳》和《CockroachDB’s Consistency Model》。对于全局Binlog,通俗的来理解,它保证了事务的“完整性”和“有序性”,便具备了保证数据强一致的能力,而PolarDB-X在没有全局Binlog之前,用户只能直接消费每个DN节点的物理Binlog,此方式既不满足“完整性”也不具备“有序性”,便不能保证强一致性。
下面通过两个案例来理解全局Binlog如何保证了强一致,案例一是个“转账”场景(如下图所示),PolarDB-X同步数据到单机MySQL,如果直接同步物理Binlog,则无法保证在MySQL中总是能查询到一致的账户余额,如果是同步全局Binlog,则在MySQL中始终可以查询到一致的账户余额。

案例二是个“更新拆分列值”的场景(如下图所示),一条数据的拆分列的值被更新之后,其所属的分片可能会发生变化,变化前后的分片可能属于同一个DN节点,也可能分属于不同的DN节点,本例的场景对应的是后者。当用户提交一条Update SQL,PolarDB-X执行引擎会自动判断该SQL是否涉及到了拆分列值的变化,如果是的话会自动启动分布式事务,将UPDATE转化为DELETE+INSERT的组合操作。比如在本例中,PK=1的某条数据,更新前数据分布在DN1,更新后数据分布在DN2,那么,DN1的物理Binlog中会有一条DELETE Event,DN2的物理Binlog中会有一条INSERT Event,它们归属于同一个事务。在此场景下,如果直接同步物理Binlog,因无法保证DELETE和INSERT之间的顺序,所以可能会导致数据丢失;但如果是同步全局Binlog,系统会保证“DELETE Always happen before INSERT”,这是靠TraceId做到的,在类似拆分键变更这样的场景中,PolarDB-X的执行引擎会按顺序为每个操作标识一个TraceId,全局Binlog在进行事务合并时,会基于TraceId保证事务内的Event之间的顺序。

透明分布式
前文多次提到了透明分布式,下面我们就以最常见的水平扩展场景为例,来分析一下全局Binlog的内部实现细节,看它是如何做到的透明平滑变更。水平扩展是分布式数据库的核心能力之一,PolarDB-X作为一款云原生分布式数据库,快速水平扩展更是它的强项,依托云的弹性能力,PolarDB-X能够在数分钟内扩展出足够多的CN和DN节点,这种能力为用户带来了巨大的价值,想了解PolarDB-X在水平扩展方向上的技术细节可参考文章《PolarDB-X 水平扩展》。水平扩展的意义显而易见,但对于Binlog消费订阅场景来说,它却是把双刃剑,想象一下在直接消费DN节点的物理Binlog的场景下,当进行水平扩展性时,会是一个怎样的场景?当流量洪峰过去之后,进行水平伸缩时,又是一个什么样的场景?是的,对应的是一套繁琐的运维流程,不仅容易出错,还直接拖慢了系统的伸缩速度,如下图所示:

全局Binlog为用户屏蔽了水平扩展时的复杂性,依托全局Binlog,用户可以向上图场景“Say Goodbye”。它是如何做到的?答案是分布式一致性拓扑快照。先来解释一下什么是拓扑,拓扑分为两类:一类是“运行时拓扑”,可以和Flink的Execution Graph Topology进行类比,为了叙述方便,定义英文简称为EGT;一类是“库表元数据拓扑”,熟悉PolarDB-X的读者会比较熟悉,就是执行“show topology …”命令时返回的元数据拓扑信息,为了叙述方便,定义英文简称为MIT(Meta Information Topology),为了更形象化的理解,继续看图


- 运行时拓扑
是一个简单的有向无环图(DAG),有3种类型的顶点,分别是DN、MergeSource和MergeJoint,DN不再解释,MergeSource担负的主要职责为偏序集合排序、数据整形和过滤,MergeJoint担负的主要职责为全序集合排序和事务合并,在顶点之间流动的是Binlog Events。 - 库表元数据拓扑
是一个无向图,上图中的MIT展示的是PolarDB-X逻辑库内部的最基本的拓扑关系(逻辑库和逻辑分区之间的关系、逻辑分区和物理DB之间的关系、逻辑分区和DN节点之间的对应关系),如果精细到表级别,拓扑会更加复杂,此处不展开介绍。
回到水平扩展的场景,继续来看全局Binlog的透明平滑变更是如何做到的,答案是靠两个关键操作“打标”和“取标”,通过执行一个分布式事务来进行打标操作,打标的内容视当前上下文而定,事务提交后,在所有DN节点的物理Binlog中都会记录该打标内容,MergeSource和MergeJoint在消费Binlog Event Stream的过程中会完成“取标”操作,针对不同打标内容做不同的处理。结合上图,核心流程分析如下:
- 最开始是一个“初始化”类型的打标,全局Binlog运行时在收到这个标记之后,会完成EGT和MIT的初始化,分别创建一个镜像,并以此次打标事务的TSO为基准记录到Topology Snapshot History表中
- 新增一个DN节点之后,PolarDB-X内核会感知到DN数量的变化,在这个DN节点正式参与服务前,会先执行一个类型为“DN节点变更”的打标操作,打标内容为最新的DN列表,全局Binlog运行时的MergeJoint节点在收到这个标记之后,会用最新的DN列表创建新的EGT镜像,并以此次打标事务的TSO为基准记录到Topology Snapshot History表中,然后对整个运行时进行重启
- 新增DN节点就绪之后,PolarDB-X会开始进行分区调度,实现分区分布的再平衡(Rebalance),在分区数据调度即将完成,新的库表元数据和旧的库表元数据进行Exchange之前,会执行一个类型为“库表元数据变更”的打标操作,打标内容为“变更前的MIT”和“变更后的MIT”,全局Binlog运行时的每个MergeSource节点在收到这个标记之后,会以“变更后的MIT”为准创建新的MIT镜像,并以此次打标事务的TSO为基准记录到Topology Snapshot History表中,并刷新Binlog Event Filter,然后局部重启MergeSource。
- 各个MergeSource都会去记录Topology Snapshot History,系统会保证幂等性
- 各个MergeSource的消费进度并一样,在同一时刻,可能有的MergeSource已经消费到了“TSO>300”的位置,有的MergeSource仍然处于“TSO>200 & TSO<300”的位置,但这不会带来任何数据一致性问题,因为打标事务的TSO在各个DN节点都是相等的,所以各个MergeSource有一致的“对齐点”
熟悉Flink或流计算的读者,应该会发现,此处介绍的“打标和取标”机制,和分布式快照算法Chandy-Lamport非常类似,感兴趣的读者可参考《Distributed Snapshots: Determining GlobalStates of Distributed Systems》
DDL和数据整形
DDL(Schema Change)是关系型数据库绕不开的话题,也是分布式数据库的一个难点课题,想了解PolarDB-X DDL引擎的技术原理可参考文章《PolarDB-X Online Schema Change》。全局Binlog系统对DDL的处理也是靠“打标和取标”机制来实现的,并且也存在一个快照机制——分布式一致性Schema快照,系统会基于快照对Physical Binlog Event进行整形,输出符合标准的Logical Binlog Event,具体细节将在后续文章中进一步介绍。
总结展望
限于篇幅,技术细节不再展开介绍,更多技术内幕后续会有专题文章陆续发布,敬请期待,下面做一下总结展望。
性能测试
全局Binlog是一个把多流归并为单流的设计,所以,从架构上来看是存在单点瓶颈的,但这个瓶颈点的水位还是比较高的,对于一般用户和大多数场景来说,并不会轻易触达这个瓶颈点。从我们内部的测试数据来看,对一个4节点的PolarDB-X实例(单个节点配置为8Core32G)进行压测,以有效Binlog Event(有效Event指的是不包括BEGIN和COMMIT这种)为单位来计量吞吐量的话,每秒可输出的Event数量在25W~30W左右。后续会有专门的文章来详解介绍全局Binlog在性能和稳定性上的一些思考和设计,应对大规模集群的多流方案也已经在路上,敬请期待。
横向对比
| CDC能力对比项 | PolarDB-X | TiDB | CockroachDB | YugabyteDB | 其他(TDSQL/GoldenDB) |
|---|---|---|---|---|---|
| 事务原子性保证 | 支持 | 支持 | 不支持 | ||
| (行级通知) | 不支持 | ||||
| (行级通知) | 不支持 | ||||
| (仅支持单分片事务) | |||||
| 外部一致性保证 | 支持 | 支持 | 不支持 | 不支持 | 不支持 |
| (仅支持分片内的局部有序) | |||||
| DDL变更兼容 | 支持 | 支持 | 有限支持 | 有限支持 | 不支持 |
| 水平扩容兼容 | 支持 | 支持 | 支持 | 支持 | 不支持 |
| 开源生态数据订阅 | 兼容MySQL Binlog | Kafka订阅 | Kafka订阅 | Kafka订阅 | Kafka订阅 |
| 开源生态数据同步 | 兼容MySQL Replication | 工具形态支持 | 不支持 | 不支持 | 依赖外部工具(类似DTS) |
| 内核级主备复制 | 兼容MySQL Replication | 工具形态支持 | 支持* | 支持* | 不支持 |
| 异构库数据同步 | Aliyun DTS生态兼容(Oracle/DB2/MySQL/Kafka等) | 自建体系(MySQL/TiDB/Kafka) | / | / | 类似DTS生态兼容 |
最后,全局Binlog是PolarDB-X团队精心打磨的一个产品,在分布式数据库Data Replication方向上实现了一些突破,透明分布式能力是它的强项,更重要的是它和MySQL生态的高度兼容,“Global Binlog is just Binlog,Treat PolarDB-X just as MySQL”——这是全局Binlog为用户带来的最大价值。另外,文章的最后预留一个彩蛋,TiDB/CockroachDB/YugabyteDB基于存储层的行级CDC机制构建数据库同步,比如两个分布式数据库集群之间互相复制,会有什么数据一致性问题?
本篇为全局Binlog的开篇文章,后续会有专栏继续揭开全局Binlog的技术内幕,PolarDB-X在Data Replication方向上的其它产品也会相继推出,敬请期待。
参考文献
- CQRS Patternhttps://martinfowler.com/bliki/CQRS.html
- Time, Clocks, and the Ordering of Events in a Distributed Systemhttps://lamport.azurewebsites.net/pubs/time-clocks.pdf
- CockroachDB’s Consistency Modelhttps://www.cockroachlabs.com/blog/consistency-model/
- Architecture of Flink’s Streaming Runtimehttps://events.static.linuxfound.org/sites/events/files/slides/ACEU15-FlinkArchv3.pdf
- Flink 原理与实现:架构和拓扑概览http://wuchong.me/blog/2016/05/03/flink-internals-overview/
- Distributed Snapshots: Determining GlobalStates of Distributed Systemshttps://lamport.azurewebsites.net/pubs/chandy.pdf




