生产事故救火指南: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 难管理)。
  • 正确做法(应急大招)临时扩容法

步骤如下:

  1. 停掉现有的慢速 Consumer。
  2. 新建一个 临时 Topic,配置 50 个分区(是原来的 10 倍)。
  3. 写一个中转 Consumer(搬运工),只做一件事:把积压 Topic 的数据读出来,不做任何业务逻辑,直接写入临时 Topic。
    • 速度极快,因为不处理业务。
  4. 部署 50 个业务 Consumer 去消费临时 Topic。
  5. 积压解决后,恢复原状。
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.bytesmax.poll.records,一次拉取一批,合并 DB 请求(如 INSERT INTO ... VALUES (...), (...))。
  • 异步解耦:如果消费逻辑里有耗时的 HTTP 请求,不要同步等待,改用 Netty/CompletableFuture 异步处理(需注意 Offset 提交顺序)。
  • 排查倾斜:查看是否某个 Partition 积压特别多?可能是 Key 设计不合理导致数据倾斜

第二部分:铜墙铁壁 —— 如何保证数据不丢?

要保证数据不丢,必须覆盖生产端、服务端、消费端这三个环节。任何一环掉链子,数据都会丢。

1. 生产端(Producer):不收到回复决不罢休

这是数据进入系统的第一道关卡。

  • 核心配置acks = all (或 -1)
    • 0:发后即忘,最快但最不安全。
    • 1:Leader 收到就返回,如果 Leader 刚落盘还没同步给 Follower 就挂了,数据丢失。
    • allLeader + 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=allmin.insync.replicas=1,当只有 Leader 活着时,它也会返回成功。一旦 Leader 挂了,数据照样丢。所以必须强制要求至少 2 个副本写入成功。
  • 选举策略unclean.leader.election.enable = false
    • 禁止非 ISR 节点的“落后副本”成为 Leader。宁可服务不可用,不可数据乱丢。

3. 消费端(Consumer):握紧手中的 Offset

消费端丢数据通常是因为“自动提交(Auto Commit)”惹的祸。

  • 核心动作关闭自动提交 (enable.auto.commit = false)。
  • 逻辑顺序
    1. 拉取数据。
    2. 执行业务逻辑(写入 DB、计算等)。
    3. 确认业务成功后,手动提交 Offset (commitSynccommitAsync)。

反例:如果开启自动提交,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

建议

  1. 监控是底线:必须监控 Consumer Lag(推荐使用 Prometheus + Grafana 或 Kafka Eagle)。不要等用户投诉才发现积压。
  2. 权衡的艺术
    • 追求零丢失,吞吐量必然下降(因为要等副本同步、要手动提交)。
    • 追求高性能,必然承担数据丢失风险。
    • 架构师的职责,就是根据业务场景(是订单金额还是日志流水),在两者之间找到平衡点。

希望这篇文章能成为你生产环境的“护身符”!如果觉得有用,欢迎关注和收藏。

posted on 2025-11-26 21:31  滚动的蛋  阅读(1)  评论(0)    收藏  举报

导航