<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>RabbitMQ on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/rabbitmq/</link>
        <description>Recent content in RabbitMQ on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Sat, 10 Oct 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/rabbitmq/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>RabbitMQ 发布端卡死：未确认消息撑爆内存水位</title>
            <link>https://blog.5772447.xyz/posts/200f6914/</link>
            <pubDate>Sat, 10 Oct 2026 09:00:00 +0800</pubDate>
            <guid>https://blog.5772447.xyz/posts/200f6914/</guid>
            <description>&lt;h2 id=&#34;问题背景&#34;&gt;&lt;a href=&#34;#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;订单系统与履约系统之间用 RabbitMQ 解耦，版本运行了三年，日均消息量百万级，单机 broker 带 6 个 vhost、40 多个队列，配置多年没动过。消费者的 prefetch 参数在接入时就没有设置——当时客户端采用的是默认行为，没人深究过这意味着什么；发布端用的是公司统一封装的 SDK,带 confirm 机制，业务侧对&amp;quot;发送成功率 100%&amp;ldquo;习以为常。&lt;/p&gt;&#xA;&lt;p&gt;10 月的一个大促预热日，订单量较日常上涨三倍。上午 10 点开始，订单服务陆续出现&amp;quot;发布消息超时&amp;quot;告警；到 10 点半，订单创建接口大面积转圈，监控大盘上 RabbitMQ 的发布速率曲线几乎贴到零——整个发布链路卡死了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;故障现象&#34;&gt;&lt;a href=&#34;#%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;故障现象&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;发布端症状统一：&lt;code&gt;publish&lt;/code&gt; 调用阻塞后超时，SDK 日志报 &lt;code&gt;connection blocked&lt;/code&gt;,重连后短暂恢复又再次阻塞。&lt;/li&gt;&#xA;&lt;li&gt;RabbitMQ 管理页面几乎打不开，刷出来后看到所有 connection 状态标着 &lt;strong&gt;blocked&lt;/strong&gt;;连接数正常、消费者在线数量正常。&lt;/li&gt;&#xA;&lt;li&gt;broker 系统日志出现 &lt;code&gt;memory resource limit alarm set on...&lt;/code&gt;,随后是密集的告警反复触发记录。&lt;/li&gt;&#xA;&lt;li&gt;消费者侧反而&amp;quot;看着正常&amp;rdquo;：消费速率没掉太多，只是比平时慢；broker 进程 CPU 不高，磁盘 IO 也不高——不是宕机，是&amp;quot;憋住了&amp;quot;。&lt;/li&gt;&#xA;&lt;li&gt;重启 broker 能恢复几分钟，但流量一上来立刻复发，指标曲线像方波。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;排查过程&#34;&gt;&lt;a href=&#34;#%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;第一步搞清 blocked 是什么机制。RabbitMQ 对发布端有一层叫 &lt;strong&gt;connection.blocked&lt;/strong&gt; 的背压保护：broker 内存使用超过 &lt;code&gt;vm_memory_high_watermark&lt;/code&gt;(默认系统内存的 40%)时，触发全局内存告警，所有发布连接被标记 blocked,客户端收到的 publish 会被挂起而不是丢弃——这是设计上的&amp;quot;停止进水&amp;quot;动作，防止 broker 被内存压垮。也就是说，故障现象里&amp;quot;发布卡死&amp;quot;不是 bug,而是告警机制在起作用；真正要查的是&lt;strong&gt;内存为什么涨到水位&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;第二步看内存的去向。&lt;code&gt;rabbitmqctl status&lt;/code&gt; 输出里 memory 段最显眼的是 &lt;code&gt;allocated_unacked&lt;/code&gt;;&lt;code&gt;list_queues name messages messages_unacknowledged&lt;/code&gt; 一对照，问题清楚了：40 多个队列里，几个核心队列的 &lt;strong&gt;messages 不多，但 messages_unacknowledged 高达数万&lt;/strong&gt;。未确认消息是什么：消息已经从队列推给消费者、消费者还没 ack,在这段时间里消息不能删除，必须驻留 broker 内存(除非分页到磁盘)。几万条业务消息(单条几百 KB)在等待 ack 期间全量占着内存，把水位顶穿了。&lt;/p&gt;&#xA;&lt;p&gt;第三步解释未确认消息为何膨胀。查消费者配置，发现消费端用的是客户端默认的 prefetch = 0(无限制)语义——对这批用多线程回调的消费者来说，broker 会一次性把队列里的消息&lt;strong&gt;尽可能多地推给消费者&lt;/strong&gt;，上限由网络窗口和消费者本地缓冲决定，而不是一个受控的数字。平时消息速率低、消费跟得上，未确认窗口自然小；大促时消息速率涨三倍，消费者处理单条要打外部接口(平均 80ms),推得进、确认得慢，未确认数迅速堆到数万，内存水位随之上穿。&lt;/p&gt;&#xA;&lt;p&gt;第四步看 broker 是否有别的内存缓解手段。RabbitMQ 在内存压力下会把 classic 队列的消息 paging 到磁盘，但分页是有限度的：只有部分消息能被换出，且换出速度远跟不上推送速度；order 系统这几个队列又是非 lazy 模式，消息默认驻留内存。加上发布端 confirm 窗口大(也是默认未收紧)，大量&amp;quot;已发布未确认&amp;quot;的消息同样占内存。三个默认值叠加，把 broker 的内存安全垫吃干净了。&lt;/p&gt;&#xA;&lt;p&gt;第五步确认磁盘水位排除：&lt;code&gt;df -h&lt;/code&gt; 显示数据盘余量 60%,disk_free_limit 未触发；CPU、GC 均平稳——故障链完全收敛在内存水位这一个点上。&lt;/p&gt;&#xA;&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;解决方案&#xD;&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;止血&lt;/strong&gt;：临时把 &lt;code&gt;vm_memory_high_watermark&lt;/code&gt; 从 0.4 调到 0.6(物理内存 16G,预留 6.4G 给系统与消费端)，给消费进度争取时间；同时把非核心队列的消费者扩容一倍，加快未确认消息的消化。发布端恢复后观察两小时，blocked 未再出现。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;治本&lt;/strong&gt;：给全部消费者显式设置 &lt;code&gt;basic.qos&lt;/code&gt; prefetch(核心队列 20、非核心队列 100),杜绝&amp;quot;一次性推满&amp;quot;；对 order 相关的三个大流量队列开启 lazy 模式(消息直接落盘，内存只留索引)，把内存占用从&amp;quot;与积压成正比&amp;quot;改为&amp;quot;常数级&amp;quot;。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;收紧发布端&lt;/strong&gt;：SDK 的 confirm 未确认窗口从默认值收敛到 1000，防止发布侧在途消息无限增长；发布超时告警阈值从 5s 调整到 2s,让阻塞更早被看见。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;压测验证&lt;/strong&gt;：按大促峰值的两倍流量做压力测试，验证 prefetch + lazy 组合下内存曲线平稳、无 blocked 触发，才允许上线。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;根因分析&#34;&gt;&lt;a href=&#34;#%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;直接根因是&lt;strong&gt;消费者 prefetch 未限制，大流量下未确认消息在 broker 内存大量驻留，触发内存水位告警，RabbitMQ 按设计全局阻塞所有发布连接&lt;/strong&gt;。深层根因有两个：其一，上线三年来 prefetch、lazy 模式、confirm 窗口三个关键的&amp;quot;内存安全参数&amp;quot;全部停留在客户端默认值，而这些默认值在低流量下看似无害、在高流量下必然出事；其二，监控只覆盖了&amp;quot;消费积压(messages)&amp;quot;，没有覆盖&amp;quot;未确认(messages_unacknowledged)&amp;ldquo;和内存水位这两个先导指标，故障是在发布端已经卡死后才被发现，而不是在内存爬升阶段被预警。&lt;/p&gt;&#xA;&lt;h2 id=&#34;预防措施&#34;&gt;&lt;a href=&#34;#%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;全部 MQ 消费者的 prefetch 参数纳入配置评审，禁止留空使用默认；大流量队列默认开启 lazy 模式；&lt;/li&gt;&#xA;&lt;li&gt;监控面板增加三个先导指标：&lt;code&gt;messages_unacknowledged&lt;/code&gt;、&lt;code&gt;vm_memory_used / vm_memory_limit&lt;/code&gt; 比值、connection blocked 计数，任一项超阈值即告警；&lt;/li&gt;&#xA;&lt;li&gt;大促等流量高峰前执行 MQ 专项压测，验证内存水位曲线与 blocked 阈值余量；&lt;/li&gt;&#xA;&lt;li&gt;把&amp;quot;内存安全参数&amp;quot;写进 MQ 接入规范：prefetch、lazy、confirm 窗口、TTL 四项为必填配置项。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;总结&#34;&gt;&lt;a href=&#34;#%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;这次故障里 RabbitMQ 的行为全程正确甚至可以说&amp;quot;救了自己&amp;rdquo;：水位告警触发全局 blocked,宁可停掉发布也不让 broker OOM 崩溃——真正失守的是配置层的三个默认值和监控层的两个盲区。消息中间件的很多默认参数是为&amp;quot;开始用&amp;quot;设计的，不是为&amp;quot;用三年后的峰值&amp;quot;设计的；把默认值当作已配置项，迟早要在最不能出事的时刻补交学费。压测能提前花的钱，故障时都会十倍还回来。&lt;/p&gt;&#xA;&lt;h2 id=&#34;延伸阅读&#34;&gt;&lt;a href=&#34;#%e5%bb%b6%e4%bc%b8%e9%98%85%e8%af%bb&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;延伸阅读&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/3863dad1/&#34; &gt;Kafka 消费者组积压，订单为何延迟两小时才处理？&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/083f4e6d/&#34; &gt;Kafka Rebalance 风暴致业务数据延迟两小时的定位&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
        </item></channel>
</rss>
