DBLog-更改数据捕获的通用框架

大家好!我们提供给您阅读本文的翻译,该文章是我们为“高负载建筑师”课程的学生准备的




介绍


跟踪数据更改(Change Data Capture,CDC)使您可以实时接收数据库中已提交的更改,并将其分发给各个使用者[1] [2]。当需要在异构数据仓库之间进行同步时(例如,MySQL和ElasticSearch),CDC变得越来越流行,并且可以替代传统方法(如双重写入和分布式事务)[3] [4]。

在MySQL和PostgreSQL等数据库中,CDC的来源是事务日志(事务日志)。但是由于事务日志通常被截断,因此它们可能不包含整个更改历史记录。因此,要获得源的完整状态,我们需要转储。我们检查了几个开源CDC项目,这些项目通常使用相同的库,数据库API和协议,并发现其中存在许多不符合我们要求的限制。例如,停止日志事件的处理直到转储完成(完整的数据快照),无法按需或执行初始化转储的转储,由于使用了表锁而影响了写入流量。

这导致我们开发DBLog。以统一的方式处理日志和转储。为了支持它,应该在DBMS中实现许多功能,这些功能已经在MySQL,PostgreSQL,MariaDB和许多其他数据库中实现。

DBLog的一些功能:

  • 日志事件按照它们发生的顺序进行处理。
  • 可以随时对所有表,一个表或表的特定主键进行转储
  • 日志处理与转储处理交替进行,将转储分为多个块。因此,日志处理可以与转储处理并行进行。如果该过程结束,则可以在最后一个完成的块之后继续进行,而不必重新开始。它还允许您在创建转储时调整吞吐量,并在必要时暂停其创建。
  • , .
  • : , API.
  • . , .


之前,我们讨论了Delta翻译),这是一个用于数据丰富和同步的平台。 Delta的目标是同步几个数据存储,其中一个是主数据存储(例如MySQL),另一个是派生数据(例如ElasticSearch)。关键的开发要求之一是将更改从源传播到接收者的延迟很短,并且事件流的可用性很高。无论一个团队使用了所有数据仓库,还是一个团队拥有数据而另一个团队使用了数据,这些条件都适用。在有关Delta翻译的文章中,我们还描述了超出数据同步的用例,例如事件处理。

为了同步数据和处理事件,除了能够实时跟踪更改之外,我们还需要满足以下要求:

  • 获取完整状态派生存储(例如ElasticSearch)应最终存储源的完整状态。我们通过转储原始数据库来实现此目的。
  • 随时开始状态恢复。我们可以在任何时候进行转储,而不是仅将转储视为一次性的主初始化操作:对于所有表,一个表或特定的主键。对于丢失或损坏数据的情况,这对于恢复消费者非常重要。
  • - . . , (, ). - . , - .
  • . . API, , , . , , , .
  • . Netflix , Kafka, SQS, Kinesis, Netflix, Keystone. , (, ), (, ). . API.
  • . Netflix , (MySQL, PostgreSQL) AWS RDS. .



我们研究了几种现有的开源解决方案,包括:MaxwellSpinalTap,Yelp MySQL StreamerDebezium在数据收集方面,它们都使用事务日志以类似的方式工作。例如,在MySQL中使用binlog复制协议或在PostgreSQL中使用复制插槽。

但是在处理转储时,它们至少具有以下限制之一:

  • 创建转储时,停止处理日志事件结果,如果转储很大,则日志事件的处理将长时间停止。如果消费者在传播更改时依靠小的延迟,这将是一个问题。
  • . . (, ElasticSearch) .
  • . . [5]. . , . . , PostgreSQL RDS .
  • 使用特定的数据库功能我们发现某些解决方案使用了并非所有系统都具有的其他数据库功能。例如,在MySQL中使用黑洞引擎或通过PostgreSQL中的复制插槽获取转储的一致快照。这限制了不同数据库之间的代码重用。

最后,我们决定采用不同的方法来处理转储:

  • 备用日志和转储事件,以便可以一起执行它们;
  • 随时开始转储;
  • 不要使用桌子锁
  • 不要使用特定的数据库功能。

DBLog框架


DBLog是一个Java框架,用于实时接收转储和更改。转储是分部分执行的,因此它们与实时事件交替发生,并且不会长时间延迟处理。可以随时通过API进行转储。这使使用者可以在初始化阶段或稍后获得灾难恢复的数据库的完整状态。

在设计框架时,我们考虑将对数据库的影响最小化。转储可以根据需要暂停和恢复。这既适用于崩溃恢复,也适用于数据库已成为瓶颈时停止。我们也不会锁定表,以免影响写操作。

