生产事故救火指南:Kafka 消息积压了怎么办?如何保证数据一条不丢?
生产事故救火指南:Kafka 消息积压了怎么办?如何保证数据一条不丢?
摘要:在大数据与微服务架构中,消息队列(MQ)是血管,数据是血液。但血管会堵(积压),血液会漏(丢数据)。本文将以 Kafka 为例,从“应急止血”到“长期调优”,深度拆解消息积压的解决方案,并奉上一份经过生产环境验证的零丢失配置清单。
第一部分:洪水猛兽 —— 消息积压(Lag)怎么办?
当你收到报警 Consumer Lag > 100,000,000 时,千万别慌。积压的本质只有一个:消费速度 < 生产速度。
解决积压分为“应急方案”和“根治方案”。
1. 应急方案:教科书里不教的“搬运工”战术
如果积压导致业务瘫痪,常规的优化代码已经来不及了。此时你需要“以空间换时间”。
场景 A:Broker 分区数够用,但 Consumer 数量少
- 动作:立马增加 Consumer 实例数量,直到
Consumer 数量 = Partition 数量。 - 原理:Kafka 的并行度由 Partition 决定,多出的 Consumer 只会空转,所以加到和分区数相等即可。
场景 B:Broker 分区数极少,Consumer 已经加满(最棘手!)
假设 Topic 只有 5 个分区,积压了 1 亿条数据。你开了 5 个 Consumer 还是处理不过来。
- 错误做法:在 Consumer 内部搞多线程处理(容易丢数据,Offset 难管理)。
- 正确做法(应急大招):临时扩容法。
步骤如下:
- 停掉现有的慢速 Consumer。
- 新建一个 临时 Topic,配置 50 个分区(是原来的 10 倍)。
- 写一个中转 Consumer(搬运工),只做一件事:把积压 Topic 的数据读出来,不做任何业务逻辑,直接写入临时 Topic。
- 速度极快,因为不处理业务。
- 部署 50 个业务 Consumer 去消费临时 Topic。
- 积压解决后,恢复原状。
graph LR
%% 样式
classDef red fill:#ffcdd2,stroke:#b71c1c;
classDef green fill:#c8e6c9,stroke:#2e7d32;
classDef blue fill:#bbdefb,stroke:#1565c0;
subgraph Before [积压状态]
Source[原始 Topic<br/>5 分区]:::red
SlowC[业务 Consumer<br/>处理慢/积压中]:::red
Source --> SlowC
end
subgraph Emergency [应急扩容方案]
Source2[原始 Topic<br/>5 分区]:::red
Mover[中转 Consumer<br/>只搬运/速度快]:::blue
Temp[临时 Topic<br/>50 分区]:::green
FastC[50个 业务 Consumer]:::green
Source2 --> Mover --> Temp --> FastC
end
Before -.-> Emergency
2. 根治方案:代码与架构调优
- 批量消费:调整
fetch.min.bytes和max.poll.records,一次拉取一批,合并 DB 请求(如INSERT INTO ... VALUES (...), (...))。 - 异步解耦:如果消费逻辑里有耗时的 HTTP 请求,不要同步等待,改用 Netty/CompletableFuture 异步处理(需注意 Offset 提交顺序)。
- 排查倾斜:查看是否某个 Partition 积压特别多?可能是 Key 设计不合理导致数据倾斜。
第二部分:铜墙铁壁 —— 如何保证数据不丢?
要保证数据不丢,必须覆盖生产端、服务端、消费端这三个环节。任何一环掉链子,数据都会丢。
1. 生产端(Producer):不收到回复决不罢休
这是数据进入系统的第一道关卡。
- 核心配置:
acks = all(或-1)0:发后即忘,最快但最不安全。1:Leader 收到就返回,如果 Leader 刚落盘还没同步给 Follower 就挂了,数据丢失。all:Leader + ISR(所有同步副本) 都收到数据才返回 ACK。
- 重试机制:
retries > 0(建议Integer.MAX_VALUE)。 - 幂等性:
enable.idempotence = true。这能保证在重试时不会产生重复数据,且保证单会话顺序。
2. 服务端(Broker):哪怕挂了一个节点也不能丢
Kafka 的高可用依赖副本机制。
- 副本因子:
replication.factor >= 3。标准生产配置,保证 1 台挂了还有 2 台,2 台挂了还有 1 台。 - 最小同步副本:
min.insync.replicas > 1(通常设为 2)。- 重要陷阱:如果
acks=all但min.insync.replicas=1,当只有 Leader 活着时,它也会返回成功。一旦 Leader 挂了,数据照样丢。所以必须强制要求至少 2 个副本写入成功。
- 重要陷阱:如果
- 选举策略:
unclean.leader.election.enable = false。- 禁止非 ISR 节点的“落后副本”成为 Leader。宁可服务不可用,不可数据乱丢。
3. 消费端(Consumer):握紧手中的 Offset
消费端丢数据通常是因为“自动提交(Auto Commit)”惹的祸。
- 核心动作:关闭自动提交 (
enable.auto.commit = false)。 - 逻辑顺序:
- 拉取数据。
- 执行业务逻辑(写入 DB、计算等)。
- 确认业务成功后,手动提交 Offset (
commitSync或commitAsync)。
反例:如果开启自动提交,Consumer 拉到数据后立马自动提交了 Offset,结果业务代码报错了(抛异常)。Kafka 认为你消费过了,下次重启直接跳过,数据就丢了。
第三部分:终极总结图谱
为了方便记忆,我整理了一份Kafka 零丢失配置清单 (Checklist):
graph TB
%% 样式定义
classDef component fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef config fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,stroke-dasharray: 5 5;
subgraph Producer [① 生产端: 确保送达]
P[Producer Code]:::component
Conf1[acks = all<br/>retries = MAX<br/>enable.idempotence = true]:::config
P --- Conf1
end
subgraph Broker [② 服务端: 确保落盘与同步]
B[Broker Cluster]:::component
Conf2[replication.factor >= 3<br/>min.insync.replicas > 1<br/>unclean.leader.election = false]:::config
B --- Conf2
end
subgraph Consumer [③ 消费端: 确保处理完成]
C[Consumer Code]:::component
Conf3[enable.auto.commit = false<br/>业务成功后再 Manual Commit]:::config
C --- Conf3
end
Producer ==>|Network| Broker ==>|Network| Consumer
建议
- 监控是底线:必须监控 Consumer Lag(推荐使用 Prometheus + Grafana 或 Kafka Eagle)。不要等用户投诉才发现积压。
- 权衡的艺术:
- 追求零丢失,吞吐量必然下降(因为要等副本同步、要手动提交)。
- 追求高性能,必然承担数据丢失风险。
- 架构师的职责,就是根据业务场景(是订单金额还是日志流水),在两者之间找到平衡点。
希望这篇文章能成为你生产环境的“护身符”!如果觉得有用,欢迎关注和收藏。
浙公网安备 33010602011771号