Kafka 如何保证消息不丢?acks 与 ISR 到底什么关系?

#中间件#面试八股

Kafka 如何保证消息不丢?acks 与 ISR 到底什么关系?

"Kafka 不丢消息"不是靠一个开关,而是生产端、Broker、消费端三段各自配置共同保证的。 绝大多数丢消息事故,都是某一段用了默认配置却没意识到默认值很弱。

1. 先分清"三个位置"上的丢失

Producer ──① 发送失败/重试耗尽──> Broker(Leader)──② 未刷盘/未同步副本──> OS PageCache / Disk
                                                        └── ISR 副本
Consumer ──③ 先提交 offset 后处理业务(处理失败但 offset 已前移)──> 丢失

2. 生产端:acks 是关键

acks语义丢失风险
0发出去就认为成功(不等 Broker 响应)极高(网络抖动就丢)
1Leader 写入本地日志即返回Leader 挂掉、未同步到副本时丢
all(-1)所有 ISR 副本写入才返回最低(配合 min.insync.replicas)
acks=all
retries=2147483647          # 用很大的值 + 幂等,让重试尽可能发生
enable.idempotence=true     # 幂等:避免重试导致重复
max.in.flight.requests.per.connection=5   # 幂等开启后 5 以内可保序
delivery.timeout.ms=120000  # 总超时(含重试);不要设得太小

三个要点:

  1. acks=all 只保证"ISR 都收到",不等于"落盘"——Broker 默认写 PageCache 就返回;
  2. 幂等 + acks=all 是生产端最佳组合:既不丢(重试)又不重复(幂等),且分区内保序;
  3. retries 与 delivery.timeout.ms 要配合:重试次数再多,总超时到点也会失败。

3. Broker:ISR 与 min.insync.replicas

ISR(In-Sync Replicas) = 与 Leader 保持同步的副本集合(含 Leader)。 acks=all 的语义是"ISR 内所有副本都写成功",所以ISR 缩小到只剩 Leader 时,acks=all 退化成 acks=1。

# 主题级
min.insync.replicas=2        # 至少 2 个副本才算写成功
replication.factor=3         # 副本数 3,容忍 1 个挂掉
unclean.leader.election.enable=false   # 不允许非 ISR 副本当 Leader(否则会丢数据)

unclean.leader.election 是最容易被忽略的一条:设为 true 时,ISR 全挂后允许落后副本当 Leader, 可用性提升但会静默丢数据。金融类场景必须为 false。

刷盘策略:

# 默认:flush 由 OS 决定(PageCache),性能好但掉电可能丢
log.flush.interval.messages=10000
log.flush.interval.ms=1000

其实靠副本冗余比靠 fsync 更划算:3 副本 + acks=all 已能容忍单机故障; 频繁 fsync 会显著降低吞吐。真要求"断电也不丢",再叠加 log.flush 配置。

4. 消费端:先处理还是先提交

这是业务侧最常写错的地方:

// 错误:先提交 offset,再处理业务
consumer.commitSync();
process(record);          // 处理失败 → 消息永久丢失

// 正确:处理成功后再提交
process(record);
consumer.commitSync();

更稳的模式是幂等消费 + 至少一次:处理可能重复,但业务用唯一键去重, 这样"重复"可接受、"丢失"不可接受。

提交方式风险
自动提交(enable.auto.commit=true)提交时机不可控,可能"未处理就提交"
手动同步提交安全但阻塞,吞吐略低
手动异步提交吞吐高,但失败要能回滚/重试

5. 一张"不丢配置"清单

段配置建议值
Produceracksall
Producerenable.idempotencetrue
Producerretries很大(配合幂等)
Brokerreplication.factor3
Brokermin.insync.replicas2
Brokerunclean.leader.election.enablefalse
Brokerlog.flush.*按可靠性要求取舍
Consumerenable.auto.commitfalse
Consumer提交时机业务处理成功后

6. 还有两类"看起来不丢其实丢"的情况

  1. 消费位点被重置:auto.offset.reset=earliest/latest 只在"无位点"时生效, 但如果消费者组被误删、位点过期(offsets.retention.minutes),重连后可能从最新开始 —— 中间消息被跳过;
  2. 重平衡期间:分区被收回后,未提交位点的消息会被重新投递(重复), 而如果用了"处理完才提交"却在新消费者上处理失败,就可能反复卡住。用 max.poll.interval.ms 与处理时长匹配,避免"处理太久被踢出组"。

小结

Kafka 的可靠性是端到端配置问题,不是单点开关:

  • 生产端:acks=all + 幂等 + 足够重试;
  • Broker:3 副本 + min.insync.replicas=2 + 禁止非 ISR 选主;
  • 消费端:处理成功再提交 + 幂等去重。

三处任一为默认值,都可能出现"配置了 acks=all 还是丢"的事故。

笔记加载中…