prefetchCount不能设太大也不能太小:设太大(如1000)会导致消息堆积客户端内存、其他消费者饥饿、OOM或超时重入;设太小(如1)则网络往返频繁、吞吐骤降50%以上;它控制channel级未确认消息上限,须在Consume前调用Qos,Go中典型值为10,0表示禁用QoS。

为什么 Qos 的 prefetchCount 不能只设大了事
设置太大的 prefetchCount(比如 1000)会让 RabbitMQ 一次性把大量消息推给一个消费者,导致「消息堆积在客户端内存里却迟迟不处理完」,其他空闲消费者拿不到任务——吞吐量看似高,实际是假象,还可能因 OOM 或超时触发 requeue,加重乱序和重复消费。
设得太小(比如 1)虽能保证公平性,但每处理一条就要等一次网络往返确认,CPU 和网络都空转,吞吐直接掉一半以上。
-
prefetchCount控制的是「未确认(unacknowledged)消息上限」,不是缓冲区大小,也不是并发数 - 它作用于 channel 级别,不是 connection 或 consumer 实例级别
- 必须在
channel.Qos()中显式调用,且要在channel.Consume()之前执行,否则无效
Go 客户端中正确调用 channel.Qos 的姿势
RabbitMQ Go 客户端(streadway/amqp)的 Qos 方法有三个参数:prefetchCount、prefetchSize、global。绝大多数场景只需关注前两个,第三个几乎总该传 false。
典型写法:
立即学习“go语言免费学习笔记(深入)”;
<pre class="brush:php;toolbar:false;">err := ch.Qos(
10, // prefetchCount:每个 consumer 最多持有 10 条 unack 消息
0, // prefetchSize:0 表示不限制字节大小(RabbitMQ 通常忽略此值)
false, // global:false 表示 per-consumer,true 才是 per-channel(已废弃,勿用)
)
if err != nil {
log.Fatal(err)
}
prefetchCount = 0 表示无限制,等同于没开 QoS,绝对不要在线上这么用-
prefetchSize > 0在实践中极少使用,因为消息大小难预估,且 broker 不强制校验,基本可忽略 - 如果用了多个
ch.Consume()在同一 channel 上(不推荐),global = true会让所有 consumer 共享这个总数,极易造成饥饿
怎么选具体的数值:从 workload 特征出发
没有银弹值,得看你的消息处理耗时分布和失败率。建议按以下逻辑试配:
- 平均处理时间 prefetchCount = 20 起步;若 CPU 利用率低、延迟稳定,可逐步加到 50
- 平均处理时间 200–800ms(如调外部 HTTP)→ 推荐 5–15,避免单个慢请求卡住整组预取
- 存在偶发超长任务(>5s)或高失败率(>5%)→ 必须降低到 3–5,并配合手动
ch.Nack()+requeue=false防止堆积 - 消费者实例数动态伸缩(如 K8s HPA)→ 把
prefetchCount设为固定值,别跟实例数联动,否则扩缩容时 QoS 总数剧烈波动
容易被忽略的副作用与验证点
设置了 Qos 后,光看消费速度还不够,得盯几个关键指标:
- RabbitMQ Management UI 的「Unacknowledged」列:应稳定在
prefetchCount × 活跃 consumer 数附近,长期远低于该值说明处理过快或 prefetch 太小;持续高于则可能 consumer 崩溃或卡死 - 客户端日志里是否频繁出现
AMQP: channel error或connection closed:QoS 过大会加剧内存压力,触发 GC 频繁或 TCP 超时 - 启用
amqp.Publishing.DeliveryMode = amqp.Transient时,Prefetch 对持久化消息无影响,但对 transient 消息的公平性更敏感
真正难的不是设数字,而是理解你自己的处理函数在真实负载下的响应时间分布——它决定了 prefetch 是杠杆还是枷锁。

















