数据源监听
数据源监听
源数据变化捕获是数据集成的起点,获取数据源变化主要有三种方式:
- 基于日志的解析模式;
- 基于增量条件查询模式;
- 数据源主动推送模式。
基于日志的解析模式常用于各种类型的数据库,例如 MySQL 的 Binlog、Oracle 的 Redo&Achieve Log、SQL Server Change Tracking & CDC 等。
不同数据库日志解析的原理差别很大,以 MySQL Binlog 模式为例,解析程序本身是一个 Slave,能够实时收到 MySQL Master 的数据流推送,并解析还原成 DDL 和 DML 操作。而 SQL Server 的 CT 模式下,增量是通过定期查询 Change Tracking 表实现的。
基于增量条件的查询模式不依赖于源端开启日志记录,但对于数据源通常有额外的格式要求。例如,数据库表或文档对象需要有标志更新时间的字段,这在一些业务系统中是无法满足的。
数据源主动推送模式的常见形式为业务插码,即应用系统通过打点或者配置切面的方式,将数据变化封装为事件,额外发送一份给数据集成平台。这种方式一般需要对源端系统代码进行一定程度的修改。
数据库日志增量捕获
通常而言,基于数据库的日志进行增量捕获应当被优先考虑。其具备以下几个显著优点:
- 能够完整获取数据变化的操作类型,尤其是 Delete 操作,这是增量条件查询模式很难做到的;
- 不依赖特别的数据字段语义,例如更新时间;
- 多数情况下具备较强的实时性。
当然,事物都具有两面性。开启数据库日志通常会对源库性能产生一定的影响,需要额外的存储空间,甚至一些解析方法也会对源库资源造成额外消耗。因此,实施过程中需要在 DBA 的配合下,根据数据库特点和解析原理进行 DB 部署规划。
推荐使用数据库的复制和灾备能力,在独立服务器对从库进行日志解析。此外,当数据库产生批量更新时,会在短时间内产生大量日志堆积,如果日志留存策略设置不当,容易出现数据丢失。这些都需要根据具体的业务数据增长特点,在前期做好规划,并在上线后根据业务变化定期进行评估和调整。
数据源推送
数据源主动 push 模式下,由于事件发送和业务处理很难做到事务一致性,所以当出现异常时,数据一致性就无从保证,比较适合对于数据一致性要求不高的场景,例如用户行为分析。
CDC 模块
变更数据抓取通常需要针对不同数据源订制实现,而针对特定数据源,实现方式一般有两种:
- 基于自增列或上次修改时间做增量查询;
- 利用数据源本身的事务日志或 Slave 同步等机制实时订阅变更;
第一种方式实现简单,以 SQL 为例:相信大家都写过类似的 SQL, 每次查询时,查询 [last_query_time, now) 区间内的增量数据,lastmodified 列也可以用自增主键来替代。这种方式的缺点是实时性差,对数据库带来了额外压力,并且侵入了表设计:所有要实现变更抓取的表都必须有用于增量查询的列并且在该列上构建索引。另外,这种方式无法感知物理删除(Delete), 删除逻辑只能用一个 delete 列作为 flag 来实现。第二种方式实现起来相对困难,但它很好地解决了第一种方式的问题,因此前文提到的开源方案也都采用了这种方式。下面我们着重分析在 MySQL 中如何实现基于事务日志的实时变更抓取。
MySQL 的事务日志称为 Binlog,常见的 MySQL 主从同步就是使用 Binlog 实现的:
我们把 Slave 替换成 CDC 模块,CDC 模块模拟 MySQL Slave 的交互协议,便能收到 Master 的 Binlog 推送:
CDC 模块解析 Binlog,产生特定格式的变更消息,也就完成了一次变更抓取。但这还不够,CDC 模块本身也可能挂掉,那么恢复之后如何保证不丢数据又是一个问题。这个问题的解决方案也是要针对不同数据源进行设计的,就 MySQL 而言,通常会持久化已经消费的 Binlog 位点或 Gtid(MySQL 5.6 之后引入)来标记上次消费位置。其中更好的选择是 Gtid,因为该位点对于一套 MySQL 体系(主从或多主)是全局的,而 Binlog 位点是单机的,无法支持主备或多主架构。
MySQL CDC 模块的一个挑战是如何在 Binlog 变更事件中加入表的 Schema 信息(如标记哪些字段为主键,哪些字段可为 null)。Debezium 在这点上处理得很漂亮,它在内存中维护了数据库每张表的 Schema,并且全部写入一个 backup 的 Kafka Topic 中,每当 Binlog 中出现 DDL 语句,便应用这条 DDL 来更新 Schema。而在节点宕机,Debezium 实例被调度到另一个节点上后,又会通过 backup topic 恢复 Schema 信息,并从上次消费位点继续解析 Binlog。
另一个挑战是,我们数据库已经有大量的现存数据,数据迁移时的现存数据要如何处理。这时,Debezium 独特的 Snapshot 功能就能帮上忙,它可以实现将现有数据作为一次”插入变更”捕捉到 Kafka 中,因此只要编写一次客户端就能一并处理全量数据与后续的增量数据。
开源方案对比
- databus: Linkedin 的分布式数据变更抓取系统;
- Yelp’s data pipeline: Yelp 的数据管道;
- Otter & Canal: 阿里开源的分布式数据库同步系统;
- Debezium: Redhat 开源的数据变更抓取组件;
这些解决方案关注的重点各有不同,但基本思想是一致的:使用变更抓取模块实时订阅数据库变更,并分发到一个中间存储供下游应用消费。下面是四个解决方案的对比矩阵:
方案 | 变更抓取 | 分发平台 | 消息格式 | 额外特性 |
---|---|---|---|---|
databus | DatabusEventProducer, 支持 Oracle 和 MySQL 的变更抓取 | DatabusRelay, 基于 Netty 的中间件, 内部是一个 RingBuffer 存储变更消息 | Apache Avro | 有 BootstrapService 组件存储历史变更用以支持全量 |
Yelp’s data pipeline | MySQL Streamer, 基于 Binlog 抓取变更 | Apache Kafka | Apache Avro | Schematizer, 作为消息的 Avro Schema 注册中心的同时提供了 Schema 文档 |
Otter | Canal, 阿里的另一个开源项目, 基于 Binlog | work node 内存中的 ring buffer | protobuf | 提供了一个完善的 admin ui |
Debezium | 提供 MySQL, MongoDB, PostgreSQL 三种 Connector | Apache Kafka | Apache Avro / json | Snapshot mode 支持全量导入数据表 |
databus
Linkedin databus 的论文有很强的指导性,但它的 MySQL 变更抓取模块很不成熟,官方支持的是 Oracle,MySQL 只是使用另一个开源组件 OpenReplicator 做了一个 demo。另一个不利因素 databus 使用了自己实现的一个 Relay 作为变更分发平台,相比于使用开源消息队列的方案,这对维护和外部集成都不友好。
Otter & Canal
Otter 和 Canal 在国内相当知名,Canal 还支持了阿里云 DRDS 的二级索引构建和小表同步,工程稳定性上有保障。但 Otter 本身无法很好地支持多表聚合到新表,开源版本也不支持同步到分片表当中,能够采取的一个折衷方案是直接将 Canal 订阅的变更写入消息队列,自己写下游程序实现聚合同步等逻辑。该方案也是我们的候选方案。
data pipeline
Yelp’s data pipeline 是一个大而全的解决方案。它使用 Mysql-Streamer(一个通过 Binlog 实现的 MySQL CDC 模块)将所有的数据库变更写入 Kafka,并提供了 Schematizer 这样的 Schema 注册中心和定制化的 Python 客户端库解决通信问题。遗憾的是该方案是 Python 构建的,与我们的 Java 技术栈相性不佳。
Debezium
Debezium 不同于上面的解决方案,它只专注于 CDC,它的亮点有:
-
支持 MySQL、MongoDB、PostgreSQL 三种数据源的变更抓取,并且社区正在开发 Oracle 与 Cassandra 支持;
-
Snapshot Mode 可以将表中的现有数据全部导入 Kafka,并且全量数据与增量数据形式一致,可以统一处理;
-
利用了 Kafka 的 Log Compaction 特性,变更数据可以实现”不过期”永久保存;
-
利用了 Kafka Connect,自动拥有高可用与开箱即用的调度接口;
-
社区活跃:Debezium 很年轻,面世不到 1 年,但它的 Gitter 上每天都有百余条技术讨论,并且有两位 Redhat 全职工程师进行维护;