4. Flink CDC核心概念

在金融主数据同步场景中,数据一致性不是“尽量做到”,而是“必须做到”。账户、客户、产品、币种、机构等主数据一旦出现重复、丢失或乱序,下游核算、风控与报表都会连锁失真。理解 Flink CDC 的核心概念,本质上是在理解“变更如何被捕获、如何被处理、如何被可靠写入”这条链路。本节围绕四个关键点展开:Flink CDC 架构、Source 与 Sink、Exactly-Once 语义、增量快照算法。

一、Flink CDC 架构:从数据库日志到下游存储

Flink CDC 的架构可以理解为三层协作:捕获层、计算层、写入层。捕获层通过 Debezium 等连接器读取 MySQL、Oracle、PostgreSQL 的 binlog 或 redo log,将行级变更解析为 INSERT、UPDATE、DELETE 事件;计算层由 Flink 作业承担,负责反序列化、过滤、聚合、维表关联与状态管理;写入层则通过 Sink 连接器将结果写入 Kafka、Hudi、Iceberg、Doris、StarRocks 或另一个数据库。

与传统的“定时批量抽取”相比,Flink CDC 的关键差异在于:它先做一次全量快照,再无缝切换到增量日志读取,整个过程不需要锁表,也不需要人工拼接全量与增量。对于金融主数据,这意味着白天业务持续写入时,同步链路依然可以稳定运行。

Flink CDC 架构与增量快照阶段切换 源数据库 MySQL / Oracle binlog / redo log Flink CDC Source 全量快照 + 增量日志 Debezium 解析 Flink 计算 状态 / Checkpoint Exactly-Once Sink Kafka / Hudi Doris / Iceberg 增量快照算法:阶段切换与一致性边界 阶段一:全量快照 分片读取,记录位点 阶段二:增量日志 从快照位点继续消费 阶段三:持续同步 Checkpoint 保障一致性

二、Source 与 Sink:数据进出 Flink 的两端

Source 负责“读进来”,Sink 负责“写出去”。在 Flink CDC 中,Source 通常使用 MySqlSource、OracleSource 或 PostgresSource,它们内部封装了 Debezium 的变更事件格式。Sink 则根据目标存储选择,例如 Kafka Sink、JDBC Sink、Hudi Sink、Iceberg Sink 或 Doris Sink。

金融主数据同步中,Source 与 Sink 的配置直接决定同步质量。以下是一个典型的 MySQL CDC Source 配置片段:

MySqlSource<String> source = MySqlSource.<String>builder()
    .hostname("10.20.30.40")
    .port(3306)
    .databaseList("finance_mdm")
    .tableList("finance_mdm.customer", "finance_mdm.account")
    .username("cdc_user")
    .password("******")
    .deserializer(new JsonDebeziumDeserializationSchema())
    .startupOptions(StartupOptions.initial())
    .build();

其中 StartupOptions.initial() 表示先做全量快照,再切换到增量日志。若改为 latest(),则只从当前位点开始读取,历史数据不会同步。对于主数据初始化场景,通常必须使用 initial()。

提示:Source 的并行度不宜盲目调大。MySQL binlog 读取需要与数据库协商,过高的并行度可能增加主库压力。建议先按表或按分片评估,再逐步调整。

三、Exactly-Once 语义:金融同步的底线

Exactly-Once 意味着每条变更事件在最终结果中“恰好生效一次”,既不重复也不丢失。Flink 通过 Checkpoint 机制 与 两阶段提交 实现端到端的 Exactly-Once。Checkpoint 会周期性保存 Source 的读取位点、算子状态以及 Sink 的预提交信息;当作业失败恢复时,从最近一次成功的 Checkpoint 重新开始。

但要注意:Exactly-Once 不是“自动获得”的。它要求 Source 支持位点回放、Sink 支持事务或幂等写入。例如 Kafka Sink 在开启 EXACTLY_ONCE 语义后,会使用事务将一批消息原子写入;而某些 JDBC Sink 若不支持事务,则只能做到 At-Least-Once,需要下游通过主键去重来补偿。

语义 含义 金融主数据适用性
At-Most-Once 可能丢失,不重复 不适用,主数据不能丢
At-Least-Once 不丢失,可能重复 需下游幂等去重
Exactly-Once 不丢失,不重复 推荐,需 Source/Sink 配合

四、增量快照算法:全量与增量的无缝衔接

增量快照算法是 Flink CDC 最核心的机制之一。传统方案需要先锁表做全量,再记录位点,再切换增量,期间容易漏数据或重复。Flink CDC 的做法是:将全量快照拆分为多个分片,每个分片独立读取,同时记录读取前后的 binlog 位点;当所有分片完成后,再从最早的分片位点开始消费增量日志,从而保证全量与增量之间不丢不重。

其关键流程可以概括为:

  1. 记录当前 binlog 位点作为低水位。
  2. 对表进行分片,并行读取全量数据。
  3. 每个分片完成后记录高水位位点。
  4. 所有分片完成后,从低水位开始回放增量日志。
  5. 结合 Checkpoint 持续保存进度,失败后从最近位点恢复。
误区:很多人以为“全量阶段不会产生增量”,实际上全量读取期间业务仍在写入。增量快照算法正是为了解决这个重叠窗口而设计的。若忽略这一点,切换阶段就可能出现数据缺口。

五、案例复盘:客户主数据同步中的一次重复写入

某金融项目在同步客户主数据时,发现下游 Doris 表中出现少量重复客户记录。排查后发现:Sink 使用的是 JDBC 写入,未开启事务,而作业在 Checkpoint 前发生了一次 TaskManager 重启。由于 Source 从 Checkpoint 恢复,部分事件被重新发送,JDBC Sink 又直接 INSERT,导致重复。

修复方式有两种:一是将 Sink 改为支持事务的 Kafka 或 Hudi,并开启 Exactly-Once;二是在 JDBC Sink 前增加按主键去重的算子,或使用 INSERT INTO ... ON DUPLICATE KEY UPDATE 实现幂等写入。最终该项目采用“Kafka 缓冲 + Doris 主键模型”的方案,既保证了 Exactly-Once,又提升了写入吞吐。

这个案例说明:Exactly-Once 是链路级能力,不是单个组件的能力。Source 能回放、Flink 能 Checkpoint、Sink 能幂等或事务,三者缺一不可。理解 Flink CDC 的架构、Source/Sink、Exactly-Once 与增量快照算法,才能在设计金融主数据同步方案时,把“一致性”从口号变成可落地的工程配置。