三种投递语义是什么
投递语义描述的是「故障发生时,数据会不会丢、会不会重」。至多一次(at-most-once)先记录位点后处理,进程崩溃时消息不再重放,可能丢;至少一次(at-least-once)先处理成功再记位点,崩溃后从旧位点重放,可能重;精确一次(exactly-once)要求端到端不丢不重,通常靠事务提交或幂等写入实现。
端到端为什么最难
引擎内部做到不丢不重相对容易,难的是把保证延伸到外部系统。例如「Kafka 里的事务 + MySQL 里的一次 insert」跨了两个系统,中间没有共同的事务协调者,只能靠幂等键、事务表或分布式事务补偿来收敛。
基本信息
| 至多一次 | 故障时可能丢,实现最简单 |
|---|---|
| 至少一次 | 故障时可能重,工程上最常见 |
| 精确一次 | 不丢不重,需要事务或幂等 |
| 前提条件 | 位点记录的时机决定重复与丢失的边界 |
| 常见折衷 | 至少一次投递 + 下游按主键 upsert |
三种语义对照
| 语义 | 故障时的行为 | 实现手段 |
|---|---|---|
| at-most-once | 先提交位点再处理,崩溃后消息不再重放 | 自动提交 + 不重试,允许丢失的埋点场景 |
| at-least-once | 处理后提交位点,崩溃后重复消费 | 手动提交 + 下游幂等,数仓链路默认选择 |
| exactly-once | 端到端不丢不重 | Kafka 事务、两阶段提交 sink、主键 upsert |
典型使用场景
- 页面埋点、监控采样:少量丢失可接受,用至多一次换吞吐与简单
- 数仓 ODS 落地:至多一次会丢数据,必须选至少一次并保证下游可去重
- 账务、计费、库存扣减:重复入账后果严重,要么精确一次,要么业务层用幂等键兜住
- 指标类流任务:位点回退会造成重复累加,用主键 upsert 收敛到精确一次效果
- 审计与对账链路:宁可重复也不可丢,并在对账环节暴露重复
Kafka 消费侧相关配置
Kafka 消费侧相关配置
isolation.level=read_committed # 只读已提交的事务消息,读未提交会看到被回滚的数据
enable.auto.commit=false # 关掉自动提交,改为处理成功后手动提交
max.poll.records=500 # 控制单次 poll 量,缩短处理时间、降低再均衡风险至少一次 + 幂等落库的组合
至少一次 + 幂等落库的组合
-- 重复消费同一条事件时结果不变,等于把语义收敛到“不丢不重”
INSERT INTO dwd_order (order_id, amount, updated_at)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE amount = VALUES(amount), updated_at = VALUES(updated_at);幂等与事务不是一回事:幂等是「重复执行结果不变」,事务是「多次读写要么全成功要么全失败」。精确一次通常两者都要,先有幂等键,再谈事务边界。
引擎文档里宣称的精确一次,多数只覆盖引擎内部与其自带的 sink。一旦落到自写的数据库写入逻辑,保证就断了,别把产品的宣传语当成链路结论。