线上消息队列积压了,消费者处理不过来,老板在群里@你------这时候怎么办?
今天聊点实际的,从判断积压到快速处理,讲点能直接上手的东西。
怎么判断消息积压了
RabbitMQ
bash
# 查看队列消息数量
rabbitmqctl list_queues name messages messages_ready messages_unacked
# 输出大概长这样
# name messages messages_ready messages_unacked
# order 15234 15200 34
关键看这几个数字:
messages- 队列里总共有多少消息messages_ready- 等待消费的消息(积压的主力)messages_unacked- 已投递但还没确认的
一般来说,messages_ready 超过几千就该关注了,上万就是严重积压。
Kafka
bash
# 查看消费 lag(落后多少)
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-consumer-group \
--describe
# 输出大概长这样
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# my-consumer order 0 52341 67589 15248
LAG 就是积压量。LAG 几百几千问题不大,上万就得处理了。
为什么会积压
常见原因就几种:
1. 消费者挂了
最常见。消费者进程没了,但生产者还在往里扔消息。
2. 消费者变慢了
比如数据库慢查询、对外接口超时。消息在那儿等着,时间一长就堆起来了。
3. 消费者数量不对
分区数固定,但消费者少了或者消费者不干活了。
4. 消费逻辑有死循环或者阻塞
消费端卡在某个地方,不消费也不报错。
快速处理方案
方案一:临时增加消费者(推荐)
如果是 Kafka,把消费者实例扩几个:
yaml
# Docker Compose 临时扩容
services:
consumer:
deploy:
replicas: 5 # 从 1 临时改成 5
RabbitMQ 的话,多启动几个消费者实例就行。
注意:Kafka 一个分区只能被一个消费者消费,所以扩容数量别超过分区数。
方案二:提高消费并行度
java
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
ConsumerFactory<String, String> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory);
factory.setConcurrency(10); // 默认1,临时调大成10
return factory;
}
RabbitMQ 的话,调整 prefetch 数量:
java
factory.setPrefetchCount(50); // 默认250,太大了会积压太久,太小了吞吐量上不去
方案三:跳过积压消息,先消费最新的
有时候积压太多了,处理完要很久。这时候可以先跳过旧消息:
Kafka 方案:重置 offset 到最新
bash
# 把消费者组的 offset 重置到最新
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--group my-consumer-group \
--topic order \
--reset-offsets --to-latest \
--execute
注意:这个方案会丢消息,只有在业务能接受丢消息的时候才用。
方案四:临时写个脚本消费掉
直接写个简单脚本,扫队列然后批量处理:
java
@Service
public class FastConsumerService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void consumeAndDiscard(String queueName, int limit) {
for (int i = 0; i < limit; i++) {
Message message = rabbitTemplate.receive(queueName, 1000);
if (message == null) {
break;
}
// 空消费,直接确认。积压太多可以用这个临时方案
// 但记得后续要补偿处理这些消息!
}
}
}
再次强调:空消费会丢消息,后续一定要有补偿机制。
排查积压的根本原因
处理完积压只是治标,找到根因才是治本。
看消费者日志
消费者有没有报错?有没有超时?有没 BLOCKED?
bash
# RabbitMQ 查看连接状态
rabbitmqctl list_connections pid channels
# 查看消费者是否正常
rabbitmqctl list_consumers queue_name
看监控
RabbitMQ 管理界面:http://localhost:15672
Kafka 使用 Kafka Eagle 或者 JMX 监控:
properties
# 开启 JMX
KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote=true"
常见坑
数据库连接池打满:消费者查询数据库,连接池耗尽,所有消费线程都在等连接。
对外接口超时:消费者调第三方接口,接口响应慢,消费速度断崖式下降。
内存泄漏:消费者 OOM 了但没死透,勉强撑着但几乎不消费。
怎么防止积压
几点经验:
- 监控先行。LAG 超过阈值就告警,别等老板@你。
- 消费者要快。IO 操作异步化,能批量处理就批量。
- 分区/队列数要够。Kafka 分区数决定了最大并发消费数。
- 做好限流。上游流量突增时,MQ 要能扛住。
总结
| 场景 | 推荐方案 |
|---|---|
| 消费者挂了 | 重启/扩容消费者 |
| 消费太慢 | 提高并发度、优化消费逻辑 |
| 积压太多,业务能接受丢消息 | 重置 offset 到最新 |
| 积压太多,业务不能丢消息 | 扩容 + 慢慢消费 + 加补偿 |
积压不可怕,怕的是没监控、没预案。下次遇到这种情况,希望你能从容应对。