实时数据湖 flink CDC + Kafka +Doris 【企业级实战】Checkpoint、Offset、事务与幂等如何闭环 06

一、两代项目的一致性边界

第一版直连

text 复制代码
SQLServer Source → Flink State → Postgres JDBC Sink

Source 与 Sink 在同一个 Job,但普通 JDBC Commit 仍不受 Flink 两阶段提交协调,因此语义是:

text 复制代码
At-least-once + Postgres ON CONFLICT 幂等

第二版 Kafka 架构

text 复制代码
数据库 → Flink CDC → Kafka
Exactly Once(依赖 Checkpoint 与 Kafka 事务)

Kafka → Flink → Postgres / Doris JDBC
At-least-once + target-side idempotence

不能因为前半段 Exactly Once,就把整条链路都描述成 Exactly Once。

二、Checkpoint 保存什么

Checkpoint 保存 Source 位置、Keyed State 和算子状态,使任务重启后能够从一致快照恢复。

两代项目都逐步采用 HDFS 存储生产 Checkpoint。第一版按 Job 创建独立目录,第二版还区分本地与集群环境:本地默认使用文件路径,集群使用 HDFS,并允许命令行覆盖。

需要共同调整的参数包括:

text 复制代码
interval
timeout
min pause
max concurrent checkpoints
tolerable failures
externalized checkpoint retention
restart attempts / delay

三、Source-to-Kafka 的事务协同

第二版 Kafka Sink 使用:

java 复制代码
.setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE)
.setTransactionalIdPrefix(transactionalIdPrefix)

Checkpoint 成功时提交 Kafka 事务。如果 Checkpoint 失败,本轮未提交事务对 Read Committed 消费者不可见。

这要求:

  • Transactional ID 前缀稳定且不冲突;
  • Kafka 事务超时大于最坏 Checkpoint 时间;
  • Operator UID 稳定;
  • Checkpoint 能持续成功,而不是长期失败。

四、Kafka 消费位置

Kafka Source 关闭自动提交,让 Offset 随 Checkpoint 提交:

java 复制代码
enable.auto.commit=false
commit.offsets.on.checkpoint=true

Consumer Group 决定"谁共享一份消费进度",Startup Mode 决定"没有可恢复状态时从哪里开始"。

Flink 自身从 Checkpoint 恢复时,优先使用 Checkpoint 中的 Source State;Group 的 committed Offset 更多用于没有相应 Flink 状态时的启动边界。

五、JDBC Buffer 为什么必须在 Checkpoint 前 Flush

如果 Checkpoint 成功时 Buffer 仍在内存,Kafka Offset 已前进,但数据尚未落库,重启后这部分记录可能无法再次读取。

因此 AbstractJdbcCdcSink.snapshotState() 会调用:

java 复制代码
@Override
public final void snapshotState(
        FunctionSnapshotContext context) throws Exception {
    runtime.flushGuard.flushForCheckpoint();
}

失败必须向上抛出,使 Checkpoint 失败。

六、一次 JDBC Flush 的事务顺序

java 复制代码
try (Connection conn = targetDs.getConnection()) {
    conn.setAutoCommit(false);
    try {
        for (RouteBatch batch : batches) {
            writer.write(conn, batch.route, batch.records);
        }

        conn.commit();
        clearCommittedBuffers();
    } catch (Exception e) {
        conn.rollback();
        throw e;
    }
}

必须遵守:

text 复制代码
先写全部路由
    ↓
全部成功后 Commit
    ↓
Commit 成功后清 Buffer

如果提前清 Buffer,Rollback 后将没有数据可重试。

七、为什么目标端仍可能重复

text 复制代码
数据库 Commit 成功
    ↓
Checkpoint 尚未完成
    ↓
TaskManager 故障
    ↓
从上一个成功 Checkpoint 重放

这批记录会再次写入。目标端必须保证:

  • Insert / Update 通过业务主键 Upsert;
  • Delete 重复执行不改变最终结果;
  • 主键缺失时明确失败;
  • 数据修正与不可变数据使用不同冲突策略。

八、顺序状态也必须恢复

SQLServer 保序不是无状态过滤。每个业务 Key 的最新 LSN、seqvalcommand_id 都是 Keyed State。

如果这部分状态没有进入 Checkpoint,任务恢复后旧事件可能重新被视为新事件。稳定 Key、稳定 Operator UID 和 Checkpoint 缺一不可。

九、Operator UID 为什么不能只用数字下标

第一版后期已经使用真实表清单 Hash 构建 UID;第二版继续把系统、算子角色、Topic 或表身份纳入 UID。

text 复制代码
错误:source-group-0
更稳妥:job + role + identities hash

UID 的目标不是永远不变,而是在逻辑身份相同的前提下稳定;拓扑身份改变时,应明确暴露状态不兼容。

十、必须做的故障注入

  1. Source 正常读取时强制终止 TaskManager;
  2. Kafka 事务未提交时触发失败;
  3. JDBC 批量执行中途抛错并验证 Rollback;
  4. 数据库 Commit 后、Checkpoint 完成前停止任务;
  5. 从 Checkpoint 和 committed Offset 分别恢复;
  6. 验证目标表无重复业务结果;
  7. 修改表分组后验证 UID 与状态兼容;
  8. HDFS 不可写时确认任务不会伪装成功。

十一、小结

text 复制代码
Checkpoint:保存可恢复状态
Kafka 事务:协调 Source-to-Kafka 消息提交
Offset:描述 Kafka 消费位置
JDBC 事务:保证一次 Flush 原子性
目标端幂等:承受故障后的重复执行

下一篇分析第二版最重要的代码重构:如何把第一版超过千行的 PostgresSink 拆成公共 JDBC 主流程和数据库差异 SPI。


相关推荐
智慧大脑搬运工10 分钟前
环保装备制造业高质量发展政策框架解析:揭榜挂帅机制与专精特新培育路径
大数据
老林说收银14 分钟前
溯引 GEO 优化系统落地实战指南
大数据·人工智能
Safeploy安策数据27 分钟前
服务器防勒索实战指南:从攻击链路到防御体系构建
大数据
huashengzsj38 分钟前
2026年WordPress建站公司推荐:哪些服务商更适合长期运营网站?
大数据·人工智能·云计算
BD_Marathon1 小时前
Hadoop组成
大数据·hadoop·分布式
whcyhhh1 小时前
头歌实践教学平台:数据科学与大数据技术导论(十八4)
大数据·开发语言·python
红色星际1 小时前
新石器走进无人配送车下半场
大数据·人工智能
FII工业富联科技服务1 小时前
工业AI Agent从单点应用到规模化落地:多智能体、Agentic Layer与制造运营协同架构解析
大数据·人工智能
时代分流1 小时前
供应商管理系统:SRM数字化采购协同方案
大数据·人工智能