RabbitMQ 里的消息堆积,是消息队列运维绕不开的问题。业务方抱怨"下单后半天没发货""通知延迟十几分钟",运维上去一看 RabbitMQ 管理页,某个队列的 Ready(待消费消息数)曲线一路向上,从几万涨到几百万,消费者却像没在干活。更危险的是,堆积会让消息一直在内存里待着,触发内存告警(memory alarm)后,RabbitMQ 会阻塞生产者发布,整个链路一起停摆——这就是消息堆积最坏的结果:不只是慢,而是全链路中断。
这篇文章要解决的是消息堆积的完整处置思路:怎么读管理页指标判断"是生产太快还是消费太慢"、消费者并发怎么配、队列本身有什么容量限制、以及怎么在不中断业务的前提下把堆积消化掉。读者读完应该能定位到是消费者处理能力不足、还是消费逻辑卡死、还是 prefetch 配置不当,并知道对应的调优手段。
文章先讲清堆积的成因和危害,再给出一套指标观察方法,然后分别从消费者并发、prefetch、队列容量几个维度讲优化,最后讲紧急泄洪和预防。文中数值为经验量级,具体以压测为准。
一、消息堆积的成因与危害
先建立一个基本模型:队列的积压量 = 生产速率 - 消费速率。只要消费跟不上生产,消息就会在队列里越堆越多。成因无非几类:
- 消费者处理太慢:单条消息处理耗时增加(下游变慢、SQL 慢了、外部接口超时),消费速率下降。
- 消费者数量/并发不足:生产端已经扩量,消费端还是老配置。
- 消费者挂了或卡住:进程假死、网络异常、线程池被占满,消息
Unacked(已投递未确认)堆积。 - 生产突发:营销活动、批量导入导致瞬间洪峰。
- prefetch 配置不当:预取太多,消息都堆在某个消费者手里,其他消费者闲着。
危害从轻到重:
- 消费延迟增大,业务时效变差。
- 消息堆在内存里,占用大量内存,触发流控。
- 触发内存/磁盘告警(alarm)后,RabbitMQ 会阻塞所有生产者连接,导致生产者发不出消息、超时报错。
- 极端情况下消息被写入磁盘,队列性能下降,甚至因磁盘满导致节点异常。
所以堆积不只是"慢",它有一条清晰的恶化链条,越早处理越好。
二、先看清楚指标:是生产快了还是消费慢了
处理堆积的第一步是看数据,别凭感觉。RabbitMQ 提供管理插件和 HTTP API。
# 开启管理插件(若未开启)
rabbitmq-plugins enable rabbitmq_management
# 管理页面默认在 http://:15672
# 用 HTTP API 查队列详情(含消息数)
curl -s -u guest:guest 'http://localhost:15672/api/queues/%2F/myqueue?columns=name,messages,messages_ready,messages_unacknowledged,consumers,message_stats' | python -m json.tool
# 列出所有队列的积压情况(按 ready 排序)
curl -s -u guest:guest 'http://localhost:15672/api/queues?columns=name,messages_ready,messages_unacknowledged,consumers' | python -m json.tool
关键指标:
| 指标 | 含义 | 判读 |
|---|---|---|
messages_ready |
待投递给消费者的消息数 | 持续上涨说明消费跟不上 |
messages_unacknowledged |
已投递但未 ack 的消息数 | 高且不降说明消费者卡住或处理慢 |
consumers |
消费者数量 | 为 0 说明没有消费者在消费 |
publish / deliver 速率 |
生产、投递速率 | publish > deliver 即积压加剧 |
用命令行也能快速看:
# 查看队列消息数、消费者数
rabbitmqctl list_queues name messages messages_ready messages_unacknowledged consumers
# 查看连接、信道、消费者
rabbitmqctl list_consumers
rabbitmqctl list_channels name prefetch_count messages_unacknowledged
判读逻辑:
messages_ready高但messages_unacknowledged低、consumers正常 → 消费速率不足,考虑加并发或优化处理逻辑。messages_unacknowledged高且不降、consumers正常 → 消费者"接了一堆但不 ack",处理卡住或 prefetch 过大。consumers为 0 → 消费者挂了或没连上,先恢复消费者。- 生产者与消费者速率对比:如果消费速率本身已经很高但生产更高,就是纯产能问题。
三、危害控制:先看有没有触发流控
如果堆积严重,先确认是否已经触发告警。
# 查看 RabbitMQ 状态,含内存、磁盘告警和流控
rabbitmqctl status
# 查看内存使用
rabbitmqctl status | grep -A5 -i memory
# 查看是否在阻塞生产者
rabbitmqctl list_connections name state
# state 出现 "blocked" / "blocking" 说明触发了流控
RabbitMQ 默认在内存或磁盘超过水位线时触发告警:达到内存阈值的 40%(默认 vm_memory_high_watermark 为 0.4)会开始流控并最终阻塞生产者;磁盘低于 disk_free_limit(默认 50MB 或内存大小)也会告警。
# 查看水位配置
rabbitmqctl status | grep -i watermark
# 或在配置文件中
grep -i 'watermark\|disk_free' /etc/rabbitmq/rabbitmq.conf
风险提示:流控/阻塞期间生产者发不出消息,如果业务没有重试和缓冲,会造成数据丢失或大量报错。恢复手段是尽快降低内存/磁盘压力(消费掉消息、扩磁盘),而不是简单调高水位线——调高只是推迟爆炸。修改水位线属于配置变更,改后 rabbitmqctl 重载或重启生效,需评估影响。
四、优化手段一:提高消费者并发
最直接的思路是让消费更快。分两层:
增加消费者实例/线程。RabbitMQ 的并发消费是"同一个队列可以被多个消费者同时消费",所以加消费者实例(多进程、多线程、多容器)就能并行消费。注意:加消费者能线性提升吞吐的前提是消息之间没有顺序依赖,且下游(数据库、外部服务)扛得住。
// Spring AMQP 中设置并发消费者
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrentConsumers(10); // 初始并发消费者数
factory.setMaxConcurrentConsumers(30); // 动态扩到上限
factory.setPrefetchCount(20); // 每次预取 20 条
return factory;
}
控制 prefetch(预取数量)。prefetch 决定一个消费者在 ack 之前最多能预取多少条消息。设太大,消息都堆在同几个消费者手里,其他消费者闲着(负载不均);设太小,消费者频繁等待网络往返,吞吐上不去。
# 全局默认 prefetch(配置文件)
# rabbitmq.conf
# default consumer prefetch 通常按需在客户端设置
经验值:单条处理耗时短(毫秒级)时,prefetch 设 10~50;处理耗时长(秒级)时,设 1~10,保证负载均衡。能者多劳的关键是 prefetch 不要过大,让快的消费者多拿、慢的少拿。
五、优化手段二:队列容量与内存管理
RabbitMQ 的队列在内存吃紧时会把消息转存到磁盘(paging),这会明显降低性能。控制手段:
设置队列最大长度(x-max-length)。超过上限时,默认丢弃队头最老的消息(可配 x-overflow 改为 reject-publish 拒绝新消息)。
# 声明队列时设置最大长度 100 万条
rabbitmqadmin declare queue name=myqueue durable=true arguments='{"x-max-length":1000000,"x-overflow":"reject-publish"}'
注意:reject-publish 会让生产者收到 nack,业务要能处理;drop-head(默认)会悄悄丢老消息,可能丢失业务数据。
设置消息 TTL。给消息或队列设置过期时间,堆积太久无意义的旧消息自动清除。
# 队列级别消息 TTL(毫秒)
rabbitmqadmin declare queue name=myqueue durable=true arguments='{"x-message-ttl":3600000}'
惰性队列(lazy queue)。把队列类型设为 lazy,消息尽早写入磁盘,减少内存占用,适合堆积量大、吞吐要求不极致的场景。
rabbitmqadmin declare queue name=myqueue durable=true arguments='{"x-queue-mode":"lazy"}'
提升处理速度的根本办法:优化消费者内部逻辑。大部分堆积其实是消费者单条处理太慢导致的——循环里查数据库、同步调用外部接口、没有批量处理。把"逐条处理"改成"批量处理"(RabbitMQ 支持批量 ack),把同步外部调用改成异步,往往比单纯加消费者有效得多。
六、紧急处置:消化已有的堆积
已经堆了几百万条,怎么办?分情况。
如果是消费者挂了:先恢复消费者,让它自动把积压消化掉。
# 查看消费者状态,确认消费者重新连上
rabbitmqctl list_consumers
rabbitmqctl list_queues name messages consumers
如果消费能力就是不够:临时扩容消费者实例,但要防止把下游打死——加消费者前确认数据库/下游能承受更高的 QPS,必要时对下游加限流。
如果要快速泄洪(谨慎):
# 简单粗暴:清空整个队列(危险!消息全丢,不可恢复)
rabbitmqctl purge_queue myqueue
# 或在管理页/HTTP API 清空
curl -X DELETE -u guest:guest 'http://localhost:15672/api/queues/%2F/myqueue/contents'
风险提示:purge_queue 会永久删除队列中所有未消费消息,无法恢复。只适用于这些消息已经不再需要或可以重建的场景。执行前必须确认业务上能接受丢失,并优先考虑"导出后再清"或"迁移到新队列"等更稳妥的方式。回滚:无。清掉就没了,所以务必三思。如果要保命,先做 shovel 迁移或导出到文件。
更稳妥的泄洪方式:
- 临时增加消费者,让积压自然消化。
- 迁移消息:用
shovel插件把堆积的消息搬到另一个临时队列,用专门的高性能消费者消化。 - 分清轻重:如果业务上这些消息已过期无效,可以设 TTL 让它们自动过期,而不是手动 purge。
七、预防:让堆积不再发生
| 预防措施 | 说明 |
|---|---|
| 消费端水平扩展 | 消费者与生产者一起扩缩容,保持速率匹配 |
| 监控告警 | 对 messages_ready 设阈值告警(如超 10 万) |
| prefetch 合理 | 避免负载不均和频繁等待 |
| 队列设置上限/TTL | 防止无限堆积拖垮内存 |
| 消费者幂等 + 重试 | 消费失败可重试,避免因个别消息卡死整条队列 |
| 批量处理 | 把逐条改为批量,提升吞吐 |
| 死信队列 | 把处理失败的消息转入死信,避免阻塞正常消息 |
监控告警建议:
# 用管理 API 定时采集,配合 Prometheus/Grafana
# 关键阈值(经验值):
# messages_ready > 100000 预警
# messages_unacknowledged 持续 > 1000 预警
# consumers = 0 立即告警
常见问题
Q:为什么消费者明明在线,消息却都在 Unacked?
说明消费者接了消息但没 ack,通常是处理逻辑卡住(下游超时、死循环、线程池占满),或者 prefetch 太大导致消息都堆在它手里。先看消费者日志,再看 prefetch 配置。
Q:堆积到触发流控,生产者发不出消息怎么办?
优先降低内存/磁盘压力:加快消费、清理无用消息、扩容磁盘。不要只调高水位线阈值,那只是把爆炸时间往后推。
Q:加消费者一定有用吗?
不一定。如果瓶颈在下游数据库,加消费者只会把数据库压垮;如果消息有顺序要求,加并发会打乱顺序。加之前先确认下游容量和业务是否允许乱序。
Q:prefetch 设大还是设小好?
看单条处理耗时。快的设大(10~50),慢的设小(1~10)。原则是保证消费者之间负载均衡,避免一条慢消费者堵着大量消息。
Q:可以直接 purge 队列吗?
只有在确认消息不再需要、丢失可接受时才能用,因为它是不可逆的。生产环境优先用"加消费者消化"或"迁移消息"等温和方式。
总结
消息堆积的本质是"生产速率大于消费速率",危害有明确的恶化链条:延迟增大 → 内存占用高 → 触发流控阻塞生产者 → 全链路中断,所以要在早期处理。
处置前先用管理 API 和 rabbitmqctl 看清 messages_ready、messages_unacknowledged、consumers 三组指标,判断是消费不足、消费者卡住还是消费者挂掉。
优化优先从消费者入手:加消费者实例提升并发、调小 prefetch 保证负载均衡、把逐条处理改批量处理;同时给队列设 x-max-length、TTL 或惰性模式控制内存。
紧急泄洪要区分场景:消费者挂了先恢复,产能不足先扩消费者并给下游限流,只有当消息确实可丢时才用 purge_queue,且要清楚它不可回滚。
预防靠消费端与生产端同步扩缩容、对积压设监控告警、队列设容量上限和死信队列,让堆积在变成故障之前就被发现。
企业QQ咨询




