变化数据捕获 (CDC)允许从数据库实时捕获已提交的更改,并将这些更改传播到下游消费者。在需要保持多个异构数据存储同步(如MySQL和ElasticSearch)的用例中,CDC变得越来越流行,它解决了双写和分布式事务等传统技术存在的挑战。
本文最初发布于Medium,由InfoQ中文站翻译并分享。
变化数据捕获 (CDC)允许从数据库实时捕获已提交的更改,并将这些更改传播到下游消费者。在需要保持多个异构数据存储同步(如MySQL和ElasticSearch)的用例中,CDC变得越来越流行,它解决了双写和分布式事务等传统技术存在的挑战。
在MySQL和PostgreSQL这样的数据库中,事务日志是CDC事件的来源。由于事务日志的保留期通常有限,所以不能保证包含全部的更改历史。因此,需要转储来捕获源的全部状态。已有几个开源CDC项目,它们通常使用相同的底层库、数据库API和协议。尽管如此,我们发现了许多不能满足我们需求的限制,例如,在转储完成之前暂停日志事件的处理,缺少按需触发转储的能力,或者使用表级锁来阻止写流量的实现。
这是我们开发DBLog的动机,它在通用框架下提供日志和转储处理。它支持的数据库需要具有MySQL、PostgreSQL、MariaDB等系统中常见的一组特性。
DBLog的部分功能如下:
在之前的一篇博文中,我们讨论了Delta,一个数据充实和同步平台。Delta的目标是保持多个数据存储的同步,其中一个存储是事实的来源(如MySQL),其他存储则是派生存储(如ElasticSearch)。其中一个关键要求是,从事实消息源到目的地的传播延迟要低,并且事件流是高度可用的。无论是同一团队使用多个数据存储库,还是一个团队拥有另一个团队正在使用的数据,这些条件都适用。在我们介绍Delta的博文中,我们还描述了数据同步之外的用例,比如事件处理。
对于数据同步和事件处理用例,除了实时捕获更改的能力外,还需要满足以下要求:
我们评估了一系列现有的开源产品,包括Maxwell、SpinalTap、Yelp的MySQL Streamer和Debezium。现有的解决方案在捕获来自事务日志的实时更改方面类似。例如使用MySQL的binlog复制协议,或者PostgreSQL的复制槽。
在转储处理方面,我们发现现有的解决方案至少存在以下一种限制:
最后,我们决定实现一种不同的方法来处理转储。它可以:
DBLog是一个基于Java的框架,能够实时捕获更改并获取转储。转储以块的形式获取,以便可以与实时事件交错,并且不会长时间停止实时事件处理。转储可以通过提供的API随时获取。这使得下游用户可以在最初或稍后的修复时捕获完整的数据库状态。
我们设计这个框架是为了尽量减少对数据库的影响。转储可以根据需要暂停和恢复。不管是对于失败后的恢复,还是数据库遇到瓶颈时停止处理,这都很重要。为了不影响应用程序的写操作,我们也不会获取表级锁。
DBLog允许将捕获的事件写入任何输出,甚至是另一个数据库或API。我们使用Zookeeper来存储与日志和转储处理相关的状态,并将其用于群首选举。我们在构建DBLog时考虑到了可插拔性,可以根据需要替换实现(比如用其他东西替换Zookeeper)。
下面的小节将更详细地说明日志和转储处理。
该框架要求数据库针对每个更改的行实时地按提交顺序发出一个事件。事务日志被认为是这些事件的起源。数据库将它们发送到DBLog可以使用的传输。我们使用术语“更改日志”来表示该传输。事件的类型可以是创建、更新或删除。对于每个事件,需要提供以下内容:日志序列号、操作时的列状态和操作时应用的模式。
每个更改都序列化为DBLog的事件格式,并发送给写入器,以便将其传递到输出。向写入器发送事件是一种非阻塞操作,因为写入器在自己的线程中运行,并在内部缓冲区中收集事件。缓冲的事件按顺序写入输出。该框架允许自定义格式化程序插件,以便将事件序列化为自定义格式。输出是一个简单的接口,允许插入任何需要的目标,例如流、数据存储甚至API。
转储是必需的,因为事务日志的保留时间有限,所以无法用它们重新构造完整的源数据集。转储以块的形式获取,这样它们就可以与日志事件交错,从而允许它们同时进行。为块的每个选定行生成一个事件,并以与日志事件相同的格式进行序列化。这样,如果事件源于日志或转储,下游使用者就无需担心。日志和转储事件都通过同一写入器发送到输出。
可以通过API随时调度针对所有表、特定表或表的特定主键的转储。每个表的转储请求按配置好的大小分块执行。此外,可以配置延迟来延缓新块的处理,在此期间只允许日志事件处理。块大小和延迟实现了日志和转储事件处理之间的平衡,并且可以在运行时更新这两个设置。
在选择块时会按主键升序对表进行排序,块中行的主键大于前一个块的最后一个主键。数据库需要有效地执行此查询,这通常适用于实现了主键范围扫描的系统。
图1 将一个包含4列(c1-c4)且以c1为主键(pk)的表分块。Pk列的类型为integer,块大小为3。块2的选择以c1 > 4为条件。
块需要以一种不会长时间阻塞日志事件处理的方式来获取,并且要保留日志更改的历史,这样,如果选取的行是旧值,就不能覆盖来自日志事件的较新的状态。
为了实现这一点,我们在更改日志中创建可识别的水印事件,以便对块选择进行排序。水印是通过源数据库中的一个表实现的。表存储在专用的命名空间中,因此不会与应用程序表发生冲突。存储UUID字段的表只包含一行。通过将这一行更新为特定的UUID来生成水印。行更新将导致一个最终通过更改日志接收的更改事件。
通过使用水印,转储采取以下步骤:
假定SELECT从一致的快照返回状态,该快照表示历史上某个特定点之前提交的更改。或者说:考虑到到那时为止的更改,SELECT在更改日志的特定位置上执行。数据库通常不公开与select语句执行相对应的日志位置(MariaDB是一个例外)。
我们方法的核心思想是在变更日志上确定一个窗口,它保证包含SELECT。由于确切的选择位置是未知的,所有与该窗口内的日志事件发生冲突的选中行将被删除。这可以确保选择的块不会覆盖日志更改的历史。通过写入低水印来打开窗口,然后运行选择,最后通过写入高水印来关闭窗口。为了实现这一功能,SELECT必须读取低水印或之后的最新状态(如果该选择还包括在低水印写之后和读之前提交的写,则没有问题)。
图2a和2b说明了块选择算法。我们提供了一个示例表,k1到k6为主键。每个更改日志条目表示主键的创建、更新或删除事件。在图2a中,我们展示了水印的生成和块的选择(步骤1到步骤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复制协议,以便从MySQL主机接收事件。对于PostgreSQL,我们通过wal2json插件使用复制槽。更改通过由PostgreSQL jdbc驱动程序实现的流复制协议接收。对于捕获的每个更改的模式,MySQL和PostgreSQL的确定方式是不同的。在PostgreSQL中,wal2json包含列名和类型以及列值。MySQL模式的更改则必须通过接收的binlog事件进行跟踪。
转储处理是使用SQL和JDBC集成的,只需要实现块选择和水印更新。MySQL和PostgreSQL使用相同的代码,其他类似的数据库也可以使用相同的代码。转储处理本身不依赖于SQL或JDBC,并且允许集成满足DBLog框架要求的数据库,即使它们使用不同的标准。
图 3——DBLog高阶架构
DBLog使用主动-被动架构。一个实例是主动的,其他的是被动的。我们利用Zookeeper进行群首选举,从而确定活动实例。领导权是一种租约,如果没有及时更新,就会丢失,让另一个实例接管。我们目前为每个AZ部署一个实例(通常我们有3个AZ),因此,如果一个AZ宕机,另一个AZ中的实例可以继续处理,保证总停机时间最少。被动实例也可以跨区域,但建议在与数据库主机相同的区域内进行操作,以保持较低的更改捕获延迟。
DBLog是Netflix MySQL和PostgreSQL连接器的基础,这些连接器在Delta中使用。Delta自2018年起用于生产,用于Netflix studio应用程序中的数据存储同步和事件处理。在DBLog之上,Delta连接器使用自定义的事件序列化器,因此,在将事件写入输出时会使用Delta的事件格式。Netflix特有的流被用作输出,比如Keystone。
图4——Delta连接器
除了Delta之外,DBLog还用于为其他Netflix数据移动平台构建连接器,这些平台有自己的数据格式。
DBLog还有一些本文没有涉及的功能,比如:
我们计划在2020年开源DBLog及更多文档。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货