CDC变更数据捕获实施模式


在本文中,我想讨论实现 CDC 的几种不同方法,以及一些关键应用程序是什么以及 CDC 如何融入现代数据流架构的大局。

有几种从数据库中提取变更事件的方法,每一种都有自己的优点和缺点。因此,让我们仔细看看每种方法。  

基于查询的CDC
首先,是基于查询的CDC。在这种方法中,一个轮询循环在一个区间内运行,并识别自其最后一次执行以来发生变化的任何记录。这在概念上是相当简单的,但也有一些注意事项。最重要的是,在尽可能频繁地运行该循环--以确保高度的数据新鲜度--但又不能过于频繁地运行它,以避免轮询对数据库造成过载,这两者之间存在着固有的冲突。

而且,无论你多频繁地轮询变化的数据,都不能保证在两次循环运行之间没有中间的数据变化被错过。在最极端的情况下,如果一个记录被创建和删除的速度足够快,它可能根本就不会被捕获。另一个缺点是,轮询循环不能捕捉被删除的事件,因为这些记录会消失。此外,这种方法需要开发人员在设计数据模型时进行协作,因为每个表都需要提供记录最后一次被修改的时间信息,例如,以 "UPDATED_AT "列的形式。一个优点是,基于轮询的CDC实现起来相当简单,而且它不需要数据库本身的任何特定功能。例如,它适用于大量的数据库。


基于触发器的CDC
另一种实施方法是基于触发器的CDC。对于每个要捕获的表,都要为 INSERT、UPDATE 和 DELETE 事件安装触发器。这些触发器通常会将记录复制到某种暂存表中,然后像上面一样通过轮询从那里提取记录。这种方法的优点是不需要数据模型中的特殊列,也不会错过任何事件。另外,可以捕获DELETE事件,因为触发器是作为编写事务本身的一部分来执行的。这也是最大的缺点:在写入性能上会有不可忽视的开销,而DBA(数据库管理员)在向数据库安装大量的触发器时,往往会持怀疑态度。

基于日志的CDC
最后,是基于日志的CDC,由Debezium和类似的工具实现。在这种方法中,变化事件是从数据库的交易日志中异步提取的,比如MySQL的binlog,Postgres的WAL,或者Oracle的重做日志。事务日志是数据库的 "真理之源":每个事务都会向它追加事件,以便在故障情况下进行恢复,并进行复制。从这个角度看,基于日志的CDC工具就像另一个复制客户端,因为它接收主数据库应用的所有变化。从交易日志中提取变化意味着保证所有的事件都能被检索到(包括DELETE);同时,对于应用程序的数据模型没有任何限制或要求。基于推送的通知接口,如Postgres的逻辑解码机制,允许低开销、低延迟的CDC,在数据库上没有任何相关的开销,而且延迟在毫秒级的范围。

基于日志的CDC在部署上可能有些复杂。例如,数据库可能需要重新配置以启用它。另外,从数据库的日志中检索变化通知没有标准化的接口;不同供应商的API和事件格式不同,在某些情况下甚至数据库版本也不同。这也意味着,如果没有针对特定数据库的Debezium或其他基于日志的CDC连接器,你需要探索其中一种替代方法。也就是说,基于日志的 CDC 通常是从数据库中检索变化事件的最强大的方法,如果有的话,它应该是首选。

变更事件里有什么?
现在,一个数据变更事件是什么样子的?在Debezium的案例中,事件的有效载荷结构是这样的。

{

   “before”: {

       “id”: 42,

       “first_name”: “Bob”,

       “last_name”: “Kruger”,

       “email”: “bob@example.com”

   },

   “after”: {

       “id”: 42,

       “first_name”: “Bob”,

       “last_name”: “Kruger”,

       “email”: “bob.kruger@example.com”

   },

   “source”: {

       “version”: “2.1.2.Final”,

       “connector”: “postgresql”,

       “name”: “ECom_Prod”,

       “ts_ms”: 1676301790192,

       “snapshot”: false,

       “db”: “ecom”,

       “sequence”: “[\”17076282\”,\”17076291\”]”,

       “schema”: “public”,

       “table”: “customers”,

       “txId”: 618,

       “lsn”: 84982171,

       “xmin”: null

   },

   “op”: “u”,

   “ts_ms”: 1676301790253

}


正如你所看到的,每个变化事件有三个部分。

  1. Before:在更新或删除事件中,受影响的数据库行的旧状态;之前块的结构类似于该事件来源的表的结构,在这种情况下,一些电子商务应用程序的 "客户 "表
  2. After:在更新或插入事件中,行的新状态;同样,它的结构是源表的结构;在上面的例子中,"email "列的值已经改变。
  3. 元数据:元数据,如操作的类型("op"),变化的时间戳("ts_ms"),以及关于源数据库和表的额外信息,交易ID,连接器名称,版本,等等。

