上一篇讲的 prefetch 是「消费者反压 broker」,这一篇反过来——broker 怎么反压生产者。当 broker 自己扛不住(内存满、磁盘满)时,它必须让生产者停下,否则自己会被压垮。这就是 broker 端的流控(flow control)。

很多生产事故和流控有关:生产者突然发不出消息、连接看似正常但 publish 全部阻塞、整个集群写入停滞。这些现象背后都是 broker 的流控机制在起作用。理解流控的触发条件和表现,才能快速定位「为什么发不出消息」这类问题。

两道防线:内存告警与磁盘告警

broker 流控的第一道触发是资源告警。RabbitMQ 有两个核心告警:

内存告警(memory alarm):当 broker 的内存使用超过 vm_memory_high_watermark(默认物理内存的 0.4),触发内存告警。触发后,broker 阻塞所有生产者的 publish 操作(连接会被 block,发消息直接卡住或失败),直到内存降下来。broker 用这个机制保护自己不 OOM。

磁盘告警(disk free alarm):当 broker 所在磁盘的剩余空间低于 disk_free_limit(默认 50MB,生产通常调大),触发磁盘告警。同样会阻塞所有 publish。磁盘满比内存满更危险(持久化写不进去 = 数据丢失),所以这个阈值要重点关注。

这两个告警是 broker 的「保命机制」——宁可让生产者阻塞,也不能让自己崩溃或丢数据。生产环境遇到「突然发不出消息」,第一反应就是查这两个告警(rabbitmqctl status 或管理界面看 alarm 状态)。

阻塞的具体表现

触发告警后,broker 对生产者的阻塞方式有两种(取决于客户端配置):

  • Blocking:broker 发一个 connection.blocked 信号给客户端,客户端停止发送。此时 publish 操作会阻塞(线程卡住),直到 broker 解除阻塞。如果客户端没正确处理 blocked 信号,可能一直卡着。
  • 非 blocking 但拒绝:某些配置下,broker 会在 connection 层面拒绝写入,客户端收到 IO 异常。

无论哪种,对生产者的体验都是「发不出消息」。这时不要去重启 broker 或调客户端参数——根本原因是 broker 资源告警,要解决资源问题(降内存、清磁盘),阻塞会自动解除。

credit 流控:更精细的内部节流

内存/磁盘告警是「全局的、粗暴的」——一旦触发,所有生产者全停。但 RabbitMQ 内部还有一套更精细的credit-based flow control(基于信用的流控),在告警触发之前就起作用。

credit 流控的思路(借鉴 TCP 的滑动窗口):broker 内部各模块之间(比如连接进程、信道进程、队列进程)维护一个「信用额度」。上游给下游发数据时,消耗信用;下游处理完归还信用;信用耗尽,上游停止发送。

这套机制的好处是逐级背压:如果某个队列消费慢、积压了,背压会沿着「队列 → 信道 → 连接」逐级传回,最终让那个连接的 publish 变慢,而不会影响其他连接。这是比「全局内存告警」精细得多的节流。

credit 流控的表现:某个生产者感觉「发消息变慢了」(延迟升高,但没完全阻塞),往往是它的某个下游队列积压,credit 流控在限流。这种情况下不是 broker 整体故障,而是局部背压——解决积压(加消费者、优化处理逻辑)就能恢复。

什么时候会触发流控

把触发流控的场景梳理清楚,便于排障:

  • 内存使用超 0.4 阈值 → 内存告警,全局阻塞 publish。原因通常是队列大量积压(消息吃内存)、内存碎片、或客户端 prefetch 过大导致消息堆积在客户端然后重新涌入。
  • 磁盘剩余空间不足 → 磁盘告警,全局阻塞。原因是持久化消息大量写盘、日志文件撑满、或磁盘配额本身太小。
  • 单个队列积压严重 → credit 流控限流该队列相关的连接。原因是消费者处理不过来或消费者全挂了。
  • Erlang 进程信箱堆积 → 某个 Erlang 进程(如某个繁忙队列)消息处理不过来,信箱堆积,触发该进程的流控。

排障时,看到 publish 变慢或阻塞,按这个顺序排查:先看全局告警(内存/磁盘),再看具体队列积压,再看 Erlang 进程状态。

生产环境的流控预防

流控是保护机制,但频繁触发说明系统已经不健康。预防的几个方向:

控制队列积压。 积压是内存压力的主要来源。保证消费者处理能力 ≥ 生产速率,积压不会无限增长。积压超出预期时,要么加消费者,要么用 lazy 队列把消息压到磁盘。

合理设内存阈值。 0.4 是默认值,一般不动。但如果 broker 还跑了别的进程(不推荐),或机器内存很大(几百 GB),0.4 可能让 Erlang VM 分配过多内存,反而引发问题。要按实际环境校准。

监控磁盘。 磁盘告警是「致命」的——触发后持久化都写不进去。要监控磁盘剩余空间,留足余量。disk_free_limit 生产环境建议设成几个 GB(比如 2GB),而不是默认的 50MB。

避免重连风暴。 大量客户端在 broker 抖动时同时重连,重连本身的开销会加剧 broker 压力,可能把 broker 从「慢」推向「挂」。客户端要用指数退避 + 抖动重连。

broker 流控的两道防线

收束:流控是 broker 的自我保护

流控不是 broker 的 bug,而是它的自我保护机制。内存和磁盘是有限资源,broker 必须有能力让生产者停下,否则会被无控制的写入压垮。理解流控的触发条件、表现、排障路径,才能在「发不出消息」时快速定位是 broker 在保护自己,而不是去怀疑 RabbitMQ 挂了。

阶段四「客户端与性能」到这里结束。下一阶段进入集群——多节点怎么组成集群、元数据怎么同步、网络分区怎么处理、跨机房怎么容灾。


关于十三Tech

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

十三Tech公众号二维码