JMeter 压测 RabbitMQ 这件事圈子里聊的人不少但真正能一次跑出有效结果的并不多。我见过太多把线程数拉满、跑出几个错误就截个图交差的压测也见过连 AMQP 协议基本握手都没搞清楚就抱怨中间件性能太差的场景。这篇文章不打算讲教科书上的理论我想从一次完整的压测实战出发把 JMeter 压测 RabbitMQ 从环境准备、脚本编写、场景设计到问题排查的全过程拆开揉碎把那些文档里不会写的坑和判断逻辑一并说清楚。无论你是刚接触 MQ 压测的测试工程师还是需要给系统容量做评估的开发人员这篇内容都能给你一套直接可落地的操作框架。1. 压测这件事想清楚再动手1.1 压测到底要测什么很多人一听到压测就觉得是并发拉高、使劲怼请求这个理解太粗糙。对 RabbitMQ 这类消息中间件做压测核心要回答的问题其实是这几个系统在单位时间内能处理多少条消息吞吐量 TPS/TQPS单条消息从生产到消费完成到底要多久端到端延迟消息积压到什么程度系统开始不可用容量边界资源消耗CPU、内存、磁盘、网络的增长曲线是否线性消费者处理能力和生产者产能是否匹配瓶颈到底在哪个环节我建议你在压测前就把这些问题明确写下来。比如我要测的是单个队列在 1KB 消息体下能撑住多少生产速率和我要测的是 10 个消费者并发消费时的端到端延迟这两个目标对应的脚本和场景是完全不同的。没有明确目标的压测跑出来的数据基本只能当参考不能指导容量规划。1.2 为什么用 JMeter 做 MQ 压测选择 JMeter 不是因为它是最专业的消息协议压测工具——严格来说如果只看协议层性能用专门的 load generator 会更轻量。但 JMeter 有一个很大的优势它是个通用型压测平台团队里熟悉度高脚本可维护性也还行。再者通过安装 AMQP 插件或引入 amqp-client 库JMeter 可以覆盖连接、发布、消费三种层级既能测协议本身也能压测业务链路的处理逻辑。真正专业的 MQ 压测通常要搭配两种工具来互相验证。一种是 JMeter 这类通用工具主要面向协议交互和业务链路建模另一种是专门的消息生产/消费脚本或命令行工具比如 RabbitMQ 自带的 perf-test用于对单节点极限吞吐做校准。两组数据对照起来看你才能判断 JMeter 这边的结果有没有被工具本身限制住。这是我踩过几次坑之后学到的经验。注意JMeter 本身是 Java 应用跑在大流量积压场景时要注意 GC 带来的毛刺。压测机自身的资源水位一定要监控否则结果会失真。2. 环境准备别让工具成为瓶颈2.1 RabbitMQ 环境与监控准备压测的第一步不是写脚本而是确认被测环境的规范性。我习惯先明确这几个要素RabbitMQ 版本不同版本对 Qos、镜像队列的实现有差异部署模式单机 or 集群镜像队列还是 Quorum Queue队列拓扑结构Exchange 类型、队列数量、绑定关系、死信策略消息体大小分布这一串如果没定下来压测结果就不具备可复现性。比如你用 3.8 之前的老版本测镜像队列和用 3.10 的 Quorum Queue 测性能模型会差非常多。监控方面RabbitMQ 自带的 Management UI 足够支撑初步观察但它的采样粒度比较粗瞬时尖峰容易漏掉。我建议你在压测机这边同时跑一个采集脚本每 2 秒拉一次管理 API 的队列指标把 ready、unacknowledged、publish、deliver 这些数值落到带时间戳的文件里。后面分析积压曲线时会非常有用。2.2 JMeter 安装与 AMQP 能力扩展JMeter 安装本身没什么特别5.x 版本即可。但默认的 JMeter 没有 AMQP 采样器需要扩展。具体有两套路线第一套方式是安装社区维护的 AMQP 插件。在 JMeter 的插件管理器中搜索 AMQP 就能找到安装后会出现 jmeter-amqp 相关的采样器。这套方式的好处是图形化配置看一眼就知道参数填什么缺点是插件更新节奏一般且脚本逻辑表达不够灵活。第二套方式是自己引入 RabbitMQ 的 Java 客户端依赖通过 JSR223 采样器直接写 Groovy 脚本。这套方式的好处是灵活度高协议层你能控制所有细节压测场景可以做到非常接近真实业务缺点是脚本门槛高一些需要自己处理连接管理。我强烈建议你优先掌握第二套。理由有两点一是真实生产环境的消息体往往不是纯文本可能带嵌套结构或二进制字段插件提供的简单发布工具很难模拟这种多样性二是在排查连接异常、手动 ack、事务模式这些场景时能用代码精确控制每一步不会一头雾水。JSR223 方式需要把 amqp-client、slf4j 等 jar 放到 JMeter 的 lib 目录下。这里有一个容易遗漏的点如果你的 JMeter 装了第三方插件管理扩展lib 下可能已经有一堆旧版本的依赖弄不好会跟 amqp-client 冲突。安全起见把要用的 amqp-client 版本放到 lib/ext 下并在脚本里动态加载时注意 classloader 顺序。我用过几次之后发现最省心的做法是给压测机准备一个独立的 JMeter 安装目录只放压测必需的依赖避免和日常调试用的那个实例混在一起。3. 连接层配置这是压测正确性的第一步3.1 连接参数背后的门道用 JSR223 写 AMQP 客户端第一步是建立 ConnectionFactory。参数基本是这组import com.rabbitmq.client.ConnectionFactory import com.rabbitmq.client.Connection ConnectionFactory factory new ConnectionFactory() factory.setHost(rabbitmq-node-01) factory.setPort(5672) factory.setUsername(mqauth) factory.setPassword(s3cret) factory.setVirtualHost(/perf-test) factory.setRequestedHeartbeat(30) // 心跳检测超时 factory.setConnectionTimeout(3000) // TCP握手超时 factory.setAutomaticRecoveryEnabled(true) factory.setTopologyRecoveryEnabled(false)有几个参数值得展开。setRequestedHeartbeat(30)是告诉 Broker 客户端的心跳检测超时是 30 秒。压测场景下如果某个消费者长时间不发送数据Broker 会依据这个值判定连接是否存活。压测环境里网络抖动少见但如果你在云端虚拟机上压测网络策略可能会误杀空闲连接心跳设太短会不停地重连设太长又会让 Broker 迟迟发现不了僵尸连接。setTopologyRecoveryEnabled(false)这个比较关键。自动恢复机制里包含拓扑恢复也就是声明队列、绑定关系这些会在断线后自动重建。压测场景里我们通常已经预建好队列不希望客户端擅自重建而且自动恢复可能引发大量重复声明干扰指标统计。3.2 连接复用与每次采样连接的区别压测脚本里最常犯的错误是在每次采样时都创建新连接。AMQP 连接的建立要走 TCP 握手、TLS 握手如果有、协议版本协商整个过程开销不小放在高并发循环里会把 Broker 连接数瞬间打爆产生大量 TIME_WAIT。正确做法是使用连接池。JMeter 的 JSR223 采样器有个特点每个线程组里的线程各自执行脚本但变量作用域可以共享。我在压测项目里的标准做法是在每个线程首次执行时建立唯一的连接用 JSR223 的 vars 存引用从这个连接上创建 Channel每个线程维护自己的 Channel压测结束后在 tearDown 线程组里关闭连接代码大概是import com.rabbitmq.client.Channel Channel channel vars.getObject(ch_ threadNum) if (channel null || !channel.isOpen()) { Connection conn factory.newConnection(jmeter-producer) channel conn.createChannel() vars.putObject(ch_ threadNum, channel) } // 然后利用 channel 执行 publish/basicGet 等操作有人可能问为什么不给每个线程都新建一个独立 Connection反正线程数也不多。我的观点是压测的目的是测 RabbitMQ 的极限连接数是权重之一但如果你用几千个线程、每人一个连接Broker 的进程文件句柄可能会先撑不住这样压出来的结论会误导容量规划。更贴近真实场景的做法是连接数控制在 5~20 个并发由 Channel 数来体现然后单独出一个线程组专门压连接数上限。4. 压测场景设计不只是并发越大越好4.1 生产消息场景发布能力的测量生产端的压测目标很直接就是看系统在一定的消息速率下能不能稳定发布、是否有大量 confirm 失败。真实业务里生产端通常是业务服务可能有事务边界、批量发送等逻辑。在 JMeter 里模拟生产场景时我会重点关注这几个维度。线程组配置这块一般不用永远运行而是定义持续时间。比如 10 分钟目的是为了观察稳态表现而不是只看头几秒的尖峰。线程数从 1 开始倍增1、2、4、8、16、32每档跑 2 到 3 分钟记录吞吐量和 RT。这种爬坡式压测比一把梭哈到 1000 并发更容易定位拐点。真实的生产速率爬坡测试消息量涨一倍响应时间的曲线斜率通常会经历三个阶段线性阶段、竞争阶段、过载阶段。找到线性到竞争的那个拐点就是推荐的日常运行水位。消息体大小对吞吐的影响很大我测过 64B 消息和 64KB 消息前者单机 TPS 可以高一个数量级。所以消息大小分布要贴近线上实际。如果你们业务里 90% 消息是 1KB、10% 是 100KB可以做一个随机生成函数权重按业务比例来。生产场景发布消息的示例脚本// 切换确认模式confirm 模式用于衡量发布可靠性 channel.confirmSelect() String exchangeName perf.exchange String routingKey perf.route byte[] messageBody new byte[actualSize] // 生成消息体内容模拟业务负载 messageBody {\trace\:\ UUID.randomUUID().toString() \,\payload\:\ messageBodyFiller \}.getBytes(UTF-8) // basicPublish 参数exchange, routingKey, 持久化属性, 消息体 AMQP.BasicProperties props new AMQP.BasicProperties.Builder() .deliveryMode(2) // 2 表示持久化 .contentType(application/json) .timestamp(new Date()) .build() channel.basicPublish(exchangeName, routingKey, props, messageBody)这里有三个值得注意的地方。deliveryMode(2)表示消息持久化。持久化消息会触发磁盘落盘这个开关对性能影响极大。测出来的指标必须标注是在什么持久化级别下得到的否则没有可比较性。对绝大多数要求不丢消息的业务持久化是必选项压测数据要以这个为基准。批量发布是很多系统采用的优化手段。RabbitMQ 的批量确认publisher confirm可以显著减少网络往返代价是单条确认粒度变粗。我在脚本里实现批量确认的方式是每发送 N 条就waitForConfirms()一次N 从 1 调到 100 对比吞吐变化。实测下来100 左右往往能到单条确认模式的几倍吞吐但前提是业务允许这种确认延迟。另外publish 操作本身的返回值调整也会影响结果。默认模式下basicPublish是异步的消息先进入本地缓冲区真正发送由底层线程处理。如果要统计单次发布耗时需要主动调用flush()或者等待确认否则 JMeter 记录的采样时间和真实网络发送时间不对齐。这个细节会直接影响聚合报告里的响应时间。4.2 消费场景消费能力的验证消费端压测往往比生产端更复杂因为消费速率受队列积压情况、消费者逻辑耗时、ack 策略三个因素叠加影响。先明确消费方式。RabbitMQ 消费者有两种姿势basicConsume是订阅式推送Broker 主动把消息推给消费者basicGet是拉取式消费者主动去队列里取一条。压测时我更推荐用basicConsume因为它是绝大多数真实业务使用的模式测试结果有代表性。使用basicGet会引入大量轮询开销测出来的数据往往偏低。basicConsume需要声明一个 Consumer 对象并且在回调里处理消息。JSR223 脚本里写回调要格外注意线程模型问题JMeter 的采样脚本在主线程执行而 Consumer 的回调跑在 RabbitMQ 客户端的消费线程上这个线程和 JMeter 采样线程不是同一个。如果你在回调里调用 JMeter 的 SampleResult 方法会有并发修改异常。我踩过这个坑之后采取的方案是采样线程负责生成消费者、注册回调并设置执行条件回调里只做前置计数然后用一个阻塞队列把接收到的消息推给采样线程处理。这么做虽然多了一道队列搬运但数据准确。消费端的 Qos 参数直接决定速率上限。channel.basicQos(prefetchCount)就是这次消费预取多少条消息它限制的是消费端本地缓冲区的大小。prefetch1 表示每次只从 Broker 拿一条处理完 ack 之后才拿下一条prefetch100 就是一次拉 100 条到本地处理完慢慢 ack。从小到大调 prefetch你会发现吞吐先升后平过大的 prefetch 不会带来额外收益反而会拉高内存占用。我从实际经验给出的参考是单条消息处理逻辑在 5 到 10 毫秒的场景下prefetch 20 到 50 之间是甜点区如果处理逻辑简单到 1 毫秒内prefetch 10 以内足矣。消费者 ack 模式也要注意编排。手动 ack 是必须的你要在脚本里模拟完整业务处理闭环。真实系统里的处理流程往往是拿到消息 - 做业务逻辑可能有 RPC 调用- ack。所以消费压测脚本应该长这样// 伪造业务处理耗时 long start System.currentTimeMillis() // 模拟业务处理从队列取消息然后执行 2ms 的负载 Thread.sleep(businessProcessTime) // 注意在这里根据每次取到的消息标识决定是否 ack channel.basicAck(deliveryTag, false)模拟业务耗时的意义在于消费速率 并发消费者数 / 单条处理耗时。如果你不模拟耗时一个高配置机器纯空转消费测出来吞吐会虚高到失去参考价值。普通场景下建议用一个Thread.sleep()或一个加权 CPU 忙循环来模拟业务开销。CPU 忙循环比 sleep 更贴近真实 CPU 密集型业务但如果你不确定业务类型sleep 是更安全的近似。4.3 场景编排混合和峰值生产端和消费端的压测不是孤立的。真实业务里生产和消费往往共存。建议把 JMeter 里的线程组设计成三类生产者线程组持续发布消息速率可配消费者线程组持续消费并模拟业务处理辅助线程组单独拉高积压、测延迟边界混合场景最关键的一个指标是积压变化。如果生产速率大于消费速率队列积压会线性上涨积压上涨的快慢直接反映了系统处理能力的缺口。你在压测脚本里可以加一个采样器定时查队列深度把这个值和 JMeter 自身记录的生产/消费 TPS 对齐看。当积压曲线持续上升且斜率稳定时就说明系统进入了稳态过载区这时候测量的延迟不反映正常情况而反映系统在压力下的排队延迟。这种数据对容量规划同样有价值。峰值场景模拟要靠调速器。JMeter 的Constant Throughput Timer可以设定每分钟吞吐用来做阶梯加载。但要注意它是作用于采样层级而不是消息层级如果你一个采样里发多条消息定时器的数值和实际消息吞吐对不上。我在项目里通常是前置一个整体 TPS 规划第 5 分钟到 3000 TPS第 10 分钟到 6000 TPS第 15 分钟到 12000 TPSJMeter 的定时器按这个目标配置。4.4 指标收集判断瓶颈JMeter 的聚合报告和图表监听器是基础中的基础但只看响应时间和吞吐不够。压测 RabbitMQ 的时候我一般会做四件事第一件JMeter 聚合报告里筛出三种数据平均响应时间、90% 行响应时间即 pct90下同、错误率。第 90 百分位响应时间比平均值更能反映体验因为平均值容易被极端尖峰拉高。发布消息时如果 90% 响应时间开始快速增长说明服务端正在排队处理确认这就是你找的瓶颈拐点。第二件管理 API 拉取队列积压。curl访问http://mq-host:15672/api/queues/perf-test取 messages_ready 字段每 2 秒一次记录到文件。压测结束后画一条时间线积压曲线从平稳转为直线上升的那一刻就是要找的容量上限。第三件Broker 节点资源包括 CPU、内存、磁盘 IO。RabbitMQ 消息落盘是顺序追加如果看到磁盘 write 队列变长说明磁盘在承受压力CPU 占用升到 80% 以上且持续大概率是锁竞争或者调度瓶颈。第四件连接和通道数量。开压测前记录基线连接数压测中连接数飙升但吞吐不涨说明大量连接在发握手包而不是在发消息脚本大概率有问题。5. 参数调优的完整过程与常见坑位5.1 从报告反推瓶颈的套路压测中出现吞吐不涨或者错误率上升不要急着改参数。我的排查顺序是固定的先看 JMeter 的错误类型。AMQP 是长连接协议错误往往不是 HTTP 状态码而是异常堆栈。常见的有channel is closed、connectionForUser is blocked、message body write timeout每一个指向的问题不同。channel is closed大概率是内存告警或者队列资源被删connection blocked是 Broker 进入内存/磁盘流控模式write timeout说明本地缓冲区积累已满网络发送速度跟不上生产速率。然后看队列积压。消费端不消费时生产端的basicPublish照样可以成功因为消息先进 Broker 内存队列积压只是增加内存使用。当积压大到触发 Broker 内存阈值时所有生产连接会被阻断这个现象在报告里会表现为发布正常某些秒级区间全部失败。遇到这类情况先看管理接口的节点内存水位确认是不是流控触发。再看 Broker 端日志。RabbitMQ 的日志会明确写出流控触发条件、连接被拒绝原因、队列不匹配告警。日志时间戳和 JMeter 的采样时间戳交叉比对能定位到分钟级的因果。5.2 连接、内存、线程问题速查我把这几年压测中遇到的高频问题整理成一张速查表遇到类似情况可以直接对照。症状可能原因优先排查项发布 TPS 上不去但 Broker CPU 不高单 Channel 发送有锁约束或确认模式开销过大增加 Channel 数调整确认批大小消费速度慢消费者 CPU 打满消息体序列化开销或业务睡眠模拟不合理检查消息解码逻辑换成字节数组处理大量connection blocked错误Broker 内存水位触发流控调整vm_memory_high_watermark或检查消息积压压测机本身 GC 抖动大JMeter 堆内存不够给 JMeter 设置-Xms4g -Xmx4g减少监听器数量响应时间规律性尖峰定时任务、GC、网络重传等多因素加 jstack 看一眼 JMeter 线程状态连接数一直涨压测结束不释放连接未关闭或自动恢复机制在重连检查 tearDown 脚本显式关闭连接池压测机本身的问题经常被忽略。JMeter 开启聚合报告后大量采样数据写入内存和磁盘监听器开销不可小觑。压大规模前先把不必要的监听器关掉只保留一个汇总写入 CSV 文件等压完再离线分析。我跑过 2 万 TPS 以上的场景JMeter 客户端都出现了 CPU 占用过 100% 的情况结果混入了很多噪声后来全换成一键采集脚本之后数据才干净。5.3 系统层参数配合调优RabbitMQ 底层依赖 Erlang 虚拟机连接数、内存、调度器配置和系统层参数都会影响压测上限。下面几个设置我几乎每次压测前都会检查。文件描述符限制。RabbitMQ 官方文档建议调高ulimit -n。每个连接至少要消耗若干个文件描述符如果连接数上到几千默认的 1024 肯定不够。运行 RabbitMQ 的操作系统用户需要确保 nproc 和 nofile 都放宽。TCP 缓冲区方面如果压测机同 RabbitMQ 之间的网络带宽较小调大内核参数可以提升单连接吞吐。net.core.rmem_max和net.core.wmem_max建议调高在压测机侧生效即可。注意不要盲目调太大TCP 缓冲区过大会让慢消费场景下的积压数据停留在操作系统层反而延迟了 Broker 流控的触发。Erlang VM 参数通常在rabbitmq.conf或环境变量里配置。默认的P进程数量上限、K开启异步线程池等会影响大并发连接下的稳定性。有经验的压测团队会提前测试连接数从 1000 到 10000 时 Broker 的 CPU 和内存增长曲线先保证连接层能扛住再谈消息层。6. 脚本组织与数据后处理6.1 工程化组织压测脚本早期我压测特别喜欢在 JMeter 里堆一堆线程组最后看的时候一团乱麻。后来学到一招用不同测试计划文件组织不同目标每个文件只做一件事perf-publish.jmx纯发布压测perf-consume.jmx纯消费压测perf-mixed.jmx混合场景压测perf-storm-peak.jmx峰值突发压测每个文件里配置全局变量比如消息大小、线程数、测试持续时间、路由键等。改用全局变量后调参不用一层层翻 UI直接在命令行加-Jthreads100 -Jduration600传参跑批量回归也容易自动化。命令行跑 JMeter 是压测工程化的基础别再用图形界面挂个通宵了。基础命令是jmeter -n -t perf-publish.jmx -l result.jtl -e -o report-dir -Jthreads64 -Jduration300通过-J参数注入变量再配合定时任务就能实现夜间无人压测。6.2 指标后处理与结果沉淀JMeter 输出的 JTL 文件直接用 Excel 打开能看但要做趋势分析还是得写脚本。我一般用 Python 读 JTL按秒聚合出 TPS、平均 RT、P95、P99。再加一步把每秒积压数据合并进来形成一张完整的大盘时间表。后处理的核心是发现对齐关系。比如第 360 秒积压开始涨对应到 JMeter 里第 360 秒生产者吞吐达到 X TPS消费者吞吐不到 X那 X 就是消费端的处理上限。把所有压测轮的拐点都汇总可以画一条吞吐与响应时间的关系曲线。这条曲线就是容量评估的核心素材。压测报告的沉淀也要做。每次压测结束输出三个结论一是量化结论拐点吞吐、极限延迟、资源水位二是配置基线线程数、消息大小、Qos 参数、Broker 配置三是改进建议比如消费者连接数该加、prefetch 该调、队列该拆分。把这些归档到文档里后续版本迭代再跑一轮压测对比同一指标是变好还是退化比每次从零开始摸索效率高得多。7. 总结一些实操中的避坑心得做 JMeter 压测 RabbitMQ 这几年有几条经验写下来遇到类似情况可以少走弯路。第一永远先确认监控数据的对齐。压测报告的“响应时间”和 RabbitMQ 的“处理时间”很可能不是同一层的东西。压之前先小流量核对一次端到端关系确认你统计的生产/消费 TPS 和 Broker 管理界面显示的数字在同一量级。第二不要忽视压测机自身的负载。跑高吞吐压测之前先单独压一下 JMeter 机器看看单机最大能产生多大 TPS。如果客户端先达到极限被测系统的瓶颈数据就失真了。多台压测机联调时要注意时钟同步日志和指标的合并分析才靠得住。第三消息体生成不要用固定字符串。固定内容在压缩生效时网络测出来偏快也不贴近真实业务。用随机 UUID 填充消息体顺带模拟业务字段分布压测结论说服力更强。第四边压边看连接数。RabbitMQ 对连接数上限有保护客户端连接数超过限制会出现writer exception和大量AMQP connection shutdown。看到这类错误先压连接数再看消息吞吐。第五压测持续时间不能太短。我见过太多团队只跑 60 秒就下结论但 RabbitMQ 的内存流控触发有水位延迟热点队列在几百秒之后才会显现问题。每次压测至少跑 5 到 10 分钟而且前面 1 到 2 分钟数据作为预热期丢弃统计口径从稳定期开始算。最后分享一个我常用的技巧压测结束后别急着清理队列积压。保持队列不消费让 Broker 空跑几分钟观察它在积压状态下的内存和磁盘表现。这个场景能暴露出生产环境可能遇到的风险水位也方便你验证清空积压时消费者能不能无缝承接压力。这对后续处理极端突发流量会有实实在在的帮助。
阅读完成 · 觉得有帮助?