DBLog允许您以各种形式记录事件,包括在另一个数据库中或通过API。为了存储与日志和转储处理相关的状态,以及选择主节点,我们使用Zookeeper。创建DBLog时,我们实现了连接各种插件的功能,使您可以根据需要更改实现(例如,用其他东西代替Zookeeper)。

接下来,我们将更详细地考虑日志和转储的处理。

日志


该框架要求数据库实时记录每个更改的行的事件,同时保持提交的顺序。这些事件的来源假定为事务日志。数据库通过DBLog可以使用的传输方式发送它们。对于此传输,我们使用术语“更改日志”。事件可以具有以下类型:创建,创建,更新或删除。对于每个事件,必须提供以下信息:日志中的序列号(日志序列号),操作期间列的状态以及操作时应用的方案。

每个更改都被序列化为DBLog事件格式,并发送给writer,以便进一步传输到输出中。将事件发送到writer是非阻塞操作,因为writer在单独的线程中运行并将事件累积在内部缓冲区中。缓冲事件按接收顺序发送。该框架允许您连接自定义格式化程序,以将事件序列化为任意格式。输出是一个简单的界面,可让您连接到任何收件人,例如流,数据仓库甚至API。

转储


转储是必需的,因为事务日志的存储时间有限,这将阻止它们用于还原完整的原始数据集。转储以块(块)创建,以便它们可以与日志事件交替进行,从而可以同时处理它们。对于块的每个选定行,将以与日志事件相同的格式生成事件并对其进行序列化。因此,使用者不必担心来自日志或转储的事件。日志事件和转储事件都通过同一编写器发送到输出。

可以通过API随时为所有表,一个表或表的特定主键计划转储。表的转储由给定大小的块执行。您还可以配置处理新块的延迟,此时仅允许记录事件。块大小和延迟使您可以平衡日志和转储事件的处理。两种设置都可以在运行时更改。

通过按主键的升序对表进行排序并选择主键大于上一个块的最后一个主键的行来选择块(块)。需要数据库有效地执行此查询,该数据库通常适用于对一定范围的主键执行范围扫描的系统。


图1.使用4列c1-c4和c1作为主键(pk)的表细分。整数类型的主键,块大小为3。通过条件c1> 4选择

块2。必须以不会长时间延迟日志事件处理并保存更改历史记录的方式来获取块,以使具有旧值的所选行不能覆盖较新的行事件。

为了能够顺序选择块,我们在更改日志中创建了可识别的“水印”。水印通过源数据库中的表来实现。该表存储在特殊的名称空间中,因此与应用程序表没有冲突。仅存储一个具有UUID值的行。当此值更改为特定的UUID时,将创建水印。更新生产线会导致变更事件,我们最终会通过变更日志收到该变更事件。

带水印的转储如下创建:

  1. 我们暂时中止日志事件的处理。
  2. 通过更新水印表来生成“低”水印。
  3. 我们为下一个块启动SELECT并将主键索引的结果保存在内存中。
  4. “” (high) , .
  5. . .
  6. , .
  7. , , .
  8. , 1.

SELECT应该返回一个状态,该状态表示一个已提交的更改,直到历史记录。或等效于以下内容:在更改日志的某个位置执行SELECT,同时考虑到此刻为止的更改。数据库通常不提供有关SELECT执行时间的信息(MariaDB除外)。

我们方法的主要思想是在更改日志中定义一个窗口,以确保保留块中的SELECT位置。通过写入下部水印打开窗口,然后执行SELECT,并通过写入上部水印关闭窗口。由于SELECT的确切位置是未知的,因此删除与该窗口中的日志事件冲突的所有选定行。这样可以确保在更改日志中不会重写历史记录。

为此,SELECT必须从较低水位标记的时刻开始或以后读取表的状态(允许包括在较低水位标记之后和读取之前所做的更改)。通常,需要SELECT才能在执行之前查看所做的更改。。我们称其为“非陈旧读取”。另外,由于高位水印是在其后写入的,因此可以确保在其之前执行SELECT。

图2a和2b说明了块选择算法。例如,我们给出一个表,其主键从k1到k6。更改日志中的每个条目代表主键的创建,更新或删除事件。图2a显示了水印生成和块选择(步骤1至4)。在第2步和第4步中更新水印表会创建两个更改事件(品红色),最终通过日志接收到它们。在图2b中,我们集中于通过出现在水印之间的主键从结果集中删除的当前块的行(步骤5至7)。


图2a-用于块选择的水印算法(步骤1-4)。


图2b-选择块的水印算法(步骤5-7)。

