Kafka 消息积压怎么处理
1. 积压本质
1.1 积压定义
Kafka 消息积压,本质上是 生产速度持续高于消费速度,导致 Consumer Group 的 Lag 持续上升。
只要积压增长时间足够长,就会进一步引发消费延迟、数据过期、重平衡放大、下游雪崩等连锁问题。
积压本身不一定等于故障。
真正需要处理的是“积压是否还在增长、业务是否还能在可接受时间内追平”。
1.2 核心指标
1.2.1 必看指标
判断积压是否严重,至少要看 4 个指标:
-
Lag:当前未消费消息数。 -
Lag Trend:Lag是否持续增长。 -
Consume TPS:消费者每秒处理消息数。 -
Produce TPS:生产者每秒写入消息数。
其中,Lag 的绝对值不是唯一标准,增长趋势更关键。
例如,积压 100000 条但 3 分钟可追平,通常比积压 10000 条但持续扩大更安全。
1.2.2 追平时间
可以用一个简单公式估算恢复时间:
追平时间 = 当前积压量 ÷ (消费速率 - 生产速率)
当 消费速率 <= 生产速率 时,系统 永远无法自然追平,必须立即扩容、限流或降级。
1.3 常见根因
| 根因类别 | 典型现象 | 本质问题 |
|---|---|---|
| 消费能力不足 | Lag 持续上涨,消费者 CPU 不高 |
分区数不足或实例数不足 |
| 单条处理过慢 | 消费线程正常拉取,但业务执行慢 | 下游接口、数据库、计算逻辑成为瓶颈 |
| 批量参数不合理 | 单次 poll 太小,吞吐偏低 |
默认参数偏保守 |
| 重平衡频繁 | 消费者频繁离组、重入组 | 心跳超时、处理阻塞、实例抖动 |
| 坏消息卡死 | 同一分区 offset 长时间不推进 |
反序列化失败、业务异常重试死循环 |
| 集群侧瓶颈 | Broker、磁盘、网络异常 |
Kafka 服务端负载过高 |
| 上游突发流量 | 短时流量飙升后出现堆积 | 峰值容量设计不足 |
2. 排查路径
2.1 先确认影响范围
第一步不是改参数,而是先确认 影响的是单个 Topic、单个 Consumer Group,还是整个集群。
如果多个组同时积压,更可能是 Broker、网络、磁盘或共享下游的问题。
建议先执行以下命令查看消费组状态:
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group order-consumer-group \
--describe
重点看以下字段:
-
CURRENT-OFFSET -
LOG-END-OFFSET -
LAG -
CONSUMER-ID -
HOST -
CLIENT-ID
如果发现 只有少数分区积压很高,通常说明存在数据倾斜或某个分区被慢消息拖住。
如果 所有分区都高,通常说明整体消费能力不足。
2.2 再定位消费端瓶颈
2.2.1 判断是“拉不动”还是“处理不动”
可以把消费过程拆成两段:
-
从
Kafka拉取消息。 -
执行业务处理并提交
offset。
排查时要先区分瓶颈在哪一段。
如果 poll 很快但提交慢,问题一般在业务侧。
如果 poll 本身拿不到足够数据,则更可能是参数、分区、网络或 Broker 问题。
2.2.2 重点检查业务链路
业务处理慢,常见有以下几类:
-
单条消息触发远程调用,且串行执行。
-
写数据库存在慢 SQL、锁等待、连接池耗尽。
-
大对象反序列化耗时高。
-
消费后又同步调用其他系统,链路过长。
-
异常重试没有上限,导致线程被坏消息长期占住。
这类问题的关键不是继续扩容消费者,而是 先降低单条消息处理成本。
否则实例越多,下游越容易被打挂。
2.3 再看集群与 Topic 设计
Kafka 积压并不总是消费者代码问题。
还要检查 Topic 分区数、Broker 负载、磁盘使用率、网络吞吐、ISR 抖动等集群指标。
可以查看 Topic 元数据:
kafka-topics.sh \
--bootstrap-server kafka1:9092 \
--topic order-topic \
--describe
重点观察:
-
分区数是否足够。
-
副本分布是否均衡。
-
是否存在热点分区。
-
是否频繁出现
UnderReplicatedPartitions。
3. 处理策略
3.1 优先止血
3.1.1 限流生产端
当 Produce TPS 明显高于当前可承载的 Consume TPS 时,最有效的止血手段通常不是先调消费者,而是先限流生产者。
否则积压会继续扩大,恢复窗口会越来越长。
适用场景:
-
上游突发洪峰。
-
下游数据库或外部接口已接近极限。
-
消费端扩容需要时间。
3.1.2 暂停非核心链路
如果同一集群承载多个业务,且资源紧张,可以临时降低低优先级任务的消费频率。
核心思路是 把有限资源优先让给关键 Topic 和关键 Consumer Group。
这种方式常见于:
-
日志类、埋点类、统计类消费。
-
离线任务与实时交易共用资源。
-
多个消费者共享数据库连接池。
3.1.3 跳过坏消息
坏消息导致某个分区长期卡住时,必须尽快隔离。
否则一个分区的 offset 不推进,会持续放大积压,并拖慢整体追平速度。
建议处理方式:
-
失败次数超过阈值后写入死信队列
DLQ。 -
记录原始消息体、异常原因、业务主键。
-
人工或离线任务补偿,不要在线死循环重试。
不要让单条脏数据长期阻塞整个分区。
3.2 提升消费能力
3.2.1 增加消费者实例
最直接的扩容方式,是增加消费者实例数量。
但要注意,消费者实例数的上限受分区数限制。
规则如下:
-
实例数 < 分区数:仍有提升空间。
-
实例数 = 分区数:基本打满并行度。
-
实例数 > 分区数:多出来的实例不会消费数据。
因此,扩容前一定要先确认分区数。
否则只是增加机器成本,不会提升吞吐。
3.2.2 增加分区数
当现有分区数不足以支撑并行消费时,需要扩容 Topic 分区。
这通常是提升消费上限的关键动作。
但要注意两点:
-
增加分区后,消息顺序只在 单分区内 保证。
-
分区扩容可能改变
key到分区的映射,影响局部有序性和热点分布。
因此,有顺序要求的业务不能只看吞吐,必须同时评估顺序语义是否可接受。
3.2.3 批量消费
如果当前是“拉一条,处理一条,提交一次”,吞吐通常会很差。
应尽量改为 批量拉取、批量处理、批量提交,降低网络和提交开销。
典型收益包括:
-
减少
poll次数。 -
减少
commit次数。 -
提升数据库批量写入效率。
-
降低线程切换与对象创建成本。
3.2.4 异步解耦
当消息处理逻辑较重时,可以把消费线程与业务线程解耦。
消费线程只负责拉取和投递到内存队列,业务线程池异步处理。
但要特别注意:
-
队列容量必须有限制。
-
必须实现背压。
-
offset提交必须基于“已处理完成”的边界,而不是“已拉取”。
异步化能提升吞吐,但也最容易引入重复消费和乱序提交问题。
3.3 降低单条处理成本
3.3.1 批量写库替代单条写库
很多积压问题,根本原因不是 Kafka 慢,而是下游数据库被单条写入拖垮。
把单条 insert 改成批量 batch insert,往往比扩容消费者更有效。
3.3.2 去掉同步远程调用
如果消费一条消息要同步调用多个外部服务,吞吐会被 RT 直接锁死。
这时应考虑:
-
改为本地落库后异步补偿。
-
合并远程调用。
-
增加缓存。
-
缩短事务边界。
3.3.3 缩短事务
长事务会显著拉低消费速度,并放大锁冲突和回滚成本。
建议将 Kafka 消费、数据库写入、外部调用拆分,避免一个大事务包住整条链路。
3.4 参数调优
3.4.1 关键参数解析
| 参数 | 作用 | 调优方向 | 风险点 |
|---|---|---|---|
max.poll.records |
单次拉取返回的最大记录数 | 适当调大,提升批量处理能力 | 过大可能导致单批处理时间过长 |
fetch.min.bytes |
服务端最少返回字节数 | 调大可提升吞吐 | 低流量时可能增加等待 |
fetch.max.wait.ms |
等待凑批的最长时间 | 与 fetch.min.bytes 配合调优 |
过大可能增加延迟 |
enable.auto.commit |
是否自动提交位点 | 建议关闭,手动提交 | 自动提交易产生消息丢失或重复 |
max.poll.interval.ms |
两次 poll 最大间隔 |
处理耗时长时需要调大 | 过小会频繁触发重平衡 |
session.timeout.ms |
会话超时时间 | 网络抖动场景可适当调大 | 过大影响故障发现速度 |
heartbeat.interval.ms |
心跳间隔 | 一般小于 session.timeout.ms / 3 |
配置不当导致误判离组 |
3.4.2 推荐调优顺序
参数调优不要一把梭。
建议按以下顺序推进:
-
先确认瓶颈是不是业务处理。
-
再调整
max.poll.records。 -
再看是否需要增大
max.poll.interval.ms。 -
最后再联合调整
fetch.min.bytes与fetch.max.wait.ms。
参数调优只能放大已有能力,不能替代架构修复。
3.5 极端场景处置
3.5.1 临时重置 offset
当消息积压已经超过业务可恢复窗口,且历史消息允许丢弃或离线补偿时,可以考虑重置 offset。
这是高风险操作,只适合明确接受数据跳过的场景。
示例命令如下:
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group order-consumer-group \
--topic order-topic \
--reset-offsets \
--to-latest \
--execute
该操作会直接跳过未消费消息。
执行前必须确认:
-
业务是否允许丢数据。
-
是否已完成数据备份。
-
是否有补偿链路。
-
是否已在预发环境验证命令效果。
3.5.2 数据迁移与旁路消费
极端积压下,也可以临时启动旁路消费集群,将历史消息转储或分流处理。
这种方式适合:
-
正常消费者无法快速追平。
-
历史消息需要保留。
-
核心实时流必须先恢复。
4. 实战方案
4.1 标准处理流程
4.1.1 5 步闭环
遇到消息积压时,建议按以下顺序执行:
-
确认积压规模、增长趋势、预计追平时间。
-
判断瓶颈在生产端、消费端、下游还是集群侧。
-
先做止血动作,例如限流、隔离坏消息、暂停低优任务。
-
再做能力提升,例如扩容实例、增加分区、批量处理。
-
最后补监控、补限流、补
Runbook,避免再次发生。
这种顺序的核心,是 先阻止问题继续恶化,再处理根因。
一上来改参数,往往只会延误恢复时间。
4.2 方案对比
| 方案 | 见效速度 | 适用场景 | 风险 |
|---|---|---|---|
| 限流生产端 | 快 | 上游流量突发 | 影响写入吞吐 |
| 增加消费者实例 | 快 | 分区数足够时 | 可能压垮下游 |
| 增加分区数 | 中 | 并行度受限 | 影响顺序与路由 |
| 批量消费 | 中 | 单条处理开销高 | 需要改代码 |
| 异步线程池 | 中 | 业务逻辑重 | 容易引入位点提交问题 |
死信队列 DLQ |
快 | 坏消息阻塞 | 需要补偿机制 |
重置 offset |
极快 | 接受跳过历史数据 | 高风险,可能丢数 |
5. 预防机制
5.1 监控体系
5.1.1 必配监控项
要避免“发现时已经积压很多”,必须对以下指标设告警:
-
Consumer Lag -
Lag增速 -
Consume TPS -
Produce TPS -
单条处理耗时
-
批处理耗时
-
重平衡次数
-
死信队列写入量
-
数据库 RT、失败率、连接池使用率
其中,仅监控 Lag 绝对值是不够的。
更有效的是监控“Lag 增速 + 追平时间”。
5.2 容量规划
容量评估至少要覆盖以下问题:
-
峰值
TPS是平时的多少倍。 -
单条消息平均处理耗时是多少。
-
当前分区数能提供多少并行度。
-
下游数据库和外部服务最多能承受多少请求。
建议预留至少 2 倍以上的突发冗余。
没有容量冗余的系统,遇到活动、补数、故障恢复时几乎一定会积压。
5.3 数据模型优化
消息体过大、字段冗余、反序列化复杂,也会直接降低吞吐。
因此在设计消息协议时,应尽量做到:
-
只放必要字段。
-
避免深层嵌套对象。
-
避免把大文本、大数组直接塞进消息体。
-
能传主键就不要传整对象。