第一部分:CDC应用场景
l CDC的概念?
CDC是ChangeDataCapture的缩写,是去捕获一个数据的变化情况,刚才提到主题是postgreSQLCDC的一个最佳实践,其实就是说 PG数据库的变化事件的捕捉,理解为数据库内容中产生的一系列行为信息,比如说insert或者delete影响一些数据,它把这些变化的数据去捕获出来。
捕获出来做什么?
第一个场景:不同数据源间的同步
左右两边是一个对比,左边的可能是一个购物网站,这个购物网站它可能有几个功能。
ü 一个就是说我们日常的库存的扣减、库存的存储,我们会存储在传统的一个数据库中。
ü 另一个是有一些用户经常频繁去点击访问的一些页面,放在数据库中的话,很难扛住压力,所以一般会把这部分数据放在缓存中。
ü 第三部分因为是一个电商网站,所以说它肯定会有去搜索商品的一个功能。
如果我们要用传统的数据库去做搜索,是很难的,它只能支持一个前缀模糊匹配,所以对于一些前后都模糊的一些情况,它是做不到快速响应查询的,那么这种场景下我们一般都会用到一些搜索引擎,比如ES,我们会把商品的一些信息存在 ES里面,然后在ES中进行一个快速的匹配和查找,这是我们一个传统的架构坐标。
这个架构到底有哪些痛点?
数据是需要去维护多份的,比如说需要去维护一份缓存的,维护一份数据库的,还要去再维护一个搜索引擎的,这样其实对于应用来说,就会有一个很大的改造难度和一个工作量。
如果说我们要用到一个刚才说到的数据库变化事件的捕获的一个功能的话,那就会变成一个什么样的效果?
比如说右边这个图,数据首先是写入你的PG的数据库,然后通过变化事件的一个捕获变更,就把数据库内变化的一些情况解码成一个时间流,然后时间流的再去你的搜索引擎和缓存中进行一个消费,这样其实很大程度减轻了我们应用需要写多份数据的情况,可以减少开发的进度和维护的成本,这是第一个场景。
第二个场景:海量数据存储对于传统数仓情况
现在是一个大数据的时代,有很多海量的数据是需要去存储的,但是对于传统的一个数仓是一个什么样的情况?
一般都是来说是将前一天的数据,通过一些ETL的程序跑出程序,然后再同步到数仓中,这样带来一个很大的问题,就是时效性,另外还有就是我们需要去定制化的开发一些ETL程序的工作。
如果用到 CDC,就会方便和快捷很多,比如说先写入数据库,就是传统数据库中,然后再通过数据库捕获这些变化的事件,最后再到我们的数仓中进行一个消费。
这样一是增强了时效性,另外也简化了中间做ETL动作,这是第二个场景。
第三个场景:微服务间的数据同步
从传统的一个集中式的服务,慢慢向一个微服务的方向去演进,为什么?
还是刚才购物商城的例子,原本可能商品用户订单大家都放在一个数据库上跑着,这个时候会有哪些问题?
Ø 第一个,如果说当用户量上涨很大的情况下,就会导致性能会出现瓶颈,并发不足,比如说我表太大了,然后索引也很大,就导致性能跟不上。 因为垂直向上的扩容能力是有限的,毕竟物理服务器的一个CPU数量,还有内存的大小是有上限的,所以说会达到上限的天花板,导致你没法再向上扩展,这是第一点。
Ø 第二点是在应用开发的一个过程中,也会有很多问题,比如你在商品应用做了一个发布,这个时候大家应用都在一起,此时其实需要你所有的都要去进行一个升级。 每做一个发布,所有应用都要去做升级,商品发布对于用户来说,其实是没有强相关的,照理来说它不用去单独升级。
Ø 第三点开发的效率也会降低,因为耦合很严重的情况下,有可能大家做的内容是有交叉的,所以说这部分也是需要单独的人去维护的。
所以说开始逐渐向微服务演变的情况,就是我们去按照一些模块去拆分。
其好处是,一个是可以支持更大的用户量和访问,另一个好处是每个团队其实只需要负责自己这个模块,中间就不会有一个互相耦合的情况。
但是做了一个拆分之后,又会出现一个新的问题:
第一个是因为变成了一个分布式服务的情况,这个时候会带来运维成本,第二点是在传统数据库中,对于两个表之间的访问,在同一库中是可以用join来做到的。
比如说一个用户他想查一个商品的明细,如果都在一起,就用ID对这两个表进行一个关联,就可以得到一个数据,但是对于非服务人员,因为数据在不同的一个库中,是没法做同库之间的效应的,那么就要通过我们CDC能力做一个数据同步,这样的话用户能快速的访问到我们商品的一个明细,这就是微服务间的数据同步。
第四个场景:数据库大版本升级
数据库是需要一个一直能在线提供服务的能力,如果说要尽量缩短它不合用的时间,就需要我们做到一个无缝切换的能力,需要有实时同步能力。
实时同步怎么做,其实跟之前一样的道理,就是相当于我们实时去捕获一个数据的变更,然后同步到新的版本的数据库上,然后等数据达到一致的时候再进行一个切留。
描述的过程很简单,但其实中间有很多很复杂需要去注意的关键步骤,但是当你有了 CDC的能力,是能帮助你去做无缝升级,数据库大版本无缝升级的一个利器。
CDC的原理
PG的CDC是怎么提供的?出现原理?
这个其实类似于一个事物的日志,因为数据库它是一个需要保证数据是不会丢的,不可能说写条数据进来,如果数据库挂了,数据就会丢,这就是不符合数据库的一个能力的。所以说第一点是要保证一个数据写入一定是不会丢的。
第一部分,看一个具体的case,例如插一条数据,数据库到底发生了什么?首先一个是shared buffer poor,shared buffer poor是数据库的一个共享缓冲区,相当于我们会分配一块很大的内存,这部分用于缓冲数据,可以极大增强我们访问数据的速度。
第二部分是Clog buffer,指的是记录一些事务日志提交的状态信息,比如说一号事务是commit或者二号事务是Block,记录这些的信息,Clog buffer其实就是记录暂时存储Clog的一个地方。
还有一个Wal buffer。Wal就是刚才提到的事务日志临时放在Wal buffer中,然后在一定的条件下会进行刷盘的1个动作,会把Wal buffer中的数据写入到一个存储中,存储一般就是指传统硬盘数据文件。刚才说到的一个例子,首先是从一个数据块中读一个空号,然后会在内存中将数据写入数据块中。
这时候这个数据块是发生过变化的,这个时候先写一条 Insert的日志,写到日志的buffer里面,当触发commit的时候,它就会把刚才写的日志落盘。
这样如果说数据库发生了崩溃的话,我们是可以通过日志来找回到这条数据的。
那么数据怎么去落盘?数据化会通过一个BG writerde 进程,或者说是通过 check PAD这种进程,定时将脏页刷写到磁盘中,这就是一个事务日志的功能和原理。
l 物理复制的原理
当我们有了日志,它不仅能产生一个备库去读取变化的日志进行一个重放,可以做到高可用的能力。
它的原理其实也很简单,比如说我们有一个主库,主库它会实时产生刚才提到的事务日志,然后事务日志通过wal sender的进程传输给 walreceiver,备库拿到日志,然后再进行还原当初一个最新的数据状态,这样就做到了主备同步的能力。
l 物理复制的局限
日志其实记录的是: 比如说一个事物日志的头,事务日志标识的是往表中插入一条数据,然后这个是实际变化数据块的头,然后是实际写入的一条数据块,它其实有很多个这样的收支来组成一个wal的文件。
默认wal文件是十六兆,它记录的内容是实际的数据块内的变化信息,因为它记录的是数据库块内变化信息,这就会导致它复制的不灵活。当然物理复制也有自己的好处,它不需要等待一个事物提交,可以实时将这些数据块的变化做一个同步,对于 DDL或者大事物这种它都不会遇到复制延迟的问题。
当然它要依赖我们数据库的版本一致,操作系统一致,这样才能保证数据块变化的情况是一致的。
l 逻辑复制
逻辑复制和物理复制的具体区别在哪?
下面是一个例子,比如说这一块是我们有三张表tableA、tableB、tableC,然后对这些表做一些变更,会产生一个刚才提到的事务日志,事物日志再通过 output plugin,output plugin相当于对事务日志做一定的解码.
比如说刚才看到的是物理块变化的情形,其实就很多0101,但是我们就要0101按照一定的方式进行解码,比如默认解码成text coding,可以按照一个Json的方式解码,解码之后会通过publication(就是发布端),比如说我们只发布a表和b表,然后订阅端订阅发布,这个时候就会把订阅到a表和b表的一个数据变化同步到目标端的一个数据库的表中.
然后还有一个功能是replication slot,就是复制槽的一个能力,复制槽是做什么的,比如下图这几个箭头代表了什么?
其实就是当订阅的时候,消费的这段数据,我们会实时通知复制槽,复制槽的话来控制事务日志是否被清理和一个位点的推进。
再举个例子,如果不通知的情况下会怎么办?
将会造成日志累积堆压,就是导致你的wal日志可能会把你的磁盘写满。
如果说不通过复制槽来保证数据不被清理掉的话,会发生一个什么样的现象?
比如说我日志写得很快,因为wal日志是会循环使用的,这个时候我订阅端消费的比较慢,就导致日志没有被消费,但是日志会复用掉,就会导致这个数据丢掉,所以在此串联了一个这样的问题。
所以replication slot就是要解决我们事务日志被清理掉的问题。
l 逻辑解码
刚才讲到逻辑复制的原理,那么它到底是怎么去做解码的?
比如说我们创建一个复制槽,刚才说了我们使用逻辑复制一定要有逻辑复制槽,否则逻辑变化数据可能会丢。
刚才知道那个插件的一个名称,比如用了test decoding,这时候我们去对表插入一行数据,通过命令来捕获复制槽内的数据变化情况,就看到一个begin和一串数号,然后对这个表做一个插入操作,然后是ID是integer类型,值是一,再做一个commit,就发现insert语句被解码出来了,原本直接看wal日志的信息时,是根本看不懂的,不知道他其实是对这个表做了一个插入,插入ID等于1的一个情况。
逻辑解码还有哪些问题?
比如DDL为什么不支持replication slot,我们通过一个例子给大家讲清楚,它的原理到底是什么,为什么它会不支持DDL?
比如说有专业的复制槽test decoding,我们对表做一个out of table的动作,这个时候我们捕捉复制槽的变化,但是发现他其实只有一个begin和commit,它是没有DDL语句的解码的,所以说它不支持 DDL语句解码,但是 DDL像这种同步的能不能做?也能做,后面会提到这种DDL同步是怎么做的。
v 我们看一个例子,先对这个表对于表先做一个replica identity default,这个是做什么呢?是说我们对这个表设置一个默认复制,它默认的话就是走主键,就是说它会记录主键的一个前进项,什么叫前进项?
这个例子中,delete from testl whereid=1,比如说我们把ID=1这行数据要删除掉, ID=1其实我们删除后之前的前进项。那么就捕获一下变更。
我们发现它删除了以后,是一个 no tuple date,因为没有数据,所以不知道删的是哪一行。 因为我们用的是一个无主键表,命令说的是default,这样它是没法去捕获到这种无主键表的一个前进项的,所以说会出现这样的情况,这个时候我们下游拿到了delete,这个语句它其实没法做删除的,因为他不知道删的是ID=1的值。
所以说这个我们为什么逻辑复制的过程中一定要设置主键,就是这个原因。
v 再看设置主键后的一个例子,比如说我们对这个表加一个主键ID,然后这个时候我们去删ID等于2,我们就可以看到它这个时候就把具体的一个ID类型L值列了出来。
如果设置了主键的话,我们就可以看到数据主键的前进项,这样就可以帮助下游去做一些 delete或者是update的动作,update也是同样的一个道理。
v 设置raplica identify full是指我们会保存整个事物日志的一个前进项,全字段的前进项。
我们看看这个例子,就是我们删个ID等于3,这个时候的话会出现看下面这个值它会出现一个Delete: ID[ingeter]:3,然后id1[ingeter]:3,这样就会记录一个删除这一行的前进项。当然也会带来一个问题,为什么不默认都设置这个就好了?因为设置这个的话,它相当于你的事物日志中会记录所有的一个前进项,就导致你的事物日志会变得很大。
所以说这是我们强调要作一个主建,用主键的话首先性能高,因为能复制。
如果没有主键就设置raplica identify full,带来的问题就是数值变大,然后原本的吞吐会下降,另外一点就是逻辑复制的速率也会降低。
v 还有一点,对于序列的话,我们的逻辑解码也是无法支持的。
比如说我们创建一个表,这个表是serial类型,就是一个智增序列的意思,这个serial它还有一个int型,它会自动默认帮你把这个列变成一个自增列,然后并且还会自动创建一个序列。
这个变化的情况,其实这个是本身没有的,插一条数据看一下变化情况,发现是一条insert,对这个表有具体变化的一个值,这是符合预期的。
但是它少了什么、在哪?你不知道。
因为我们没有指定具体的已知序列,因为它是自增的,它 ID是会加一的。但是我们在事物日志中是没有看到一个序列变化的情况的,所以说对于逻辑解码,我们也无法获取一个具体的序列变化。
这些,会让我们很清楚的明白逻辑复制的原理是什么,逻辑复制有哪些局限,为什么它不支持DDL,为什么不支持序列,为什么我们一定要有主键,其实就是这些原因。
l CDC的主流工具
第一个,就是PG自带的一个逻辑复制,这个能力是10.0版本之后才提供的能力。
比如说我们先创建一个普通的表,然后创建一个发布端发布这个表,然后对这个表插一下数据,在订阅端再创建一个相同的表,做一个订阅,订阅的连接串指向我们发布端的地址,发布端名字是什么。
创建完订阅端之后,再去查这个表,就发现数据有了,这样就很方便快捷,就达到了我们需要的数据捕获的能力,这个时候我们实时往里插,它也会一直同步,这就做到了表值的同步能力。
它有哪些优点?
ü 第一点:方便易用;
ü 第二点:具备一个物理复制不具备的能力,支持跨一个数据库的大版本;
ü 第三点:实时同步。
它的缺点是什么?
v 第一点:不支持异构数据源
v 第二点:冲突导致订阅中断
v 第三点:DDL维护需要提前创建,比如说我们刚才如果要对第一个表加一个字段,如果加上字段之后,我们再去插三个字段,这个时候订阅端同步过来是有三个字段的,订阅端的提议表它是写不进去的,之后发生冲突的话,就会导致整个复制后面之后数据都是没法同步的。 所以我们需要提前对t1表去加一个字段,比如加上刚才说到新的字段,然后再对发布端加字段,需要注意顺序的。
第四点:必须是PG的一个10.0及以上的版本,因为10.0版本才默认带了发布和订阅的能力,所以说的试用场景是一个数据库的大版本升级,就很便捷的表决同步,微服务间的数据同步就很方便。
l pglogical
上面说到PostgreSQL逻辑复制只有10.0及以后的版本才有内置的逻辑复制能力,之前怎么办?
9.3版本之前是没有逻辑复制能力的,之前都是通过吹杆触发器的方式来做。这种文化对原端有很大的一个性能影响,因为要去做触发器,然后每变化一次就要触发一次,就会造成源端性能的大幅下降。
但有了逻辑复制的话,就完全就解决了这样的问题,因为是实时解码的日志,其实对源库基本上是不会有影响的。
所以pglogical就是最早去做逻辑复制的能力,它是9.4的PG版本,支持了一个逻辑复制的接口,pglogical就是基于这些逻辑复制定义的一些接口,使我们拿到这些数据,然后再组装成SQL到下游去消费(从订阅端解析日志,然后再去下游消费),是做这样的一个过程。
但如果你有一些开发的能力的话,也可以自己做解码解析,再做订阅。
还有一些像outjson这种,其实他相当于已经帮你做了很多事情,它把发布端解码的能力已经做了,比如说拿到日志,就已经是一个Json的格式了,然后再通过Json进行转码转成SQL。还有一些创意,比如default buffer,它可以直接帮你解码成一个SQL。
再看一下pglogical的使用方式,它也是创建一个发布端的 note,这时我们选择一个发布的副本级,其实设置的所有表都会发布到默认default副本集中,然后我们对 text logical去查一查数据,订阅端也是同样的创建一个节点,设置为noad name,是一个订阅端的节点,再创建订阅端,这时订阅地址就是发布端的地址,然后我们这配置了一个structure:=true,这就相当于我们会同步表结构。
可以注意一下之前在pg自带逻辑复制中的话,其实我们是提前创建了这个表结构,然后在这里是完全没有创建表结构的,我们直接就查这个表,会发现数据已经过来了,而且表结构也同步好了,所以说它有这样的一个能力。
l Pglogical额外的功能
一、支持DDL多个节点同时执行
刚才提到了pglogical逻辑复制,它其实对于ddl是很不友好的,因为我们要提前在源端做,但是它有一个能力叫replicateddlcommand,它可以同时支持DDL在多个节点同时去执行,这就免去了我们需要重复劳动的成本,只需要去执行一下命令,然后带上DDL语句就好,也不会引起复制的中断的问题。
二、序列值的同步
原生的逻辑复制是不会去捕获事件变化的情况的,但是它可以做到序列式同步,就是你可以在你的源端状捕获到你的序列synchronize,然后再将它更新为目标端的一个最新值就好了。
三、支持行条件过滤
有些数据是需要被过滤掉的,而这种同步也是能做到的,所以很灵活。
一、支持四种冲突策略
1) 保留本地数据
2) 应用源端数据
3) 保留最新版本
4) 保留最老版本
五、支持PostgreSQL9.4版本及以上
支持9.4版本及以上,比如说9.4版本、9.5版本的都可以通过 pglogical的能力进行大版本的升级,这样就很大降低了平均迁移的时间。
六、支持指定延迟复制
因为PG的物理复制是支持这个能力的,但是逻辑复制是不支持的,所以说pglogical是支持延迟复制。比如说有延迟备库的情况,这样就方便了在一些误操作时,导致我们数据删除,也可以通过延迟备库去来找回来。
七、支持结构初始化迁移
优点:
u 支持冲突检测,健壮性更好
u 支持行过滤,灵活性更高
u 维护DDL方便
缺点:
n 不支持异构数据源
n 需要主键,不支持replica identity full
n 不支持不同字符集间的数据库逻辑复制
l Debezium
debzium 其实是一个分布式服务,是捕获一个数据库变更到kafka中的,它这个是怎么做的?
它源端是支持多种数据源的,比如MySQL是解析它的并log,得到具体的SQL变化情况,写入到kafka中,然后PG解析wal日志,解析到变化的情况后写入到kafka,然后如何消费呢?
其实kafka的话,它有一些JDBC connector或者ES connector或Infinispon connector,就可以直接写入到你的目标库中。
l 它的优点是什么?
优点:
1) 支持异构数据源
2) 分布式服务健壮性高
3) 直接写入到kafka中,生态好
缺点:
A. 依赖kafka,需要熟悉kafka
B. 无界面化,配置项复杂
C. 组件多,运维成本高
适用场景:
不同数据源间的同步、微服务间的数据同步、实时数仓。
l DTS
DTS是数据传输服务的能力,它提供多种数据源的数据迁移、实时同步、数据订阅的一站式服务。
它的原理分为三步:
第一步:结构迁移;
第二步:数据全量迁移,数据全量迁移的时候,会启动一个增量数据的拉取模块,然后解析,当全量迁移完成之后,再去做一个增量数据的回放。回放到目标库后就完成了数据的实时迁移;
第三步:实时同步。
l DTS
优点:
Ø 支持DDL同步
Ø 支持行过滤,灵活性更高
Ø 支持MySQL、PolarDB、ADB等目标端迁移
Ø 界面化、操作简单
缺点:
Ø 不支持kafka生态
Ø 操控感较差,用户使用相对不透明
适用场景:
数据库大版本升级、微服务间的数据同步、实时数仓。
这4个主流的工具,对于它们的一些适用场景、使用方式、优缺点,已经有了一个大致的对比,以后可能需要真正的去要落地用到我们刚才说的那些能力,比如说大版本升级,或者说实时数仓、用户通过,我们就可以考虑这些CDC的一些工具一些方案,来支撑我们的一个业务的发展和能力。