去年有次线上排查让我印象很深。凌晨两点,做实时数仓的同事喊我,说 Kafka 消费延迟从几十秒一路涨到几十分钟,数据库里还多了一批重复订单。查到最后,问题根本不在 Kafka 自身,而是生产端把一个可重试异常当成了成功发送,消费端又没做幂等。Kafka 在大数据链路里几乎是默认的消息总线,但很多团队默认它天生就能保证不丢不重,其实分布式消息中间件从来不承诺这种“白给”的可靠性。
这篇内容聚焦 Kafka 消息可靠性的落地实践。我会按照一条消息从生产、存储到消费的完整路径,把真正会导致丢失或重复的节点逐个拆开,再结合大数据环境下的集群配置、监控和故障演练,讲清楚每个关键参数为什么这么设。适合正在维护 Kafka 集群的工程师、用 Kafka 做数据同步或实时计算的开发,以及所有不想在凌晨爬起来补数据的同学参考。
1. 先把“丢消息”拆到三个环节再谈保障
1.1 一条 Kafka 消息的一生,有三个最容易翻车的时间窗口
任何可靠性保障,前提都是先把“风险面”画清楚。一条 Kafka 消息从业务系统产生,到最终被消费方处理并写入目标存储,经过的位置其实很长。很多人排查问题时只盯着 broker 集群,但真实生产环境里,消息丢失高发在你可能完全想不到的地方。
第一个时间窗口是生产端发送之后、broker 写入并确认之前。客户端调用 send() 方法并不等于消息已经进入 Kafka,它只是把消息放进了本地的发送缓冲区。如果此时服务端无法写入,或者客户端发送模式太粗糙,消息就可能无声无息地消失。第二个窗口在 broker 端:Kafka 先写到操作系统页缓存,再异步刷盘;如果分区只有一份副本,磁盘故障或进程崩溃时,未刷盘的数据就会丢。即便有多副本,如果 Leader 选举策略设置不当,也可能出现“截断数据”的灾难。第三个窗口最容易被忽略——消费端已经拉取到消息,但还没处理完或还没提交位移,一旦发生重平衡或宕机,消息会再次被消费,或者在 at most once 语义下直接跳过。
把这三个窗口记在心里之后,你再去看任何一篇 Kafka 可靠性参数的文档,都会觉得清晰很多。生产端、Broker、消费端各负责一段可靠性,任何一端偷懒,整条链路的保障就是零。
1.2 大数据环境下的可靠性陷阱:默认配置是给“单机玩耍”用的
小规模测试环境里,副本数不够、ack 配置随意、消费端自动提交,这些坑通常不会立刻暴露。但在每天几亿甚至几十亿消息的大数据集群里,流量洪峰、broker 滚动重启、磁盘故障、消费者处理抖动都会被放大成可见事故。
你随便起一个本地 Kafka,用默认配置跑 Demo,消息基本不会感觉丢。可一旦进入多节点生产环境,以下几种情况会像定时炸弹一样埋伏着:自动创建 topic 时副本数只有 1,某台 broker 磁盘坏了,线上立刻出现分区不可用,历史消息无法消费;某个消费者处理一条记录需要查一次外部数据库,耗时一长触发 max.poll.interval.ms 超时,被踢出消费组后触发频繁重平衡,机器一多,全链路抖动成雪崩;数据量上来之后 broker 的请求处理线程被打满,ISR 不断收缩,如果这个阶段再碰上 Leader 宕机,选出来的新副本可能差着十万八千里。
这篇文章接下来要做的,就是把这三段分别配置到真正可靠的状态,并解释这些配置背后“为什么是它”。只有理解了参数之间的关系,你才能在集群规模翻倍时做出正确的取舍,而不是死抄别人博客里的配置。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 生产端可靠性:acks、重试与幂等是三个不能割裂的旋钮
2.1 acks 的三个档位不是“快慢问题”,而是“丢不丢”的问题
生产端最基础的可靠性控制,就是 Producer 的 acks 参数。它决定了消息发送后,什么样的服务端反馈对客户端来说才算“发送成功”。
先看最危险的 acks=0。这个模式下,生产端把消息扔进 socket 缓冲区就不管了,既不关心 broker 是否收到,也不关心是否写入成功。它的吞吐最高,但代价是任何网络闪断、broker 繁忙、分区无 Leader,都会让消息直接蒸发。很多把 Kafka 当“日志管道”用的采集程序为了追求极致吞吐会这么配,但我强烈建议,除非你能明确接受数据丢失,否则别碰。
acks=1 是很多业务系统默认或常用的级别。Leader 收到消息并写入自己的本地日志后,就向生产端返回成功。注意,这里还没等 Follower 副本确认。如果分区只有单副本,Leader 所在的 broker 一旦宕机,消息就随磁盘损坏或日志丢失而消失。即使有多副本,在 Leader 返回成功到 Follower 完成同步之间的窗口期,宕机同样可能造成数据缺口。
acks=all 表示 Leader 需要等待当前 ISR 集合中的所有副本都确认写入后才算成功。这才是真正意义上的“集群确认”。在关键交易、订单、对账等不允许丢消息的大数据链路里,acks=all 应该是唯一选择。付出的代价是单次请求的延迟更高,但相比补数据的代价,这点延迟完全可以接受。
2.2 acks=all 并不等于绝对安全:必须配合 min.insync.replicas
关于 acks=all,有个非常容易被误解的点:如果 ISR 中只剩下 Leader 自己,acks=all 也会退化成 acks=1 的效果。因为“等所有 ISR 副本确认”在 ISR 只有一个成员时,等到的其实只有 Leader 自己的确认。
打个比方:你要求快递必须当面签收,但整个仓库只剩一个仓库管理员了,他签了字,并不代表包裹有第二个人知道。为了堵住这个漏洞,需要设置 min.insync.r
