📖 开场:快递站的噩梦
想象一下,你是一个快递站的站长 📦
正常情况:
每小时:收100个包裹,送出100个包裹 ✅
站内积压:0个 😊
双11期间:
每小时:收1000个包裹,送出100个包裹 😱
站内积压:900个/小时 × 24小时 = 21600个!💀
结果:
- 快递站爆仓了!📦📦📦📦📦
- 客户投诉电话打爆!☎️☎️☎️
- 快递员累瘫了!😵
这就是Kafka的消息积压(Lag)问题!
生产速度 > 消费速度 = 消息堆积 🔥
🤔 什么是消息积压(Lag)?
定义
消息积压(Consumer Lag) = 生产者写入的最新offset - 消费者已消费的offset
Topic: orders, Partition: 0
Producter写到: offset = 10000 (最新消息)
Consumer读到: offset = 5000 (消费进度)
↓
消息积压(Lag) = 10000 - 5000 = 5000条消息 😰
图示:
Kafka Topic: orders-0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
| 已消费 | 未消费(积压) |
| offset 0 ~ 5000 | offset 5001 ~ 10000 |
| ✅ | ⏳ |
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
↑ ↑
Consumer Offset Producer Offset
Lag = 5000条消息!😱
🔍 如何发现消息积压?
方法1:命令行工具 🔧
# 查看消费者组的Lag
kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--group order-consumer-group \
--describe
# 输出示例:
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
order-consumer-group orders 0 5000 10000 5000 ← 积压5000条!
order-consumer-group orders 1 8000 8100 100
order-consumer-group orders 2 9000 9000 0
重要字段:
CURRENT-OFFSET: 消费者当前offsetLOG-END-OFFSET: 最新的offset(生产者写到哪了)LAG: 积压数量 = LOG-END-OFFSET - CURRENT-OFFSET
方法2:监控系统 📊
使用Kafka Manager / CMAK:
可视化界面显示:
┌─────────────────────────────┐
│ 消费者组: order-consumer │
│ ━━━━━━━━━━━━━━━━━━━━━━━ │
│ Partition 0: │
│ Lag: 5000 ⚠️ │
│ [▓▓▓▓▓░░░░░] 50% │
│ │
│ Partition 1: │
│ Lag: 100 ✅ │
│ [▓▓▓▓▓▓▓▓▓░] 90% │
└─────────────────────────────┘
推荐监控工具:
- ✅ Kafka Manager (CMAK)
- ✅ Kafka Eagle
- ✅ Burrow(LinkedIn开源)
- ✅ Prometheus + Grafana
方法3:Java代码监控 💻
import org.apache.kafka.clients.admin.*;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
import java.util.*;
public class LagMonitor {
public static void main(String[] args) throws Exception {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
try (AdminClient adminClient = AdminClient.create(props)) {
// 1. 获取消费者组列表
String groupId = "order-consumer-group";
// 2. 获取消费者组的offset
Map<TopicPartition, OffsetAndMetadata> committedOffsets =
adminClient.listConsumerGroupOffsets(groupId)
.partitionsToOffsetAndMetadata()
.get();
// 3. 获取Topic的最新offset
Map<TopicPartition, Long> endOffsets = new HashMap<>();
for (TopicPartition tp : committedOffsets.keySet()) {
ListOffsetsResult.ListOffsetsResultInfo info =
adminClient.listOffsets(
Collections.singletonMap(
tp,
OffsetSpec.latest()
)
).all().get().get(tp);
endOffsets.put(tp, info.offset());
}
// 4. 计算Lag
System.out.println("╔══════════════════════════════════════════════╗");
System.out.println("║ Kafka Consumer Lag Report ║");
System.out.println("╠══════════════════════════════════════════════╣");
long totalLag = 0;
for (Map.Entry<TopicPartition, OffsetAndMetadata> entry :
committedOffsets.entrySet()) {
TopicPartition tp = entry.getKey();
long committedOffset = entry.getValue().offset();
long endOffset = endOffsets.get(tp);
long lag = endOffset - committedOffset;
totalLag += lag;
String status = lag > 1000 ? "⚠️ WARNING" : "✅ OK";
System.out.printf("║ %s-%-2d | Current: %-6d | End: %-6d | Lag: %-6d %s%n",
tp.topic(), tp.partition(),
committedOffset, endOffset, lag, status);
}
System.out.println("╠══════════════════════════════════════════════╣");
System.out.printf("║ Total Lag: %-6d ║%n", totalLag);
System.out.println("╚══════════════════════════════════════════════╝");
}
}
}
输出示例:
╔══════════════════════════════════════════════╗
║ Kafka Consumer Lag Report ║
╠══════════════════════════════════════════════╣
║ orders-0 | Current: 5000 | End: 10000 | Lag: 5000 ⚠️ WARNING
║ orders-1 | Current: 8000 | End: 8100 | Lag: 100 ✅ OK
║ orders-2 | Current: 9000 | End: 9000 | Lag: 0 ✅ OK
╠══════════════════════════════════════════════╣
║ Total Lag: 5100 ║
╚══════════════════════════════════════════════╝
🔥 消息积压的常见原因
1️⃣ 消费能力不足 🐌
场景:
生产速度:10000条/秒 🚀
消费速度:100条/秒 🐌
Lag增长速度 = 10000 - 100 = 9900条/秒!😱
可能原因:
- 消费者数量太少(只有1个,处理不过来)
- 消费逻辑太复杂(每条消息处理10秒)
- 消费者性能差(机器配置低)
- 消费者有阻塞操作(同步调用第三方接口)
2️⃣ 突发流量 🌊
场景:
正常流量:100条/秒
双11流量:10000条/秒 ← 突然暴增100倍!🔥
消费者来不及处理,瞬间积压!
生活比喻:
平时餐厅有10桌客人,厨师应付自如
春节突然来了1000桌,厨师直接崩溃!👨🍳💀
3️⃣ 消费者挂了 💀
场景:
正常:3个消费者,每人处理1000条/秒
异常:2个消费者挂了,只剩1个 💀
结果:1个人要干3个人的活,积压爆表!😱
4️⃣ Rebalance频繁 🔄
场景:
消费者频繁加入/退出 → 频繁触发Rebalance
→ Rebalance期间,所有消费者停止消费 🛑
→ 消息积压!
原因:
- 消费者心跳超时
- 消费时间过长(超过
max.poll.interval.ms) - 消费者频繁重启
5️⃣ 下游依赖慢 🐢
场景:
消费者处理流程:
1. 从Kafka拉取消息(1ms) ⚡
2. 调用数据库写入(100ms) 🐌
3. 调用第三方API(5000ms) 🐢🐢🐢
4. 发送通知邮件(1000ms) 🐢
总耗时:6101ms处理一条消息!😱
问题:
- 数据库慢查询
- 第三方接口超时
- 网络抖动
- 锁竞争
🛠️ 消息积压的解决方案
方案1:扩容消费者 🚀(最快最有效)
原理
消费者数 = 分区数 时,性能最佳!
Topic: orders, 分区数: 6
情况1:只有2个消费者
C1: 处理 P0, P1, P2
C2: 处理 P3, P4, P5
每个消费者负担重!🐌
情况2:扩容到6个消费者
C1: 处理 P0
C2: 处理 P1
C3: 处理 P2
C4: 处理 P3
C5: 处理 P4
C6: 处理 P5
完美分配,每个消费者负担轻!⚡
效果:
扩容前:2个消费者,消费速度 2000条/秒
扩容后:6个消费者,消费速度 6000条/秒
效率提升:3倍!🚀
实施步骤
步骤1:评估分区数
# 查看Topic分区数
kafka-topics.sh --describe \
--bootstrap-server localhost:9092 \
--topic orders
# 输出:
Topic: orders PartitionCount: 6 ReplicationFactor: 3
步骤2:启动更多消费者
Docker部署:
# 启动6个消费者实例
docker-compose scale consumer=6
Kubernetes部署:
apiVersion: apps/v1
kind: Deployment
metadata:
name: kafka-consumer
spec:
replicas: 6 # ⭐ 扩容到6个副本
template:
spec:
containers:
- name: consumer
image: my-consumer:latest
env:
- name: KAFKA_GROUP_ID
value: "order-consumer-group"
手动启动:
# 启动多个消费者进程,group.id相同
java -jar consumer.jar --group.id=order-consumer-group &
java -jar consumer.jar --group.id=order-consumer-group &
java -jar consumer.jar --group.id=order-consumer-group &
注意事项 ⚠️
消费者数不能超过分区数!
分区数: 6
消费者数 = 6 ✅ 完美!
消费者数 = 3 ✅ 可以,但没充分利用
消费者数 = 9 ❌ 浪费!有3个消费者会闲置
如果分区数不够怎么办?
先增加分区数!
# 增加分区(只能增加,不能减少)
kafka-topics.sh --alter \
--bootstrap-server localhost:9092 \
--topic orders \
--partitions 12 # 从6个扩到12个
⚠️ 注意:增加分区会影响Key分区策略!
之前:hash(key) % 6
之后:hash(key) % 12
相同Key可能进入不同分区,顺序性被破坏!
解决方法:
- 新消息用新分区
- 老分区慢慢消费完
- 或者不增加分区,优化消费逻辑
方案2:优化消费逻辑 ⚡
2.1 异步处理
❌ 错误做法(同步):
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// 同步处理:慢!🐌
processMessage(record); // 耗时1秒
consumer.commitSync();
}
}
// 性能:1条/秒 💀
✅ 正确做法(异步):
// 创建线程池
ExecutorService executor = new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
// 批量提交到线程池异步处理
List<Future<?>> futures = new ArrayList<>();
for (ConsumerRecord<String, String> record : records) {
Future<?> future = executor.submit(() -> {
processMessage(record); // 异步处理
});
futures.add(future);
}
// 等待所有任务完成
for (Future<?> future : futures) {
future.get(); // 确保处理完成
}
// 统一提交offset
consumer.commitSync();
}
// 性能:10-20条/秒(根据线程池大小)🚀
效果:
同步处理:1条/秒
异步处理(10线程):10条/秒
性能提升:10倍!🚀
2.2 批量处理
❌ 错误做法(逐条):
for (ConsumerRecord<String, String> record : records) {
// 每条消息都访问一次数据库
jdbcTemplate.update("INSERT INTO orders VALUES (?)", record.value());
}
// 1000条消息 = 1000次数据库操作 💀
✅ 正确做法(批量):
List<String> batch = new ArrayList<>();
for (ConsumerRecord<String, String> record : records) {
batch.add(record.value());
if (batch.size() >= 100) {
// 批量插入100条
jdbcTemplate.batchUpdate(
"INSERT INTO orders VALUES (?)",
batch
);
batch.clear();
}
}
// 处理剩余的
if (!batch.isEmpty()) {
jdbcTemplate.batchUpdate("INSERT INTO orders VALUES (?)", batch);
}
// 1000条消息 = 10次数据库操作 ✅
效果:
逐条插入:1000次数据库操作,耗时10秒
批量插入:10次数据库操作,耗时1秒
性能提升:10倍!🚀
2.3 去掉不必要的操作
排查慢操作:
@Slf4j
public void processMessage(ConsumerRecord<String, String> record) {
long start = System.currentTimeMillis();
// 操作1:解析JSON
long t1 = System.currentTimeMillis();
Order order = parseJson(record.value());
log.info("解析JSON耗时: {}ms", System.currentTimeMillis() - t1);
// 操作2:数据库查询
long t2 = System.currentTimeMillis();
User user = userService.getById(order.getUserId()); // 慢查询!🐌
log.info("查询用户耗时: {}ms", System.currentTimeMillis() - t2);
// 操作3:调用第三方API
long t3 = System.currentTimeMillis();
notificationService.send(user.getPhone(), "订单通知"); // 超慢!🐢
log.info("发送通知耗时: {}ms", System.currentTimeMillis() - t3);
// 操作4:写数据库
long t4 = System.currentTimeMillis();
orderRepository.save(order);
log.info("保存订单耗时: {}ms", System.currentTimeMillis() - t4);
log.info("总耗时: {}ms", System.currentTimeMillis() - start);
}
输出分析:
解析JSON耗时: 2ms ✅
查询用户耗时: 50ms 😐 可以优化(加缓存)
发送通知耗时: 3000ms 😱 罪魁祸首!
保存订单耗时: 10ms ✅
总耗时: 3062ms
优化方案:
// 方案1:异步发送通知(不阻塞主流程)
executor.submit(() -> {
notificationService.send(user.getPhone(), "订单通知");
});
// 方案2:批量发送通知
List<Notification> notifications = new ArrayList<>();
notifications.add(new Notification(user.getPhone(), "订单通知"));
if (notifications.size() >= 100) {
notificationService.batchSend(notifications);
}
// 方案3:延迟发送(写到延迟队列,稍后发送)
delayQueue.add(new DelayedNotification(user.getPhone(), "订单通知", 5000));
2.4 增加本地缓存
问题:
// 每条消息都查询数据库
User user = userService.getById(order.getUserId());
// 如果100万条消息,就查询100万次数据库!💀
解决:
// 使用本地缓存(Caffeine/Guava Cache)
LoadingCache<String, User> userCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(userId -> userService.getById(userId));
// 消费消息时,先查缓存
User user = userCache.get(order.getUserId()); // ⚡ 快!
效果:
无缓存:每次查询数据库,50ms
有缓存:命中缓存,0.1ms
性能提升:500倍!🚀
方案3:临时扩容+专项处理 🚒(应急方案)
场景:积压太多(几百万条),正常消费要好几天才能清完!😱
步骤1:停止写入(如果可以)
暂时关闭生产者,或者限流
防止积压继续增长
步骤2:创建临时消费者组
// 创建一个新的消费者组(专门清理积压)
Properties props = new Properties();
props.put("group.id", "emergency-cleanup-group"); // ⭐ 新的group
props.put("enable.auto.commit", false);
props.put("max.poll.records", 500); // 单次多拉取一些
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("orders"));
// 从最早的offset开始消费
consumer.seekToBeginning(consumer.assignment());
步骤3:简化处理逻辑
// 只做最核心的操作,其他全部省略
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// 只保存到数据库,不发通知,不调第三方
orderRepository.save(parseJson(record.value()));
}
consumer.commitSync();
}
// 其他操作(发通知)可以后续补发
步骤4:启动大量实例
# 启动50个临时消费者实例(前提是有足够的分区)
for i in {1..50}; do
java -jar emergency-consumer.jar &
done
步骤5:清理完成后删除消费者组
# 删除临时消费者组
kafka-consumer-groups.sh --delete \
--bootstrap-server localhost:9092 \
--group emergency-cleanup-group
生活比喻:
河流决堤了(消息积压),正常的抽水泵(正常消费者)来不及,
紧急调动100台大功率水泵(临时消费者)全力抽水!🚒💦
方案4:消息降级/丢弃 🗑️(最后手段)
适用场景:
- 消息不重要(日志、埋点数据)
- 时效性要求高(超过1小时就没意义了)
- 业务可接受部分数据丢失
策略1:丢弃旧消息
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// 消息时间戳
long messageTime = record.timestamp();
long currentTime = System.currentTimeMillis();
// 超过1小时的消息,直接丢弃
if (currentTime - messageTime > 3600000) {
log.warn("消息过期,跳过: offset={}", record.offset());
continue; // 跳过,不处理
}
processMessage(record);
}
consumer.commitSync();
策略2:采样处理
// 只处理10%的消息
for (ConsumerRecord<String, String> record : records) {
if (record.offset() % 10 == 0) {
processMessage(record); // 只处理offset是10的倍数的消息
}
}
策略3:直接跳到最新
// 放弃所有积压消息,从最新的开始消费
Set<TopicPartition> assignment = consumer.assignment();
consumer.seekToEnd(assignment); // ⚠️ 跳到最新offset
log.warn("已跳过所有积压消息,从最新offset开始消费");
⚠️ 警告:这些是最后手段,会丢失数据!需要业务方确认!
方案5:增加分区数 📦
适用场景:分区数太少,限制了并发度
操作:
# 当前6个分区
kafka-topics.sh --describe \
--bootstrap-server localhost:9092 \
--topic orders
# 扩展到12个分区
kafka-topics.sh --alter \
--bootstrap-server localhost:9092 \
--topic orders \
--partitions 12
然后扩容消费者到12个:
kubectl scale deployment kafka-consumer --replicas=12
效果:
扩容前:6个分区,6个消费者,消费速度 6000条/秒
扩容后:12个分区,12个消费者,消费速度 12000条/秒
性能提升:2倍!🚀
⚠️ 注意:
- 增加分区后,Key的分区策略会改变
- 无法保证顺序性(相同Key可能进不同分区)
- 分区只能增加,不能减少
🎯 完整的应急处理流程
阶段1:发现问题 🔍
1. 监控告警:Lag超过阈值
2. 查看Lag详情:kafka-consumer-groups.sh
3. 判断严重程度:
- Lag < 10000:轻微,观察
- Lag 10000-100000:中等,需要优化
- Lag > 100000:严重,紧急处理!
阶段2:快速止血 🩹
1. 扩容消费者(5分钟见效)
2. 优化消费逻辑(去掉慢操作)
3. 如果还不行,启动临时消费者组
阶段3:治本 🏥
1. 分析根因:
- 是突发流量?→ 加限流
- 是消费慢?→ 优化代码
- 是分区少?→ 增加分区
2. 预防措施:
- 配置监控告警
- 定期演练扩容
- 准备应急预案
📊 监控告警配置
Prometheus + Grafana
Kafka Exporter:
# docker-compose.yml
version: '3'
services:
kafka-exporter:
image: danielqsj/kafka-exporter
command:
- --kafka.server=kafka:9092
ports:
- 9308:9308
Prometheus配置:
# prometheus.yml
scrape_configs:
- job_name: 'kafka'
static_configs:
- targets: ['kafka-exporter:9308']
告警规则:
# alert-rules.yml
groups:
- name: kafka
rules:
- alert: KafkaConsumerLagHigh
expr: kafka_consumergroup_lag > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "Kafka消费积压 (instance {{ $labels.instance }})"
description: "消费者组 {{ $labels.consumergroup }} 的Lag已达到 {{ $value }}条"
- alert: KafkaConsumerLagCritical
expr: kafka_consumergroup_lag > 100000
for: 1m
labels:
severity: critical
annotations:
summary: "Kafka消费严重积压!"
description: "消费者组 {{ $labels.consumergroup }} 的Lag已达到 {{ $value }}条,请立即处理!"
自定义告警代码
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class LagAlertService {
@Autowired
private AdminClient adminClient;
@Autowired
private AlertService alertService; // 告警服务(发邮件/短信/钉钉)
private static final long LAG_WARNING_THRESHOLD = 10000;
private static final long LAG_CRITICAL_THRESHOLD = 100000;
@Scheduled(fixedRate = 60000) // 每分钟检查一次
public void checkLag() {
try {
Map<String, Map<TopicPartition, Long>> lagMap = calculateLag();
for (Map.Entry<String, Map<TopicPartition, Long>> entry : lagMap.entrySet()) {
String groupId = entry.getKey();
long totalLag = entry.getValue().values().stream()
.mapToLong(Long::longValue)
.sum();
if (totalLag > LAG_CRITICAL_THRESHOLD) {
// 严重告警:发短信+电话
alertService.sendCritical(
String.format("🚨【严重】消费者组%s积压%d条消息!", groupId, totalLag)
);
log.error("🚨 严重告警:消费者组{}积压{}条", groupId, totalLag);
} else if (totalLag > LAG_WARNING_THRESHOLD) {
// 警告:发邮件+钉钉
alertService.sendWarning(
String.format("⚠️【警告】消费者组%s积压%d条消息", groupId, totalLag)
);
log.warn("⚠️ 警告:消费者组{}积压{}条", groupId, totalLag);
}
}
} catch (Exception e) {
log.error("检查Lag失败", e);
}
}
private Map<String, Map<TopicPartition, Long>> calculateLag() throws Exception {
// 实现逻辑:计算所有消费者组的Lag
// ...
return new HashMap<>();
}
}
🎓 面试题速答
Q1: Kafka消息积压的常见原因?
A: 四大原因!
- 消费能力不足 - 消费者太少,处理不过来
- 突发流量 - 双11、秒杀等场景,流量暴增
- 消费者故障 - 消费者挂了,或频繁Rebalance
- 下游依赖慢 - 数据库慢查询、第三方接口超时
Q2: 如何快速解决消息积压?
A: 三板斧!
- 扩容消费者 - 最快最有效!(前提是有足够分区)
- 优化消费逻辑 - 异步处理、批量操作、去掉慢操作
- 临时消费者组 - 应急方案,启动大量临时消费者清理积压
Q3: 消费者数量和分区数的关系?
A:
消费者数 ≤ 分区数(最佳:相等)
例如:
6个分区 → 最多6个消费者有意义
→ 第7个消费者会闲置
如果消费者不够用:
→ 先增加分区数
→ 再扩容消费者
Q4: 如何监控Kafka的Lag?
A: 三种方法!
- 命令行:
kafka-consumer-groups.sh --describe - 监控工具:Kafka Manager、Kafka Eagle、Burrow
- 自己写代码:AdminClient API查询offset并计算
推荐:Prometheus + Grafana + 钉钉告警
Q5: 如果Lag一直增长怎么办?
A: 分析原因,对症下药!
Lag增长 = 生产速度 > 消费速度
解决方案:
-
提高消费速度:
- 扩容消费者
- 优化消费逻辑
- 异步处理
-
降低生产速度(如果可以):
- 限流
- 削峰(用缓冲队列)
-
应急手段(业务允许的话):
- 丢弃旧消息
- 跳到最新offset
Q6: 增加分区数会有什么影响?
A: 三个影响!
好处:
- ✅ 可以扩容更多消费者
- ✅ 提高并发度
坏处:
- ❌ Key的分区策略改变(
hash(key) % n) - ❌ 无法保证相同Key进同一分区(顺序性被破坏)
- ❌ Rebalance会更慢
注意:
- 分区只能增加,不能减少
- 慎重操作,做好测试
🎯 最佳实践总结
预防措施 ✅
1. 合理规划分区数
- 分区数 = 期望吞吐量 / 单消费者吞吐量
- 建议:初期就设置足够的分区数(如50个)
2. 配置监控告警
- Lag > 10000:警告
- Lag > 100000:严重
- 消费速率 < 生产速率:警告
3. 定期演练
- 模拟消息积压场景
- 演练扩容流程
- 确保应急预案有效
4. 消费者优化
- 异步处理
- 批量操作
- 本地缓存
- 去掉慢操作
5. 容量规划
- 预留足够的资源
- 支持快速扩容
- 准备降级方案
应急预案 🚨
┌─────────────────────────────────────────┐
│ 消息积压应急预案 │
├─────────────────────────────────────────┤
│ Lag级别:轻微(< 10000) │
│ 措施:观察,无需干预 │
├─────────────────────────────────────────┤
│ Lag级别:中等(10000 - 100000) │
│ 措施: │
│ 1. 扩容消费者(2倍) │
│ 2. 检查消费者日志,排查慢操作 │
│ 3. 持续观察 │
├─────────────────────────────────────────┤
│ Lag级别:严重(> 100000) │
│ 措施: │
│ 1. 立即扩容消费者(5-10倍) │
│ 2. 启动临时消费者组 │
│ 3. 简化处理逻辑(只做核心操作) │
│ 4. 如果还不行,考虑降级: │
│ - 丢弃旧消息 │
│ - 采样处理 │
│ - 跳到最新offset │
│ 5. 通知相关业务方 │
│ 6. 事后复盘,修复根因 │
└─────────────────────────────────────────┘
🎬 总结:一张图看懂消息积压处理
Kafka消息积压处理全景图
┌──────────────────────────────────────────────────────────┐
│ 监控发现 │
│ │
│ kafka-consumer-groups.sh --describe │
│ Prometheus + Grafana │
│ 自定义监控代码 │
│ │
│ ⚠️ Lag > 10000:警告 │
│ 🚨 Lag > 100000:严重 │
└──────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────┐
│ 原因分析 │
│ │
│ □ 消费能力不足?→ 扩容消费者 │
│ □ 突发流量?→ 限流 + 扩容 │
│ □ 消费者故障?→ 重启 + 健康检查 │
│ □ 下游依赖慢?→ 优化代码 + 异步处理 │
│ □ 分区数太少?→ 增加分区 + 扩容消费者 │
└──────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────┐
│ 解决方案 │
│ │
│ 🚀 方案1:扩容消费者(最快最有效) │
│ - 消费者数 = 分区数 │
│ - Kubernetes: kubectl scale --replicas=12 │
│ │
│ ⚡ 方案2:优化消费逻辑 │
│ - 异步处理(线程池) │
│ - 批量操作(批量插入数据库) │
│ - 本地缓存(减少DB查询) │
│ - 去掉慢操作(第三方API异步调用) │
│ │
│ 🚒 方案3:临时消费者组(应急) │
│ - 新建消费者组 │
│ - 简化处理逻辑 │
│ - 启动大量实例 │
│ - 快速清理积压 │
│ │
│ 📦 方案4:增加分区数 │
│ - kafka-topics.sh --alter --partitions 12 │
│ - 然后扩容消费者到12个 │
│ │
│ 🗑️ 方案5:降级/丢弃(最后手段) │
│ - 丢弃旧消息 │
│ - 采样处理 │
│ - 跳到最新offset │
└──────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────┐
│ 事后复盘 │
│ │
│ 1. 记录本次事故的原因和处理过程 │
│ 2. 修复根因,避免再次发生 │
│ 3. 完善监控告警 │
│ 4. 更新应急预案 │
│ 5. 团队分享经验 │
└──────────────────────────────────────────────────────────┘
🎉 恭喜你!
你已经掌握了Kafka消息积压的处理方法!🎊
记住核心要点:
- 监控先行 - 及时发现问题
- 快速扩容 - 扩容消费者是最有效的方法
- 优化代码 - 异步处理、批量操作
- 准备预案 - 应急方案要提前演练
下次面试,这样回答:
"Kafka消息积压主要有四个原因:消费能力不足、突发流量、消费者故障、下游依赖慢。
解决方法:首先扩容消费者,消费者数建议等于分区数,可以实现最大并发。
然后优化消费逻辑,使用异步处理、批量操作、本地缓存等手段提高消费速度。
如果积压非常严重,可以启动临时消费者组,简化处理逻辑快速清理积压。
我们线上配置了监控告警,Lag超过阈值会自动告警,触发自动扩容机制,一般能在10分钟内恢复正常。"
面试官:👍 "很好!你有实战经验!"
📚 推荐阅读
🎈 表情包时间 🎈
看到Kafka消息积压时的你:
之前:
😱 "完了!Lag爆表了!怎么办!"
现在:
😎 "淡定!扩容消费者,搞定!"
Lag清理完成后:
🎉 "又拯救了一次生产环境!"
本文完 🎬
记得点赞👍 收藏⭐ 分享🔗
上一篇: 183-Kafka如何保证消息不丢失.md
下一篇: 185-RocketMQ的顺序消息和事务消息实现.md
作者注:写完这篇,我都想开一家"消息积压应急救援公司"了!🚒
如果这篇文章帮你解决了生产问题,请给我一个Star⭐!版权声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。