kafka--基础知识点--5.2--最多一次、至少一次、精确一次
个人理解,仅供参考。一个消息的传递可以分两个过程,a) producer发送消息到 broker,b) consumer从broker读消息并发送。因此对于三种消息的传递策略要分两个阶段来看:
- a) producer发送消息到 broker,对于producer来说,有自己的最多一次、至少一次、精确一次策略;
- b) consumer从broker读消息并发送,对于consumer来说,也有自己的最多一次、至少一次、精确一次策略。
1 producer端
1.1 ACK策略
Kafka 通过 acks
参数控制消息确认机制,可实现三种消息传递语义:至多一次(At-Most-Once)、至少一次(At-Least-Once) 和 恰好一次(Exactly-Once)。以下是详细对应关系:
kafka–基础知识点–5.1–ACK机制
1.1.1 至多一次(At-Most-Once)
- 语义:消息可能丢失,但绝不会重复。
- ACK策略:
acks=0
- 生产者不等待 Broker 确认,发送后立即继续下一条消息。
- 若 Broker 未收到消息或崩溃,消息丢失且不会重试。
- 配置要求:
- 生产者:
acks=0
,retries=0
(禁用重试)。
- 生产者:
- 适用场景:实时性要求高但允许少量数据丢失的场景(如日志采集)。
1.1.2 至少一次(At-Least-Once)
- 语义:消息绝不会丢失,但可能重复。
- ACK策略:
acks=all
acks=all
:生产者等待所有 ISR 副本确认,确保消息持久化,结合重试可避免丢失。
- 配置要求:
- 生产者:
acks=all
,retries=Integer.MAX_VALUE
(无限重试)。
- 生产者:
- 适用场景:允许重复但不容忍丢失的场景(如支付状态更新)。
1.1.3 精确一次(Exactly-Once)
- 语义:消息不丢失、不重复。
- ACK策略:
acks=all
+ 幂等性 + 事务- 幂等性:通过
enable.idempotence=true
确保重复消息不会导致数据不一致。kafka–基础知识点–5.2–producer幂等性 - 事务:跨分区原子写入,配合消费者隔离级别
isolation.level=read_committed
。kafka–基础知识点–5.3–producer事务
- 幂等性:通过
- 配置要求:
- 生产者:
acks=all
,retries=Integer.MAX_VALUE
,enable.idempotence=true
。
- 生产者:
- 适用场景:要求严格一致性的场景(如金融交易、流处理)。
2 consumer端
2.1 读取策略
isolation.level=read_committed
:当消费者设置该参数时,表示消费者仅消费已提交的事务消息。该参数只有当生产者使用事务时消费者设置该参数才有效。
2.2 提交策略
2.2.1 自动提交
如果消费者设置为自动提交(enable.auto.commit参数设置为true),则意味着偏移量在poll()函数成功返回时已经提交了,而不管消费者是否对消息是否已经成功进行了业务处理,因此如果消费者在处理此消息时崩溃,会导致消息丢失。类似于 手动提交的至多一次
。
2.2.2 手动提交
2.2.2.1. 至多一次 (At-Most-Once):
行为: 消费者读取消息后,先提交位移,然后再进行业务处理。
风险: 如果业务处理逻辑在提交位移之后失败,这条消息就永远不会被再次处理了(因为位移已经前移)。导致消息丢失。
2.2.2.2 至少一次 (At-Least-Once):
行为: 消费者读取消息后,先进行业务处理,处理成功
后再提交位移。
风险: 如果业务处理成功,但在提交位移之前消费者崩溃了,当消费者恢复后,它会从上一次提交的位移重新消费,导致消息被重复处理。导致消息重复。
2.2.2.3 精确一次 (Exactly-Once)
2.2.2.3.1 幂等性消费 (Idempotent Consumption) - 推荐且最常用
思路:承认消息可能会被重复传递,但从业务逻辑上保证重复执行不会产生负面效果。
做法:在消费者的处理逻辑中,设计幂等性。例如:
为每条消息生成一个唯一 ID(可以是消息key,或自定义UUID)。
在处理前,先检查这个 ID 是否已经被处理过(比如在数据库里查一下)。
如果已处理,就直接跳过并提交位移(视为成功);如果未处理,则执行业务逻辑。
这是最有效、最通用的方法,因为它不依赖于任何特定技术,而是从业务设计上根本性地解决问题。
例如:
a) 对于流程中的消息,每条消息中包含唯一id,比如业务id,在数据库中将业务id作为Unique key,插入重复时会报duplicate key异常,不会导致数据库中出现脏数据
b) Redis中使用set存储业务id,天然幂等性
c) 如果不是上面两个场景,需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下消费过吗?如果没有消费过,就执行相应业务进行处理,然后这个 id 写 Redis,最后提交偏移
。如果消费过了,那如果消费过了,那就别处理了,保证不重复处理相同的消息即可
2.2.2.3.2 事务性输出 (Transactional Output) / 两阶段提交 (2PC) - 复杂且受限
思路:将消费者的“业务处理”和“位移提交”绑定为一个分布式事务。
做法:例如,使用 Kafka 的事务性生产者,将处理结果和位移提交到外部系统(如另一个Kafka主题)的操作放在一个事务里。但这通常需要外部系统(如数据库)也支持参与 Kafka 事务(通过 Kafka Connect),实现复杂度非常高,性能和可用性也会受影响。不推荐普通应用使用。