Kafka 如何保证消息不丢失?
结论:Kafka 的不丢要三段同时成立——生产端确认(Acknowledgement)到位(
acks=all+ 幂等 + 重试)、Broker 端副本够且不允许脏选举(副本 3、min.insync.replicas=2、unclean.leader.election=false)、消费端处理成功才提交位点。Kafka 的默认值在这三处都"偏性能",不显式改就会丢。
一、丢失点与默认值陷阱
Producer ──① 缓冲区满/超时被丢弃──> Leader ──② 只写页缓存,未同步到副本 ──> Follower
└──③ ISR 缩到 1 时 acks=all 形同虚设
Consumer ──④ 先提交位点后处理 ──> 处理失败,位点已前移 ──> 丢
| 配置 | 默认值 | 风险 | 建议 |
|---|---|---|---|
acks | all(3.0 起) | 老版本默认 1,Leader 挂即丢 | 显式写 all |
enable.idempotence | true(3.0 起) | 老版本关闭,重试产生重复/乱序 | 显式开启 |
retries | 2147483647 | 配合 delivery.timeout.ms 才安全 | 保持默认,调大总超时 |
min.insync.replicas | 1 | ISR 只剩 Leader 时写入仍成功 | 设为 2 |
unclean.leader.election.enable | true | 落后副本被选为 Leader,静默丢数据 | 设为 false |
enable.auto.commit | true | 提交时机不可控 | 设为 false,手动提交 |
auto.offset.reset | latest | 位点丢失后从最新开始,中间消息被跳过 | 关键业务设 earliest 并告警 |
二、三段的具体配置
# 生产端
acks=all
enable.idempotence=true
max.in.flight.requests.per.connection=5
delivery.timeout.ms=120000
buffer.memory=67108864
# Broker 端(server.properties)
default.replication.factor=3
min.insync.replicas=2
unclean.leader.election.enable=false
log.flush.interval.messages=10000 # 默认靠副本冗余,不必每条 fsync
# 消费端
enable.auto.commit=false
auto.offset.reset=earliest
max.poll.records=200 # 控制单批处理时长,避免超时被踢
三、消费端提交位点的正确姿势
while (running) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(200));
for (ConsumerRecord<String, String> r : records) {
biz.handle(r); // 幂等处理,失败抛异常
}
consumer.commitSync(); // 全部成功后再提交
}
两个反例:commitSync 放在循环外但在处理之前(先提交后处理);用 enable.auto.commit=true 却指望"处理失败不丢"。自动化提交由后台线程按时间触发,与业务处理结果毫无关系。
四、验证与演练
| 检查项 | 命令/方法 | 期望结果 |
|---|---|---|
| 副本与 ISR | kafka-topics.sh --describe | ISR 数量 = 副本数,无 -1 |
| 消费积压 | kafka-consumer-groups.sh --describe | LAG 稳定,不持续增长 |
| 副本一致性 | kafka-replica-verification.sh | 无最大滞后告警 |
| 断电演练 | 直接 kill -9 一个 Broker | 生产端无 NotEnoughReplicas 之外的失败,数据可读 |
| 脏选举 | 手动停两个副本再写入 | 应抛 NotEnoughReplicasException 而不是成功 |
五、常见追问
- acks=all 就落盘了吗? 没有。Broker 写入页缓存即返回,靠的是多副本冗余而不是 fsync;真要防整机掉电,得配
log.flush.interval.messages=1,代价是吞吐骤降。 - 副本数 3 就够了吗? 还需
min.insync.replicas=2,否则 ISR 缩到 1 时acks=all退化成acks=1,写入照常成功但只有一份数据。 - 为什么说 Kafka 不能 100% 不丢? 见下一章:涉及页缓存、跨机房、消费者进程崩溃等边界,工程上只能做到"可度量、可补偿"。
- 重平衡会丢消息吗? 会丢"已拉取未提交"的消息(如果先提交后处理),也会重复投递(处理完成未提交),本质仍是提交时机问题。
小结
- 不丢是三段配置的乘积,且默认值在可靠性上普遍偏弱,必须显式声明。
acks=all+min.insync.replicas=2+ 副本 3 + 禁止脏选举,是 Broker 侧的底线组合。- 消费端只认"处理成功再提交",自动提交与顺序颠倒都会丢。
- 用 describe、lag、kill -9 演练来验证,而不是只看配置文件。