基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

讲讲Flink-CDC的数据同步流程

Flink CDC(Change Data Capture)的数据读取原理经历了从 1.x 版本(基于锁、单并发)2.x/3.x 版本(无锁、并发读取、支持断点续传) 的重大演进。其当前最核心的底层数据读取原理,是基于 Flink 社区的 FLIP-27 架构 以及借鉴 Netflix DBLog 算法设计的 无锁增量快照算法(Lock-free Incremental Snapshot Algorithm)

下面将从架构设计、读取阶段、核心无锁算法以及状态恢复机制四个维度,详细解析 Flink CDC 的数据读取原理。


一、 整体架构:FLIP-27 读写分离模型

从 2.0 版本开始,Flink CDC 抛弃了传统的单线程 SourceFunction 架构,全面基于 Flink 的 FLIP-27 规范进行重构。该架构将“工作发现(元数据切分)”与“实际数据读取”进行解耦,核心由两个组件协同工作:

  1. SplitEnumerator(分片协调器)
    • 运行在 JobManager 上,属于单线程组件。
    • 负责监控物理表、提取表结构信息,并将整张大表按照主键范围切分成多个小数据块,称为 Split(分片)Chunk(数据块)
    • 负责将这些 Chunk 动态分配给空闲的 SourceReader 执行。
  2. SourceReader(数据读取器)
    • 运行在 TaskManager 上,多实例并行运行(支持水平扩展并发度)。
    • SplitEnumerator 接收分配的 Chunk,与源数据库建立连接,读取该分片内的历史存量数据,并在全量读取结束后无缝切入增量读取。

二、 数据读取的两个核心阶段

Flink CDC 对一张表的完整同步过程分为两个阶段:全量快照(Snapshot)阶段增量日志(Binlog)阶段

plaintext
[全量快照阶段 (多并发并发读取)]
   Table -> Chunk 1, Chunk 2, ..., Chunk N ──> 并行 SELECT 扫描数据 (结合低/高水位线修正)
                                                 │
                                                 ▼
[增量日志阶段 (单并发持续读取)]
   所有全量 Chunk 消费完毕 ──> 启动单并发 Reader ──> 从所有 Chunk 的最小高水位线(HW)开始,持续消费 Binlog 

1. 全量快照阶段(Snapshot Phase)

在此阶段,Flink CDC 会将历史存量数据并行导出。为了避免对数据库加锁,Flink CDC 采用并发分块读取机制。

  • 分片划分(Chunk Splitting)SplitEnumerator 获取表的主键范围(Min PK 至 Max PK),并结合用户配置的步长(chunk.size)将主键区间动态切分为多个左闭右开的 Chunk(例如:[1, 1000)[1000, 2000) 等)。
  • 并行读取:多个 SourceReader 并行认领这些 Chunk,并通过执行普通的 SQL 查询(如 SELECT * FROM table WHERE pk >= 1000 AND pk < 2000)来导出历史存量数据。

2. 增量日志阶段(Binlog Phase)

在所有的全量 Chunk 被并行读取完毕后,SplitEnumerator 会将这些分片的元数据收回,并重新组装成一个全局的 BinlogSplit,分配给某一个特定的 SourceReader

  • 此时,任务会自动切换为单并发模式,该 Reader 与数据库建立 Binlog 复制连接,开始持续、实时地拉取数据库的增量变更日志。

三、 核心:无锁增量快照算法(实现“不锁表”与“恰好一次”)

由于在全量阶段多并发读取历史数据的过程中,业务系统仍在对数据库执行写入、修改和删除操作,这就无法直接保证读取出来的快照与后续的 Binlog 保持一致。

为了不使用全局读锁(FLUSH TABLES WITH READ LOCK)就能保证全量与增量阶段数据的“恰好一次(Exactly-Once)”一致性,Flink CDC 实现了无锁打点与合并算法

对于每一个被分配的 Chunk,单个 SourceReader 执行以下读取步骤:

plaintext
时间线 ───────────────────────────────────────────────────────────────>
        │                           │                           │
   [1. 记录低水位线 LW]       [2. SELECT 扫描存量]        [3. 记录高水位线 HW]
        │                           │                           │
        └─────── 捕获该期间产生的 Binlog [LW, HW] ──────────────┘
                                    │
                                    ▼
                      [4. 数据合并与修正 (Normalize)]
                                    │
                                    ▼
                           [5. 输出最终快照数据]
  1. 记录低水位线(Low Watermark, LW)
    在开始读取该 Chunk 历史数据之前,Reader 首先向数据库查询当前最新的 Binlog 位点,并将其记录为低水位线 LWLW
  2. 扫描存量数据
    Reader 执行 SELECT 查询读取当前分片主键区间内的所有历史数据,并在内存中缓存这些数据。此时读取到的数据可能是脏数据(因为在 SELECT 执行期间,数据可能随时被其他业务事务修改)。
  3. 记录高水位线(High Watermark, HW)
    SELECT 查询执行完毕后,Reader 再次向数据库查询当前的最新 Binlog 位点,并将其记录为高水位线 HWHW
  4. 捕获区间变更 Binlog
    Reader 读取在低水位线与高水位线区间 [LW,HW][LW, HW] 之间产生的所有涉及当前 Chunk 主键范围内的 Binlog 变更日志。
  5. 数据合并与修正(Normalize)
    这是保证Exactly-Once的核心。Reader 会将步骤 2 缓存的存量快照数据与步骤 4 捕获到的 Binlog 变更流进行对齐和修正
    • 若存量数据中的某条记录在 Binlog 中存在 UPDATE 事件,则将该记录更新为最新的修改值。
    • 若某条记录在 Binlog 中存在 DELETE 事件,则将该记录从快照中移除。
    • 若 Binlog 中产生了新的 INSERT,则将其加入。
      经过修正后的快照,其数据状态完美等价于数据库在 HWHW(高水位线)时刻该主键区间内的真实物理快照
  6. 下发与切换
    当所有 Chunk 均按照上述流程处理完毕并下发后,Flink CDC 会开始从所有已完成 Chunk 中最小的高水位线 HWHW 开始消费增量 Binlog,在此位点之前已被快照合并过的 Binlog 事件将被自动过滤掉,从而确保了全量与增量切换时的无锁、无缝、且不丢不重。

四、 状态恢复与全量阶段 Checkpoint 支持

得益于 FLIP-27 架构,Flink CDC 在全量读取阶段和增量阶段均支持完整的 Checkpoint:

  • 全量读取阶段SplitEnumerator 会在状态中持久化保存当前“待分配”、“正在消费”以及“已消费完成”的 Chunk 列表。
  • SourceReader 在每次成功读取完一个 Chunk 并完成打点修正后,也会将该 Chunk 的完成状态反馈给协调器。
  • 一旦作业中途崩溃,Flink 会从上一个成功的快照点恢复,SplitEnumerator 只需要重新分配那些在快照点时仍处于“未完成”或“正在消费”状态的 Chunk,已完成的 Chunk 无需重新读取。这彻底解决了大表同步时中途失败必须“重头再来”的痛点。
00:00
00:00