请注意,如果一个或多个事务进行了许多行更改,则在上下水印之间,日志中会出现大量事件。因此,我们在阶段2-4暂停了日志的处理,以免丢失水印。因此,可以逐个事件地恢复日志事件的处理,最终使您无需缓存日志的日志事件就可以检测水印。日志处理仅在短时间内暂停,因为步骤2-4预计会很快:更新水印是单个写入操作,而SELECT则受到限制。

在步骤7中一接收到较高水印,就立即按照接收顺序将块中无冲突的行发送到输出。这是一个非阻塞操作,因为编写器在单独的线程中运行,这使您可以在步骤7之后快速恢复处理日志。此后,对于在高水位标记之后发生的事件继续进行日志处理。

图2c使用与图2a和2b相同的示例示出了整个块的记录顺序。日志中出现在较高水位标记之前的事件将被首先记录。然后,该块的其余行将产生(洋红色)。最后,记录在最高水印之后发生的事件。


图2c-记录输出的顺序。带转储剥离交替。

支持的数据库


要使用DBLog,数据库必须提供变更日志,作为具有非失效读取的已提交变更的线性历史记录。诸如MySQL,PostgreSQL,MariaDB等系统可以满足这些条件,因此可以将框架与这些数据库以相同的方式使用。
到目前为止,我们已经添加了对MySQL和PostgreSQL的支持。每个数据库都使用自己的库来接收日志事件,因为每个数据库都使用专有协议。对于MySQL,我们使用shyiko / mysql-binlog-connector,它实现了binlog复制协议。对于PostgreSQL,具有wal2json插件的复制插槽。通过流复制协议接受更改,该协议由jdbc驱动程序实现PostgreSQL的 在MySQL和PostgreSQL中,每个捕获的更改的模式定义都不同。在PostgreSQL中,wal2json包含名称,列类型和值。对于MySQL,架构更改应作为binlog事件进行跟踪。

转储处理使用SQL和JDBC完成,仅需要执行块选择和更新水印即可。对于MySQL和PostgreSQL,使用相同的代码,该代码可用于其他类似的数据库。转储处理本身不依赖于SQL或JDBC,即使您使用不同的标准,也允许您使用满足DBLog要求的数据库。


图3-高级DBLog架构。

高可用性


DBLog使用单节点主动-被动架构。一个实例是主动的(领先),而其他实例是被动的(待机)。要选择主机,我们使用Zookeeper租约用于主节点,必须定期进行更新才能继续成为主节点。在终止租约续约的情况下,领导者的功能将转移到另一个节点。当前,我们为每个可用区部署一个副本(可用区,通常有3个可用区),因此,如果一个可用区落下,则另一个可用区中的副本可以在最少的总停机时间内继续进行处理。备份实例可以位于不同的区域,尽管建议您与数据库主机在同一区域工作,以提供较低的延迟来捕获更改。

用于生产


DBLog是Delta中使用的MySQL和PostgreSQL连接器的基础自2018年以来,Delta已在生产中用于在Netflix Studio应用程序中同步数据仓库和事件处理。Delta连接器使用其事件序列化器。Netflix特定的流(例如Keystone)用作output


图4-Delta连接器。

除了Delta之外,Netflix还使用DBLog在Netflix上为具有自己的数据格式的其他数据移动平台创建连接器。

和我们在一起


DBLog具有本文未涵盖的其他功能,例如:

  • 无需使用锁即可获取表模式的能力。
  • 与架构存储集成。对于每个事件,该方案都存储在存储器中,事件有效负载中指示了指向的链接。
  • 单调写入模式。确保保存特定行的状态后,其过去状态不会被覆盖。因此,消费者仅在向前方向上接收状态变化,而没有及时来回移动。

我们计划在2020年打开DBLog源代码,并在其中包含其他文档。

致谢


我们要感谢以下人员为DBLog的发展做出了贡献:Josh SnyderRaghuram Onti SrinivasanTarranga GamaethigeYun Wang

参考文献


[1] Das,Shirshanka等。“数据总线上的所有功能!:Linkedin的可扩展一致变更数据捕获平台。” 第三届ACM云计算研讨会。ACM,2012
[2] “关于变更数据捕获(SQL Server)”,Microsoft SQL文档,2019
[3] Kleppmann,Martin,“使用日志构建可靠的数据基础结构(或:为什么双重写入是个坏主意)”,合流,2015
[4]克莱普曼,马丁,阿拉斯泰尔·贝雷斯福德,伯格·斯文根。“在线事件处理。” ACM 62.5(2019)的通信:43–49
[5] https://debezium.io/documentation/reference/0.10/connectors/mysql.html#snapshots


了解有关该课程的更多信息。

All Articles