客户端治理讲完连接,这一篇讲消费者侧最核心的性能参数——prefetch(预取)。前面几篇多次提到过它,这一篇把它彻底讲透。

prefetch 是 RabbitMQ 消费侧吞吐调优的关键旋钮。设得对,消费者既高效又不被压垮;设错了,要么吞吐上不去,要么消息在客户端堆积导致 OOM。理解 prefetch 的工作机制,是做好消费者调优的前提。

默认行为:没有 prefetch 会怎样

先看不设 prefetch(或设成 0/无限)时的默认行为:broker 会尽可能快地把消息推给消费者,不管消费者处理得过来不。

假设一个队列有 1 万条消息、一个消费者。不设 prefetch 时,broker 会一口气把这 1 万条全推给消费者,消费者把它们缓存在本地内存里慢慢处理。问题:

  • 消费者内存被撑爆——1 万条消息全在客户端进程里,如果消息大,直接 OOM。
  • 处理慢的消费者堆积更严重——它处理一条要 1 秒,但 broker 已经把后面几千条都塞过来了。
  • 失去背压——broker 看不到消费者的真实处理能力,一味推送,队列很快被掏空,但消费者其实堵着。

这就是没有 prefetch 的问题:broker 的推送速度和消费者的处理速度完全脱钩,中间没有任何节流。

prefetch:限定在途未确认消息数

prefetch(也叫 QoS prefetch count)解决的就是这个问题。它限定:一个消费者在途未确认(unacked)的消息数量上限

设 prefetch=10 的语义是:这个消费者最多同时持有 10 条未确认的消息。broker 每推一条给消费者,未确认数 +1;消费者每 ack 一条,未确认数 -1。当未确认数达到 10,broker 停止推送,等消费者 ack 后才继续推。

这个机制的精妙之处在于它自动实现了背压

  • 消费者处理快(ack 快)→ 未确认数下降快 → broker 继续推 → 高吞吐。
  • 消费者处理慢(ack 慢)→ 未确认数维持在 10 → broker 停推 → 消息留在队列,不压垮消费者。
  • 消费者卡死(不 ack)→ 未确认数卡在 10 → broker 完全停推 → 消息安全留在队列,等消费者恢复或被重新投递。

prefetch 让 broker 的推送速度自动匹配消费者的处理速度,这就是「背压」——消费者用自己的 ack 节奏反向调节 broker 的推送。

prefetch 的取值权衡

prefetch 设多少合适?这是一个典型的权衡,没有万能值:

设太小(如 1):消费者每次只拿一条,处理完才拿下一条。问题:每条消息都要一次网络往返(ack + 下一条推送),吞吐受限。适合处理极慢、且每条消息都很重要的场景。

设太大(如 1000):消费者一次拿很多,吞吐高。问题:消息堆积在客户端内存,风险回到「不设 prefetch」的状态;多消费者时还会导致负载不均(快的撑死、慢的饿死)。

合理的中间值:让「消费者处理一条消息的时间」内,broker 能持续推送新消息,不空等。经验值通常在 10–100 之间,具体取决于:

  • 单条消息处理耗时。处理慢(秒级)→ prefetch 小一点(几十);处理快(毫秒级)→ prefetch 可以大一点(上百)。
  • 消息大小。消息大 → prefetch 小(避免内存压力);消息小 → prefetch 大。
  • 消费者并发度。多消费者共享队列时,单个消费者的 prefetch 不用太大。

实际调优方法:从一个小值(如 10)开始,压测观察吞吐和消费者内存,逐步调大,找到吞吐拐点。吞吐不再随 prefetch 增大而提升的那个值,就是性价比最高的点。

全局 prefetch vs 单消费者 prefetch

RabbitMQ 客户端有两个 prefetch 概念,容易混淆:

  • prefetch_count:每个 Channel 上每个消费者的在途上限。这是最常用的。
  • prefetch_global:整个 Connection 上所有消费者的在途上限(全局共享一个额度)。

一般用 prefetch_count(每消费者独立额度)。prefetch_global 适用于想给整个连接设一个总限制的场景,用得少。

prefetch 与消费者并发

prefetch 是「单个消费者」的参数。要提升整体吞吐,除了调 prefetch,还要考虑消费者并发数——一个队列挂几个消费者。

多消费者共享一个队列时(点对点模型),RabbitMQ 轮询分发。这时整体在途消息数 = 消费者数 × prefetch。比如 5 个消费者、每个 prefetch=20,整体在途上限 100。

消费者并发的权衡:

  • 并发太少:单消费者处理能力是上限,队列吞吐受限。
  • 并发太多:消费者间互相竞争,上下文切换开销增加,且如果下游资源有限(比如消费者要写数据库,数据库连接池有限),并发消费者会把下游打垮。

消费者并发数也要匹配下游处理能力。常见的做法:消费者并发数 ≈ 下游瓶颈资源数(如数据库连接池大小),避免消费者拿了消息却卡在等下游资源上。

prefetch 与可靠性的关系

prefetch 还和可靠性有关。在途未确认的消息,如果消费者崩溃,会被 broker 重新投递。所以:

  • prefetch 大 → 崩溃时要重投的消息多 → 重新投递的开销大(但这些消息不丢)。
  • prefetch 小 → 崩溃时重投的消息少 → 恢复快。

对于「处理很重要、绝不能丢」的消息,prefetch 反而要小一点(如 1–5),这样消费者崩溃时重新投递的消息范围可控,配合幂等设计,重投不会有副作用。

prefetch 与 Quorum Queue 的配合

用 Quorum Queue 时,prefetch 的意义更大。Quorum 的 ack 由 leader 经 Raft 日志处理(先写日志再复制到多数派),开销高于经典队列的本地 ack。如果 prefetch 设太小(如 1),每条消息都要等一次较慢的 ack 往返,吞吐会很难看。Quorum Queue 通常建议 prefetch 设大一点(几十到上百),用批量在途摊薄每条 ack 的处理开销。

prefetch 背压机制

收束:prefetch 是消费者调优的核心旋钮

prefetch 是 RabbitMQ 消费侧最重要的参数。它同时影响吞吐、内存、负载均衡、可靠性——理解它的工作机制,才能在「吞吐」和「稳定」之间找到平衡。调优的思路是:从一个保守值开始,按实际处理速度和消息大小调整,配合消费者并发数一起优化。

下一篇讲 broker 侧的流量控制——当 broker 自己内存吃紧时,它怎么反过来限制生产者,避免自己被压垮。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。我相信 AI 是程序员的最佳搭档。想跟完这套「图解 RabbitMQ」,欢迎关注公众号 「十三Tech」

十三Tech公众号二维码