实时数据湖 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、seqval、command_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。


相关推荐
2601_9556624615 小时前
搞笑视频配音怎么做?如何选择有辨识度的声音
大数据·人工智能·音视频·语音识别·媒体
云上先途115 小时前
企业 AI 服务部署周期多久,会不会影响原有业务运转?
大数据·运维·人工智能
汤姆宝库15 小时前
收官深拆:第一性原理、产业电网与未来推演
大数据·人工智能·算法
会议咨询16 小时前
2027年智慧能源、大数据应用与人工智能国际会议(SEBA 2027)
大数据·人工智能·能源
SelectDB17 小时前
安全评审卡了三周,我把部署形态的四种选项重新排了一遍
大数据·数据库·云原生
注塑机数据采集联网17 小时前
注塑机生产管控MES系统应用
大数据·运维·服务器·人工智能·物联网
2601_9520477920 小时前
聚水潭售后退仓单同步到金蝶云·星空退货单:轻易云实战教程
大数据·机器人·钉钉·制造
QYRese20 小时前
全球氢气压缩机市场发展前景报告:2026-2032年CAGR5.5%
大数据
四季豆3320 小时前
AI时代中小企业如何低成本提升数据和知识质量?
大数据·人工智能·数据挖掘
2601_9672127221 小时前
轨道式插座安全结构、认证要求与场景参数技术说明
大数据·经验分享·安全