开发者学堂课程【PolarDB-X 开源系列课程:数据导入与导出(三)】学习笔记与课程紧密联系,让用户快速学习知识
课程地址:https://developer.aliyun.com/learning/course/1032/detail/15143
数据导入与导出(三)
三、Global Binlog(实时数据导出能力)
接下来看Global Binlog的整体的overview。
先看右边的图,PolarDB-X会有多个DN节点,每个DN上边有原始的物理Binlog,叫物理Binlog或者原始Binlog。然后CDC的Global Binlog组件会有一个流水线,核心的几个阶段就是Extractor它会解析DN的Binlog,解析完之后会进行排序的操作,Merger会把所有DN的经过排序的Binlog做全局的合并,之后排序输出,输出的Dumper,然后Dumper在对接下游,来提供输出能力。下游就是DTS、 MySQL、PolarDB-X等等。
在左边的文字里边来看一下Global Binlog的核心特性。首先是兼容事务的,Global Binlog里面所有的通过CN节点来执行的事务操作在Global Binlog里面是可以完全还原成它完整事务的。会基于TraceID还有TSO来做全局排序,这是一个核心的feature。
然后会兼容分布式的DDL,就是我们在执行DDL的时候,每个DN节点上面都会有对应的一些物理的DDL,我们可以做到透明的输出,屏蔽掉内部的一些DDL变更的细节。
兼容分布式的扩缩容就是内部做DN节点的增加或者删除操作的时候,对于下游来说是一个完全透明的操作,下游会完全感知不到内部的一些变更细节。
至于兼容MySQL生态的主备复制前面已经说过了。
接下来看一个演示,Global Binlog可以兼容MySQL。先看一下demo这个demo就是通过Flink-CDC来消费Global Binlog,一个双11大屏的展示。
这个视频在之前刚刚完成的发布会上也有对应的内容,如果有看过的同学,可以做复习,没有看过的同学,可以先感受一下这个视频。
视频网址:https://www.bilibili.com/video/BV1WS4y1q7jb/
这个视频只是想让大家来感受一下前面讲的Global Binlog的能力。
接下来讲一下内部的结构,外部我们来使用Global Binlog的时候非常简单,但内部是做了非常多的数据处理和各种各样的一些比较复杂的算法处理。
可以从图里看一下整个Global Binlog是怎么生成的。首先是从用户控制台去执行一个事务,到了CN节点,CN节点会执行事务里边的各种DmR的SQL,这些SQL会最终下发到所有的DN节点来执行,DN节点执行完SQL之后,等用户最终提交的时候,在DN节点就会产生Binlog,这个Binlog叫原始Binlog或者叫物理Binlog。右边虚线的部分是整个前期Binlog的内部的总体的结构,就是黄色的图。对接每个DN就会有一个Merge Source,Merge Source内部会做一系列的数据处理。Merge Source的右边有一个Merge Joint,这是做全局的排序处理。再到右边,就是Dumper的Leader和Follower,Leader就是主节点,follow作为备,当leader挂掉的时候,follow可以接管,作为一个新的leader。
这个图就是给大家展示一下Global Binlog的总体的大概的生产过程。虚线部分就是生产出来的Global Binlog怎么给下游去消费的,比如从下游是一个单键MySQL,然后执行一个Change master的命令,CN就会收到这样的一个命令,CN收到这个命令之后就会给CDC这边的Dumper发送对应的请求,最终Dumper的leader会把数据源源不断的推送给消费端。
今天的分享重点是事务排序和分布式的DDL是怎么来实现的。
讲排序之前,先熟悉一下PolarDB-X的重要特性,就是全局的时间戳,简称是TSO,左上角的图是TSO的格式,高42位是物理的时间戳,中间的16位是逻辑时间戳,这个逻辑时间戳可以认为是一个自增的系列,最后的六位就是保留字节。最终,它是64bit的长度,能够保证单调递增。当我们在PolarDB-X去执行完一个事务提交的时候,刚刚演示的那个片子里边说在DN会产生Binlog,我们会把TSO持久化到DN的Binlog,然后以名字叫GCN的event来持久化。
这个截图是从DN的Binlog里边截出来的一部分,可以看到有XA START、XA END、XA PREPARE、XA COMMIT,正好是一个事务完整提交的所有的Binlog。红色的框里边就是两个GCN event,上面的是Snapshot TSO,就是在启动的时候拿到的一个Snapshot的TSO,下面的是Commit TSO,这个就是Commit的时候取到的一个全局的时间序列,这个Commit TSO会作为CDC内部对事务进行全局排序的依据。这个是讲了最基本的概念,就是TSO以及它在DN Binlog里边的存储形态。如果想了解更多的细节,可以看我们的知乎文章,绿色文字里面展示的这个文章,从而了解它内部的一些细节。
接下来就循序渐进地来看一下,有了TSO之后,是怎么来排序的。
先看左边的图,它展示了总体的排序过程,有事务1,有一个黄色标识的事务2,TXN-1的TSO是1111,TXN-2的TSO是1112。当这两个事务提交完之后,在DN的Binlog里边就会是偏序所示的形态,在每一个对应DN的Merge Source内部会做一级排序,一级排序完之后会保证每个Merge Source输出的都是局部有序的序列,然后会再做全局的排序,全局排序完之后就会保证整个事务是全局有序的,按照TSO来做一个全局排序,并且会做事务的合并,接着会把这些分布式事务里边的XA START等等一些事件就给它变掉,然后呈现出mysql单机事务的形态,Begin、Commit,然后TSO,大概是这样的流程。
再看一下右边的文字,进一步地来看一下为什么会有一级排序。之所以会有一级排序,是因为在原始的DN里边,我们的事务的commit TSO是天然的,并不是有序的,我们内部做了一个名词定义,叫事务空洞,这个空洞就是说对于2PC的事务。如果在它的prepare commit之间穿插了其它事务的prepare和commit,我们就认为这些事务之间是存在空洞的。
因为多个分布式事务是并发提交的,所以说空洞这种现象是不可避免的。有几个小例子,比如P1 P2 C1 C2是有空洞的,之间有其它事务的PC,但P1 C1 P2 C2是没有空洞的,P和C之间没有穿插其它事务的操作,我们就认为没有空洞。没有空洞的这种TSO是天然有序的,有空洞的TSO就可能是乱序的,针对这种场景,也要做一级排序。二级排序是非常经典的一个排序算法,就是多路归并,依次从各个节点去取事务,然后做排序,完成事务的合并输出。
接下来再看一下虚拟TSO。虚拟TSO就是我们在完成排序之后,输出到全局Binlog的内容会和原生的mySQL有一些差异,原生的mySQL有begin 、commit,紧接着下边就是一个begin、commit。而全局Binlog会在每个commit的后边会有一个cts,就是commit Transaction Snapshot,就是一串数字。
这个是我们CDC在基于刚刚讲过的64位的原始的TSO基础上生成的虚拟TSO。它的格式,高19位就是原始的commit TSO,中间的19位是分布式事务的事务ID,是全局唯一的,然后10位是事务的sequence,下边有一个DN的哈希码,标明事务从属的DN是哪个。有虚拟TSO是因为PolarDB-X在进行事务提交的时候会有很多的优化,比如如果发现事务里边只在一个DN上面有操作,那它就优化成了一阶段提交,如果没有开启失误,那就是auto commit单机提交。在这种场景下,在Binlog里面的TSO是并没有记录TSO的,所以会在整个排序的过程中,会做虚拟的构造来保证这样的全局有序。这里边的东西比较复杂,一些细节就不展开讲解。大家如果对一些细节感兴趣,可以看一下知乎的文章PolarDB-X全局Binlog解读理论篇,里面会有更详细的介绍。大家知道全局Binlog会有一个虚拟TSO,并且会通过ROWS_query event的形态来记录到这个Binlog文件里面去。
以上是通过三个ppt讲了一下全局Binlog的一些原理性的东西,以及它是怎么来生成的,接下来就通过具体的例子来看一下整个合并的过程是什么样的。
已经提前准备好了操作的demo,大家可以看到屏幕上有四个客户端,左上角是CN的命令行,左上角是一个DN,左下角是另外一个DN的控制台,整个演示就是来看一下去执行事务之后,具体是怎么来完成分布式的合并的。
先看一下左上角PolarDB-X目前有几个库,有一个bank库,bank下边有一个account的表,表里边预置了几条记录。然后看一下当前的全局binlog的位点生产到了什么位置。
这是一个全局Binlog的文件,它的位点是15121014,
再来看目前DN1的位点1225336,这个是另外一个DN的位点。可以看到当前全局Binlog的位点,这个是这两个DN的位点。
现在是准备了脚本,开启一个分布式事务,事务里边操作也比较简单,有一个账号余2加1,另外一个账号余2减1,然后进行commit,再看一下事务commit完之后在DN里边Binlog的样子。
先看DN1里边有一堆提交,这里有一个字眼可以看CBC heartbeat,这个后边会讲,会有两百毫秒一次的心跳,所以这个日志里边也会有一些比较多心跳的日志。可以看到这个是刚才提交的事务在DN1里边保存的形态,可以看一下它的table ID,这个表的名字是带后缀的。因为之前的表是个逻辑表,逻辑表在每个DN上边每个分片会对应一个物理表,而这个是一张物理表,带一个随机后缀。然后这个是update row event,并且也可以看到XA END、XA PREPARE等分布式事务相关的一些事件。
再看一下DN2,其实DN2跟DN1是大同小异的。这个时间有些长了,所以里边的心跳会比较多。这个里边account的随机后缀和刚才那块是一样的,只不过库不一样,刚才是bank_000001,这个是000002。可以看到这个事务正好执行了两次操作,在两个DN里边分别有对应的物理的Binlog。
这时候看一下在全局Binlog里边的事务是什么样的。可以看到在全局Binlog里边,它的表现形态会非常简单,因为别的里边有很多杂七杂八的一些event,到了全局Binlog,正好是一个事务begin、 commit。有两次提交,有两次更新,后边有一个刚刚讲过的CTS。
可以看一下这个account,account已经变成了一个逻辑表的名字,因为我们在PolarDB-X这边创建的就是一个名字为account的。它在物理Binlog里面是bank_000002.account_y8db这样的名字,这块是因为我们在CDC Binlog生产过程中会做一些数据整形的操作,把它去翻译成逻辑表,这样下游才可以透明的去消费这些事件。这个demo已经演示完毕,帮助大家更方便直观地去理解一下刚才讲的那些原理性的内容。
下边紧接着又是一个demo,在讲全局Binlog第一篇ppt的时候,里边说了可以保证事务的完整性,刚才那个视频里边可以看到事务的确已经合并完了。这个视频是一个转账测试的场景,更深入地看一下我们是怎么来保证了事务的强一致。
还是四个窗口,左上角是PolarDB-X的CN节点,右上角是一个单机MySQL买,左下角跟右下角是一会执行脚本的两个准备好的窗口。
开始还是先看一下当前PolarDB-X里边是一个空库的状态。
这边单击MySQL,除了它的系统表没有其它任何的业务库。
然后来看一下全局Binlog当前的位点,
接下来做的事情是会在单机MySQL上边来执行Change Master消费PolarDB-X的全局。
这个链路已经创建好了,我们看一下链路的状态。目前是已经读完了Binlog所有的状态,正在等待更多的更新。
在PolarDB-X这边创建一个数据库,看一下在单机MySQL这边是否已经同步过来了,验证一下这个链路的连通线。
这边已经也过来了库名是xxx的库。
接下来要开启一个脚本,这个脚本会做一些数据准备的操作
data prepare success!
然后去PolarDB-X这边的窗口来看一下准备的什么内容。
这边新增了一个bank库,bank库里边新增了accounts的表。然后看一下单机MySQL是不是正常同步过来了。
accounts表已经存在了,看一下accounts表里边的内容是什么,
里边插入了100条记录,每一条记录的余额是10万。同样的,去单机MySQL这边来看一下是不是已经同步过来了,
这边的数据也已经同步过来了,一共是100条。
接下来看一下它的总余额。一千万,单机MySQL这边也看一下。
不出意外的也是一千万。
下一步就要准备开启转账测试。转账测试里边的脚本也是启动了多个线程,然后不断地在随机的账户之间进行账户的转入、转出的操作。
转账测试已经开始,可以看到它的余额已经发生了变化,它一直在变。
单机MySQL这边余额的变化也已经同步过来了。
接下来的程序是一个check data的脚本,会去对单机MySQL的总余额进行验证,不断地去查询它的总余额。
程序已经起来,可以看到它在不断地输出,它的余额一直是一千万。可以看到这个就是强一致的一个表现,因为这边事务既能保证完整性又能保证全局有序,所以说,在一些兼容的场景或者说大家在用PolarDB-X的时候,如果下游对于数据的强一致要求比较高,基于全局Binlog来做是一个非常好的选择。因为现在目前市面上大部分的数据库并没有这样的能力,大部分都是把数据打散,在下游消费的时候,想保证这样的一种强一致的能力是做不到的。
下边就再看一下PolarDB-X这边余额是一千万,
这边正在不断地去消费,因为我这个配置比较低,源端的转账测试已经停掉了,但是因为配置比较低,它还要稍微去追一下数据。最终等数据追完之后,我们会再校验一下云端和目标端的checkson。然后更进一步地来验证一下强一致,因为XOR是一样的,是否每条数据都一样,通过checksum的脚本来看一下,checksum只要有一条数据不一样,算出来的结果肯定是不一致的,再进一步地来看一下是不是所有的内容都是一模一样。再看一下右下角,余额一直都是一千万,并没有出现非一千万的场景,这时候开始计算一下checksum。
源端的checksum是4094。看一下目标端是不是已经把所有的数据都已经消费完了,顺便可以看一下右边还是在持续地输出
这时候目标端和源端的是完全一致的。
这个是拆分键变更的场景,我们用任何一个分布式数据库,都会有分区,数据分布就会分布到各个分区。但是它分布完之后,会有一个分区变更,比如有一条数据之前在分区一,但是对这条数据做了变更之后,它的分区件发生了变化,这时候这条数据就会从分区一,比如说变到了分区二。它从分区一变到分区二,把这个行为定义为数据漂移。数据漂移会导致什么样的问题?可以看一下这个图,有一条PK=1的数据,然后发生了一次拆分键变更,它的拆分键最开始是X,然后变成了Y,变成Y之后,这条数据就从了DN1变到了DN2,PK为1,拆分键变成了Y。我们在进行这个操作的时候就会在DN1里边直接进行delete的操作,然后在DN2里边执行一个insert,最终完成了数据漂移的过程。
图中上面显示的是,没有任何机制保证delete和insert顺序的场景。如果delete在insert前边是没有问题的,但是如果恰好insert变到了delete的前边,那这条数据我们从微端去查的时候就没有了,这个肯定是大家不愿意看到的。刚刚描述的这个场景,其实在我们用传统的这种分库分表中间件作为选型来做分布式分库分表的时候,是一个非常常见的行为。比如说举一个业务的场景,在打车软件里边,用户下了一个单,它的拆分键是司机的ID,这个订单分配给了司机,但是后来这个订单又发生了一次改派,那订单所属的司机就发生了变化,在这个业务场景里边,拆分键变更,因为订单改派发生了产品的变更,这个订单最终同步到比如说我们这个库是索引查询库,这时候司机去查这个订单,发现根本就查不到,因为这条数据在传统的分库分表查询下边,没有办法保证delete和insert的顺序,这样用户下完单之后司机收不到,用户的投诉就过来了。
再看一下图中下半部分,下边是演示了PolarDB-X内核是有Traceid这样的设计,Traceid是一个有序自增的序列号,它的组成是右上角红框里边来展示的样子。有了Traceid,我们就可以规避刚刚描述的场景。通过Traceid实现事务内event的排序,这样就能保证顺序。总结一下,就是靠TSO来实现事务之间的排序,靠Traceid来保证事务内不同操作之间的顺序,从而来保证事务内的有序性,最终实现了数据的一致性,图中右边代码部分就是Traceid的形态。
接下来继续看一个Demo来看一下Traceid是怎么工作的。
还是熟悉的屏幕,四个框,左上角是一个PolarDB-X控制台,右上角是一个DN,左下角又是一个DN,我们直接开始。按照惯例先看一下库,现在创建一个数据库,这个数据库名字就叫Traceid,
然后再创建一张表,这个表名字叫T1,有两个字段a和b,按照哈希的方式,以a作为拆分键来创建一张表。
表已经创建好了。
看一下这张表的拓扑。
Show topology from是PolarDB-X私有的语法。可以看一下这张名字为T1的表,它有16个分区,然后分别分布到了16个物理库。GROUP就是分区名字,每一个GROUP会对应一个DN里面物理的库,因为没有分表,所以在每个物理库上边就有一个物理表。看完它的拓扑之后,再来看DN里边正好是有8个库,1 3 5就是奇数的,
对应了这边GROUP奇数的1 3 5 7 9 11 13 15这几个库。再来看另外一个DN,这个DN里边也是有八个库偶数,
还有一个single库。接下来往T1表里边插十条数据,
数据已经插入,来看一下数据。10条数据,其中a是拆分键,我们通过执行计划来看一下,因为选中了两条跨DN的数据,就是两条分布到不同的DN的数据,可以看到a=1的这条数据,看一下它的执行计划。
它是分布在000001这个物理库分区下边,然后再看一下2这条数据,
它是分布在000002这个分区下边。看一下这两条数据,一个是在000001,一个是在000002,也去对应的DN上边去看一下是不是这样的,我们看一下DN的Traceid000001这个库下边有没有a=1的这条数据。
再看一下DN2有没有a=2的这条数据,
可以看到数据都在,接下来就要进行拆分键变更的操作。预先看一下各个DN的Binlog,因为一会操作完之后还要看Binlog里面的内容。看完它的Binlog之后,这边实现了拆分键的变更,把a=1的这条数据变成了a=2。
先不往下看,可以停一下,当a=1的数据变成2之后,它这条数据会从DN1漂移到DN2,
也就是说这条数据在这个库里边去查应该就没有了。
然后在这边去查,应该会有两条a=2的数据。接下来继续看是不是这样的行为。可以看到DN1里边已经没有数据,DN2里边多了一条a=2的数据,符合预期。
接下来是要看DN1的Binlog里面的内容。我们看一下拆分键变更这一条SQL,因为这边只执行一条SQL,所以看一下它在DN里面的Binlog是什么样的。耐心等待一下,因为里面有心跳,往上翻一下。
可以看到这边有一个delete,因为这条数据要漂移,就要从DN里边把数据删掉,我们从DN的Binlog里面也看到了,的确是有delete这样的操作,
然后rows_query这个常规的单机mysql是用来记录事务的查询日志的,它在数据复制的时候没有什么太多的影响,通过rows_query这个事件,已经把Traceid记录进来了。
可以看到这个Traceid有一个编号为2。大家记一下2这个内容,我们这个delete操作的Traceid的序号是2。
接下来看DN2里边Binlog的情况,可以看到这边Write_rows_v1,对应的是insert。它的Traceid是3。这个也是符合预期的,因为delete肯定是要在前面的,insert肯定是要在后边的。我们在CDC内部程序处理的时候,就会按照2和3进行排序,然后在事务里边来保证这样的顺序,最后还要在全局Binlog里边再看一眼,
可以看到这边是一个单机事务。这个里边记录的Traceid 2 3是取自DN的两个Binlog里边的,然后就做了一个排序,和原生MySQL不一样的就是update的操作,在我们的全局Binlog里边,并不是update的event,因为这个是分布式的场景下规避不了的问题,所以说我们会在一个事务里面把它拆分成delete和insert。这个视频就结束了。
讲完事务就来讲一下DDL,对于分布式数据库来说,DDL变更其实是一个非常复杂的操作,因为它有非常多的节点,我们从控制台去提交完一个DDL之后,后台我们内核内部在执行用户提交的DDL的时候是非常复杂的,因为它有非常多的节点,我们在每个节点之间执行的是一个一步的操作,因为分布式场景没有办法做到原子性,这是一方面,另外一方面,对于全局Binlog CDC这边来说,其实也更复杂,因为在DDL变更过程中,整个PolarDB-X所有的DDL都是online ,online的意思就是在执行DDL变更过程中,同时不能阻塞DMR的操作,那这样,我们在各个方面上去做变更的时候,DMR的流量还在不断地来进行操作,这时候就会生成多版本的数据。
比如说这个图里边提交了DDL的MySQL,分片分别是在TSO100、TSO200、TSO300的时间点完成的,它们在不同时间点完成了对应分片上的DDL变更。如果在整个100到300的过程中,DMR的流量还在持续不断的进来,这时候在不同的分片上边就会有不同的数据。比如我们在这个时刻,分片1的版本的数据是V2,比如说操作是加列,V2的数据已经有新列的数据了,但同样的在分片2上面,它还没有执行加列这个操作,那它里边的数据就是唯一的版本。在CDC这边汇总完所有DN的数据之后,再往下游输出的时候,如果不做处理,它会产生这样的形态,那就是说在100和300之间会同时穿插着V1和V2这样两个版本的数据,这样会有问题。如果下游挂的单机MySQL的Schema是强校验的,但它的schema还没有发生变更,如果我收到了多列的数据,整个同步链路就报错了。所以我们就要解决这样的问题。然后我们看一下下面这张图。
有一个Not Online的方案,比如说我们去阻塞DMR,让它规避在变更过程中有流量进来,肯定是可以实现的,但是这种方案肯定是不可取,因为站在现在这个角度去阻塞DMR执行肯定是不现实的,那我们选择什么样的方案?选择了整形的方案,这个整形方案的原理,如果大家有熟悉Flink,就应该会比较熟悉,就是我们会维护schema版本快照的历史,也就是我们在CDC的内部,会对逻辑表维护一套快照,然后对每一个DN上边所有的物理表也维护一份快照,维护两份快照,在消费各个DN的Binlog的时候,在这个点我拿到了一个DN的数据,我就会去找当前在这个时间线上逻辑schema的形态是什么样的,对应的物理schema的Binlog又是什么样的,然后它们之间的schema如果是一致的,可以放心地去对下游输出。如果它们之间的schema是不一样的,那我就要依照逻辑schema为依据来进行数据的整形。说起来比较绕口,力求给大家讲明白,先看一下文字的描述,
就是时刻的Schema结构称为一个版本,在DDL变更前的版本叫Vx定义成Vx,DDL操作后的版本叫Vy,它有两种形态,一个叫Vy兼容Vx的DDL类型,比如说加列。加列,因为它是新增一个,新增列如果插入老版本的数据,一般情况下是不会有问题的。Vx兼容Vy的场景指的是变更后和变更前是不兼容的,比如删列这种场景。
上面这张图就以非常常见的加列的场景来看。加列的场景就是用户提交了一个加列的逻辑DDL,按照分布式的特性,它会在各个分片上面分别在不同的时间点执行,执行完之后,内部这边设计了一个叫逻辑DDL打标的操作,这样的设计就是等所有的DDL执行完之后,会做一个打标,这个打标它本质上就是一个分布式事务,打标的目的是让这个事务产生TSO,我们会依据这个逻辑打标来更新CDC里边的逻辑schema的结构。
整形的意思就是说无法规避在同一个时刻会出现双版本的数据,但是可以去对数据进行一个整形。比如说,在某一时刻,既收到了V1版本的数据,也收到了V2版本的数据,怎么去整形?比如在提交加列操作这个DDL之前这个表结构有abc三列,那CDC里边维护的逻辑表的原数据就有三列,提交加D这个列之后,在这个物理DDL变更的时候,它是什么形态,比如说在这个分片上面,执行完了DDL的变更,它的版本就从V1变成了V2这样的版本,就是说在这个物理DDL执行完之后,后边再收到的这个分片上面的物理Binlog里边就已经有了D这个列。并且这个物理DDL,它是一个autotable add colum,CDC下边收到这个物理DDL变更之后,会把物理schema做一个实时更新。这个物理DDL同步给CDC之后,这时候CDC逻辑schema有abc,然后物理对应于这个分片的物理schema已经变成了abcd,然后接下来会收到包含abcd这个数据的这样的event,这时候我们就会触发整形,因为整形的这个操作会完全以逻辑schema为基准,这时候记录的逻辑schema还是abc,所以说收到加列操作之后,会把这个里边d的操作,以逻辑schema为基准,把d的操作干掉,从而来保证在双版本数据期间,数据通过整形能始终保证它是和逻辑schema保持一致的。什么时候整形操作会终止?是在CN内核内部做完达标之后,CDC下游就会收到打标操作,打标操作会包含新增的这个d列,那逻辑schema就变成了abcd,同时,各个分片的物理schema这时候也是abcd,逻辑的schema和物理schema是一致的,这时候后面的数据也都是V2,就不会触发数据整形的操作,从而来保证在进行各种复杂的DDL变更的过程中,全局Binlog里边通过整形操作来保证DMR event里面的数据和schema是始终保持一致的,然后来实现下游消费的时候能去规避掉内部的各种细节,下游消费是完全无感知的。这个比较绕口,大家想了解细节,可以看知乎文章——PolarDB-X全局Binlog解读之DDL,里边会有更详细的一些描述。总之就是给大家演示一下这个特性里边的一些处理还是比较复杂的,其实这个和分库分表的场景去比,也是带来了非常大的收益。有过分库分表使用经验的同学应该知道做DDL变更的时候是一个非常头疼的事情。各种操作会非常繁琐,有的时候还要去做上下游的各种数据的验证,是一个非常耗时耗力的形态。但是如果用PolarDB-X,对于DDL变更来说,就是so easy。
这个简单来看一下,它就是DDL变更的一个总体操作流程,这块就不细讲了。下边继续看demo,这个demo就是来把上边啰嗦了一堆的简述通过具体的演示来给大家呈现一下。
还是四个窗口,左上角是一个CN,右上角是PolarDB-X的源数据库,左下角是另外一个DN。
我们先看一个库,这个库叫CDC,首尾有下划线,这个是CDC在PolarDB-X内部的内置的系统库,只有高权限的账户才可以看到。这里边有三张系统表一个叫__cdc_ddl_record__,一个叫heartbeat,一个叫instruction。重点来看__cdc_ddl_record__,这个是和CDC刚才讲的DDL打标相关的一个系统表。
和DDL相关的也有两张表。一个叫_logic_meta_history,一个叫_phy_ddl_history,logic就对应刚才我们讲的逻辑schema的历史快照的信息表,physic顾名思义就是每一个分片上边物理schema的快照历史表。现在这三张表,一个是在PolarDB-X里边的系统表,然后是在PolarDB-X源数据库下边有两张表,一共是三张表,来保证刚才说的DDL变更当中的整形的操作。建一个数据库,叫ddltest,再去创建一张表,
表已经创建好。先看一下__cdc_ddl_record__系统表里边的内容是什么样的。
可以看到里边已经有了一条记录,叫create database ddl,那是因为我刚刚创建了一个ddl的库,每一个ddl操作都会有一个打标数据在里边,大家可以看一下,这里面有一条数据,META_INFO保存了这个库的所有的拓扑信息,这个是建表,再打标。直观的来感受一下:
这个表比较多,拓扑比较多,因为物理表比较多,我们分区分得多。
接下来再看一下右上角两张表里的内容。
可以看到这边也有一条记录CREATE_ TABLE和database,内容其实和打标的数据是非常类似的。再来看一下物理表,这个里边limit了一下,因为物理表非常多,分片非常多,
这个是物理表的建表SQL,我们会维护到物理的DDL history表里面去。可以看一下这个是DN1,有一个表,上面DN2有对应的建表操作。
并且里面记录了一个tso,刚才说为什么会打标,打标就是借用了一下分布式事务这样的东西,通过打标事务,我们会取得tso,因为tso是一个逻辑时钟,就可以以它为基准在时间线上进行逻辑schema和物理schema的对比。
接下来去做一些操作,看到T1表里面目前是没有数据的,给它不断的插入数据,刚才的脚本是启动了一个程序,这个程序不断地往T1表里边插数据,目的是构造一个流量,刚才说DDL是online的,整形操作也是online的。构造这个流量的目的,就是一会来触发整形的场景。
可以看到里边已经插入了一部分数据,其实目前正在源源不断地在往里边进行各种各样的插入操作,那接下来要做add column c这样的ddl变更,
已经变更完成了,刚才大家可以看到它的时间是4.95秒,真正的生产环境不会这么慢,因为我这个是测试实例,并且刚才建的那个逻辑表分区非常多,所以说会看起来比较慢一些,这时候已经把流量停掉了, 因为DDL变更已经操作完了。刚才在变更的过程中是有DMR流量的,这时候看一下,我们找了一个DN,左下角刚刚正在演示的是DN的节点,去mysql的Binlog里面看一下。
在看之前,得先去看一下右上角这块是刚才执行的SQL语句,
它在打标之后,在这个里边的TSO的情况,我们去拿到它的TSO,这是一个虚拟的TSO,从虚拟的TSO里边,我们要截取到高19位,因为这个是原始的TSO,基于这个原始的TSO去物理Binlog里边看一下,因为物理Binlog里面会通过GCN event来保存这个,通过mysql Binlog工具来解析mysql的Binlog,去把它输出到3.log里边,接下来看3.log通过GCN,就是通过这个TSO来找到这个打标。我们看到有这样的操作,对应的TSO,
这个对应的就是打标,接下来演示的是在这个逻辑DDL打标之前物理的表里面的数据,因为加了新的列C列,所以肯定会有,并且可以看看C的默认值是0,在打标之前,各个物理DDL执行完之后,它所有的物理分片上就已经有C这个列了,同时,刚才还有流量进来。在打标的这个之前肯定会有一些物理表,它的里边已经有C这一列的数据了,找到了一条数据,这条数据已经有C这条内容了,它的内容是0。
把它的id记一下,一会我们会基于这个id来做一个校验。接下来再去全局Binlog里边看一眼。这时候我登到了CDC的节点去看一下全局Binlog里面的内容,输出到1.log。刚才我们提了uu I,然后通过id去全局Binlog里面看一下,去搜索一下,
以发现已经找到了这条数据。可以看到这条数据只有123,
但是在左下角是1234,刚才讲的整形的操作已经生效了。这样我们就保证了schema和实际的数据之间的一致性。群里面有同学说内容不太好消化,今天讲的内容会比较偏原理,不同的课程是针对不同的同学,因为有一些同学在之前那些开源的课程里边说想去更多地了解一下原理性的内容,所以今天准备的内容会更偏原理一些。
DDL就讲完了。
接下来讲节CDC的性能的情况。
有一个指标叫Event Per Second,Event就是刚才我们看得很多demo里面Binlog里的event,就是每秒的event写入的情况。对于EPS,我截的图是Sysbench来跑出来的,CN 、DN、CDC的配置就是片子里边展示的这样。EPS在高点可以达到五十万的水平,并且EPS是不包含begin和commit的,就是Binlog里面有begin 、commit是不包含这两个的。五十万是什么样的水平,对于大部分的这种场景来说,五十万对应的流量是130兆,基本上可以支撑中型的一些互联网公司的流量是绰绰有余的,延迟的情况在800毫秒到4秒之间的水平,中间会有一些抖动。原理文章是有介绍的,原理文章在我们的知乎文章里面都可以去搜,搜全局Binlog就可以,里边会有更详细的介绍。如果对内核更有兴趣的同学可以去我们的开源github上搭一个实例出来,然后按照今天讲的内容以及结合这个开源文章做一些实操,来更深入地去了解一下,其实我讲这些内容的目的是体现一下我们整个全局Binlog是经过了一系列的各种设计,一系列复杂的操作来给用户达到了这样非常透明的分布式的使用体验。
整形过程中不会导致变更的数据丢失,因为整形是以逻辑schema为基准来做的整形,所以说整形的这些数据其实都是中间数据,都是没有用的,因为整形的数据都是在DDL变更过程中产生的。最终打标完成之后才会返回给客户说这个DDL成功了。这时候用户插入的才是它正常的数据。因为在整形过程中,逻辑的那个表还没有生效,用户插入的字段就会报错,所以整形的数据不会导致变更,会保证数据一致。