端到端精确一次是什么
端到端精确一次要求三个环节同时成立:数据源可以按位点重放,中间状态有检查点兜底,写入端支持事务提交或按主键幂等。任意一环缺失,语义就会降级——源不可重放会丢数据,检查点不可用会回退状态,sink 非幂等会在恢复后重复写入。因此实现方式通常是「可重放源加检查点加两阶段提交 sink」,或者在湖表上按主键覆盖写入并保留版本列,让重复消费不产生重复行。
基本信息
| 三要素 | 可重放的源、检查点状态、事务或幂等写入 |
|---|---|
| 源的要求 | 支持按位点重读,如消息队列位点、文件清单 |
| 状态要求 | 检查点开启且持续成功,恢复时回到最近成功点 |
| 写入方式 | 两阶段提交事务,或按主键幂等覆盖 |
| 常见组合 | 消息队列事务生产者、湖表基于检查点的提交 sink |
| 验证方式 | 故障注入:随机终止执行节点,比对结果是否不重不漏 |
| 降级说明 | 任一环不满足,只能退化为至少一次或最多一次 |
典型使用场景
- 消息队列到湖表:源可重放、检查点兜底、湖表 sink 走两阶段提交
- 数据库变更入仓:变更位点可追踪,写入按主键覆盖保证幂等
- 双流 join 结果落地:连接状态保存在检查点中,写出用事务提交
- 压测验证:在测试环境反复注入故障,确认恢复后结果不重不漏
- 多次回放:保留版本列,重复消费时按版本取最新,结果稳定
实现要点示意
实现要点示意
// 源可重放加检查点加事务型 sink,三者缺一不可
env.enableCheckpointing(60_000L, CheckpointingMode.EXACTLY_ONCE); // 检查点决定提交时机
source.setStartFromGroupOffsets(); // 源按位点重读
stream.sinkTo(icebergSink) // 湖表 sink 基于检查点两阶段提交
.uid('iceberg-sink'); // 固定 uid,便于从检查点恢复
// 幂等替代方案:按业务主键覆盖并保留版本列
// MERGE INTO target t USING staging s ON t.order_id = s.order_id
// WHEN MATCHED THEN UPDATE SET ... WHEN NOT MATCHED THEN INSERT ...
// 验证:注入故障后比对总数与金额,重复与缺失都应被检出三要素检查表
| 环节 | 要求 | 不满足时的表现 |
|---|---|---|
| 数据源 | 支持按位点重读 | 恢复后只能跳过,数据缺失 |
| 中间状态 | 检查点开启且持续成功 | 重启后状态回退,结果重复或错乱 |
| 写入端 | 事务提交或按主键幂等 | 重复消费产生重复行,指标翻倍 |
幂等写入的关键是主键唯一且可复现:按业务主键覆盖并保留版本号,重复消费不会产生重复行;主键由当前时间或随机数生成时,重放会得到不同结果,幂等性直接失效。
只开检查点而没有幂等 sink,故障恢复后的重复数据会直接落到下游;反过来,源头不支持重放时,检查点再稳定也补不回已经跳过的那段数据。