在过去的几年里,Debezium变化事件格式已经确立了自己作为一个事实上的标准。与Debezium兼容的连接器不仅由项目本身提供,也由其他数据库供应商提供,如ScyllaDB和Yugabyte,它们将Debezium连接器框架和事件格式作为其自身CDC连接器的基础。另一个例子是谷歌,它最近刚刚宣布为他们的Cloud Spanner数据库提供一个基于Debezium的CDC连接器。

当传播变化事件到消费者时,确保正确的排序语义是非常重要的。虽然通常不需要全局排序(例如,在所有采购订单或所有客户记录中),但与同一源行有关的事件的正确排序是至关重要的。否则,举例来说,如果一个消费者以相反的顺序收到同一记录的两个更新事件,那么它最终将得到该记录的不正确表示。因此,当使用流行的数据流平台(如Apache Kafka)作为向消费者传播变更事件的传输层时,记录的主键通常被用作变更事件的分区键。这样,所有与同一源记录有关的事件将被写入Kafka主题的同一分区,确保它们以完全相同的顺序到达。

使用案例
在讨论了实现 CDC 的不同方法以及变更事件的常见形式之后,现在让我们深入了解该技术的一些常见用例。第一大类用例是复制:将变更事件传播到其他数据存储中,可以满足广泛的查询要求,这些要求通常可以或不应该由操作数据库处理。这包括将数据复制到一个单独的数据库,以便进行离线分析,将数据输入Elasticsearch等全文本搜索系统,更新Snowflake等云数据仓库和Apache Pinot等实时分析数据存储。一个相关的用例是利用变化事件来驱动缓存更新,例如,在用户附近保持数据的读取视图,允许非常短的响应时间。

除了普通的数据复制,CDC还可以帮助解决微服务架构中的一些用例。它可以用来实现outbox模式,促进不同微服务之间的可靠数据交换,避免向服务自己的数据库和Kafka这样的流媒体平台进行不安全的双重写入,因为在没有分布式(XA)事务的情况下,Kafka在故障情况下容易出现不一致的情况。

当从单体系统设计迁移到微服务架构时,绞杀者无花果模式就派上用场了:单体的组件被逐渐提取到等价的微服务中,而整个系统前面的路由组件将传入的请求发送到单体(对于尚未提取的组件所提供的请求)或正确的微服务中。在这种情况下,变化数据捕获可用于从单片机的数据库中捕获变化事件,并将其流向提取的微服务。这样,一个微服务就可以,例如,实现数据的读取视图(例如,显示客户的待定采购订单列表),而该数据的写入仍由单体处理(例如,下一个新的采购订单)。

但它并不局限于此;CDC还可以用来创建审计日志(持久化的变更事件流基本上可以被视为审计日志),驱动对数据的物化视图的增量更新,或者用于更具体的应用,如在SaaS架构中,用于将所需配置状态的变更从控制平面传播到数据平面。

数据流架构中的CDC
虽然变化数据捕获是许多令人兴奋的实时数据用例的强大推动力,但仅靠它本身是不够的。毕竟,向Kafka主题发布变更事件只是达到目的的一种手段,事件需要被传播到其最终目的地。此外,仅仅按原样接受变更事件是不够的:你可能需要过滤它们(例如,排除特定租户的数据),预测它们(例如,排除大的BLOB列),修改它们(例如,规范日期格式或仅预测表行的字段子集),连接它们,分组和聚合它们,等等。

这就是流处理平台的作用:它们可以从流平台(如Kafka或AWS Kinesis)或通过在流处理平台内作为 "本地 "连接器运行来摄取和处理变化事件。然后,经过处理的事件可以再次写入Kafka(或类似)的另一个主题、数据库、数据仓库等。这样,这样的流处理平台允许你实现整个端到端的数据集成管道,从源头(CDC)超过需要下沉的许多处理步骤。

在这种情况下,Apache Flink是一个特别有趣的例子,因为它内置了强大的变化流处理能力。此外,还有Flink CDC框架,它将Debezium集成到Flink生态系统中。

通过Flink DataStream和Table API以及Flink SQL,存在两种实现变化流管道的选择。前者是强大的命令式API,可以通过Java和Python使用,后者是完全的声明式方法,不仅吸引了软件开发人员,也吸引了精通SQL的数据工程师。当涉及到运行和操作基于Flink的数据管道时,用户有一系列的选择,从在他们自己的基础设施上运行一切到完全管理的SaaS产品。在后者的情况下,你只关注你的实际流处理逻辑,不管它是Java/Python还是SQL,然后把处理工作交给SaaS平台执行。这可以大大降低新管道(基于CDC或其他)的成本和上市时间,因为它将你从计算安全、可靠和高效地运行Flink的所有细节中解放出来。

虽然CDC不是新技术,但对实时摄取和为应用程序提供最新鲜的数据的现代需求,使你有机会重新评估不同技术的利弊。使用Debezium等开源工具的基于日志的CDC可以成为现代分布式数据存储(如Apache Pinot)和流处理框架(如Apache Flink)的强大伙伴,无论你是使用开源实现还是像我在Decodable帮助建立的管理服务。无论哪种方式,我们几乎肯定进入了一个时代,CDC成为现代实时数据堆栈的一个强大的、必要的元素。