前言
XTransfer 专注为跨境 B2B 电商中小企业提供跨境金融和风控服务,通过建立数据化、自动化、互联网化和智能化的风控基础设施,搭建通达全球的财资管理平台,提供开立全球和本地收款账户、外汇兑换、海外外汇管制国家申报等多种跨境金融服务的综合解决方案。
在业务发展早期,我们选择了传统的离线数仓架构,采用全量采集、批量处理、覆盖写入的数据集成方式,数据时效性较差。随着业务的发展,离线数仓越来越不能满足对数据时效性的要求,我们决定从离线数仓向实时数仓进行演进。而建设实时数仓的关键点在于变更数据采集工具和实时计算引擎的选择。
经过了一系列的调研,在 2021 年 2 月份,我们关注到了 Flink CDC 项目,Flink CDC 内嵌了 Debezium,使 Flink 本身具有了变更数据捕获的能力,很大程度上降低了开发门槛,简化了部署复杂度。加上 Flink 强大的实时计算能力和丰富的外部系统接入能力,成为了我们构建实时数仓的关键工具。
另外,我们在生产中也大量使用到了 MongoDB,所以我们在 Flink CDC 基础上通过 MongoDB Change Streams 特性实现了 Flink MongoDB CDC Connector,并贡献给了 Flink CDC 社区,目前已在 2.1 版本中发布。很荣幸在这里能够在这里和大家分享一下实现细节和生产实践。
一、Flink CDC
Dynamic Table (动态表) 是 Flink 的支持流数据的 Table API 和 SQL 的核心概念。流和表具有对偶性,可以将表转换成一个变更流 (changelog stream),也可以回放变更流还原成一张表。
变更流有两种形式:Append Mode 和 Update Mode。Append Mode 只会新增,不会变更和删除,常见的如事件流。Update Mode 可能新增,也可能发生变更和删除,常见的如数据库操作日志。在 Flink 1.11之前,只支持在 Append Mode 上定义动态表。
Flink 1.11 在 FLIP-95 引入了新的 TableSource 和 TableSink,实现了对 Update Mode changelog 的支持。并且在 FLIP-105 中,引入了对 Debezium 和 Canal CDC format 的直接支持。通过实现 ScanTableSource,接收外部系统变更日志 (如数据库的变更日志),将其解释为 Flink 的能够识别的 changlog 并向下流转,便可以支持从变更日志定义动态表。
在 Flink 内部,changelog 记录由 RowData 表示,RowData 包括 4 种类型:+I (INSERT), -U (UPDATE_BEFORE),+U (UPDATE_AFTER), -D (DELETE)。根据 changelog 产生记录类型的不同,又可以分为 3 种 changelog mode。
-
INSERT_ONLY:只包含 +I,适用于批处理和事件流。
-
ALL:包含 +I, -U, +U, -D 全部的 RowKind,如 MySQL binlog。
-
UPSERT:只包含 +I, +U, -D 三种类型的 RowKind,不包含 -U,但必须按唯一键的幂等更新 , 如 MongoDB Change Streams。
二、MongoDB 复制机制
如上节所述,实现 Flink CDC MongoDB 的关键点在于:如何将 MongoDB 的操作日志转换为 Flink 支持的 changelog。要解决这个问题,首先需要了解一下 MongoDB 的集群部署和复制机制。
2.1 副本集和分片集群
副本集是 MongoDB 提供的一种高可用的部署模式,副本集成员之间通过 oplog (操作日志) 的复制,来完成副本集成员之间的数据同步。
分片集群是 MongoDB 支持大规模数据集和高吞吐量操作的部署模式,每个分片由一个副本集组成。
2.2 Replica Set Oplog
操作日志 oplog,在 MongoDB 中是一个特殊的 capped collection (固定容量的集合),用来记录数据的操作日志,用于副本集成员之间的同步。oplog 记录的数据结构如下所示。
{
"ts" : Timestamp(1640190995, 3),
"t" : NumberLong(434),
"h" : NumberLong(3953156019015894279),
"v" : 2,
"op" : "u",
"ns" : "db.firm",
"ui" : UUID("19c72da0-2fa0-40a4-b000-83e038cd2c01"),
"o2" : {
"_id" : ObjectId("61c35441418152715fc3fcbc")
},
"wall" : ISODate("2021-12-22T16:36:35.165Z"),
"o" : {
"$v" : 1,