1 概念
Kafka 的幂等性确保生产者重复发送相同消息时,broker的topic的分区中只会保存一份
。此特性通过生产者配置实现,与 Broker 无关。
2 生产者端配置
关键参数:enable.idempotence,默认值是false,开启幂等性时需显式设置为true
producer幂等性配置如下:
from confluent_kafka import Producer
producer_config = {
'bootstrap.servers': 'localhost:9092',
'enable.idempotence': True, # 启用幂等性
'acks': 'all', # 必须配合 acks=all 使用
'retries': 5, # 重试次数
'client.id': 'idempotent-producer'
}
producer = Producer(producer_config)
3 Broker 端要求
版本兼容性:Kafka 0.11.0.0 及以上版本支持幂等性。
无需额外配置:Broker 端无需修改 server.properties,只需确保版本兼容。
4 原理
我们在客户端参数显示设置enable.idempotence=true就会开启生产者幂等消息传递
1 每条消息都有一个主键,这个主键由 <PID, Partition, SeqNumber>组成。
- PID:ProducerID,每个生产者启动时,Kafka 都会给它分配一个 ID,ProducerID 是生产者的唯一标识,PID会被事务协调器保存到__transaction_state主题中[即使客户端只启用幂等性没有启用事务,事务协调器也会给生产者分配一个PID并保持到__transaction_state主题中]。。
- Partition:消息需要发往的分区号。
- SeqNumber:生产者,他会记录自己所发送的消息,给他们分配一个自增的 ID,这个 ID 就是 SeqNumber,是该消息的唯一标识,每发送一条消息,序列号加 1。
2 对于主键相同的数据,kafka 是不会重复持久化的,它只会接收一条。
- broker端会给每一对<PID,分区>维护一个序列号,对于收到的每一条消息,只有当它的序列号的值(SN_new)正好比broker端中维护的对应序列号的值(SN_old)大1,broker才会接收该消息。如果 SN_new < SN_old + 1,说明消息被重复写入,broker会将该消息丢弃。否则,说明中间有数据尚未写入,暗示可能有消息丢失,对应生产者会抛出 OutOfOrderSequenceException 异常
注意:序列号实现幂等只是针对每一对<PID,分区>,即Kafka的幂等性只能保证单个生产者会话(session)中单分区的消息不重复;当kafka 挂掉,重新给生产者分配了 PID,还是有可能产生重复的数据
为什么说幂等性只能保证单会话,单分区消息不重复?[仅是个人理解,不一定正确]
- 单会话:当broker收到消息后,如果生产者崩溃了,生产者重启后,会冲洗分配给生产者一个pid,而不是之前的那一个,序列号也会从0开始,broker会认为是另一条消息,发生了同分消息重复,因此幂等性只能保证单会话不重复;
- 单分区:当broker收到消息后,由于网络波动,生产者没有收到来自broker的确认消息,会重复发送,此时若发生以下几种情况,都可能导致跨分区消息重复,因此只能幂等性只能保证单分区不重复
- 分区数变化:例如,主题分区数增加,导致消息被重新分配到新分区。
- 键的哈希值改变:若消息带有键(key),且重试时键的哈希值因某些原因改变(如键本身被修改),可能导致分区分配不同。
- 自定义分区器逻辑:若使用自定义分区器(如轮询策略),重试时可能因分区顺序改变而发送到不同分区。
5 关键验证步骤
1. 生产者日志验证
-
启用调试日志:在生产者配置中添加
debug='producer'
。from confluent_kafka import Producer # 生产者配置 producer_config = { 'bootstrap.servers': 'localhost:9092', # Kafka Broker 地址 'debug': 'producer', # 启用生产者调试日志 # 其他配置(可选) 'acks': 'all', # 消息确认机制 'retries': 3 # 重试次数 } # 创建生产者实例 producer = Producer(producer_config) # 发送消息(示例) producer.produce('my_topic', key='key', value='message') producer.flush()
关键说明:
debug 参数:设置为 ‘producer’ 会输出生产者相关的调试日志(如消息发送、分区选择、网络交互等)。
日志输出:调试日志默认输出到 stderr,可通过 Python 的 logging 模块重定向到文件或其他处理器。
多组件调试:若需同时调试多个组件(如 Broker、Topic),可用逗号分隔:debug=‘producer,broker,topic,network’。[没验证过,具体用时可以去查一下相关资料] -
观察日志:
[2025-07-19 10:00:00,000] DEBUG Setting producer IDEMPOTENT (kafka.producer.KafkaProducer)
2. Broker 日志验证
- 检查去重日志:
[2025-07-19 10:00:01,000] INFO Received message with PID=12345, Partition=0, SeqNumber=1 (kafka.broker.LogManager) [2025-07-19 10:00:02,000] WARN Duplicate message detected for PID=12345, Partition=0, SeqNumber=1 (kafka.broker.LogManager)
3. 消费者测试
- 模拟重试:
- 发送消息后,立即断开生产者网络。
- 生产者重试发送相同消息。
- 消费者确认仅接收一次消息。
补充:
与 max.in.flight.requests.per.connection 的关联:
启用幂等性后,Kafka 会自动将 max.in.flight.requests.per.connection 设为 1,以确保消息顺序。若需更高并发,可显式设置该参数为更大值(如 5),但可能破坏顺序保证。
max.in.flight.requests.per.connection 是 Kafka 生产者客户端配置参数,用于控制生产者与单个 Broker 连接中未确认请求的最大数量。简单来说,它限制了生产者在等待之前发送的消息确认(ACK)时,可以同时向同一个 Broker 发送的未完成请求数量。
producer_config['max.in.flight.requests.per.connection'] = 5