当前位置: 首页 > news >正文

用 JMeter 打通“异步入队 + 网关限流”的高并发压测实践

目标:在不打穿下游的前提下,验证入口链路的高并发承载能力,并形成可复制的压测脚本。

一、为什么要先压“入队链路”?

很多系统的重任务(报表、地图/视频处理、机器人调度等)都会走 “请求→入消息队列→异步消费” 的模式。
上线前必须回答三个问题:

  1. 峰值请求能不能稳稳接住?

  2. 网关限流能不能把洪峰挡在外面?

  3. 队列是否按预期累积,不触发级联雪崩?

本实践选择 Dry-Run + 禁用消费者 的“用法 A”:

  • 请求只入队,不真正执行耗时任务;

  • 既能压出真实入口 QPS,又不会把下游打穿;

  • 观测面(SkyWalking/RabbitMQ/JMeter)一样齐全。


二、压测目标与环境

  • 目标:100 QPS × 3 分钟(可 50→100→200 QPS 梯度爬坡)

  • 接口POST /external/gs/async/_mqSmoke(创建“烟囱任务”,仅入队)

  • 网关策略:必须带 X-Dry-Run:true,否则 403;命中限流返回 429

  • 观测:SkyWalking(ruoyi-gatewayruoyi-robot),RabbitMQ 控制台,JMeter 报表

  • 结果判定:JMeter Error%≈0;SW SuccessRate≈100%、Apdex 高;RabbitMQ Ready 累积

三、脚本结构(可直接照抄)

3.1 Test Plan 变量(User Defined Variables)

BASE_SCHEME=http
BASE_HOST=localhost
BASE_PORT=8080      # 或直连 9300
SUBMIT_PATH=/xxxxxx/_mqSmoke
POLL_PATH=/xxxxxxx
AUTH_HEADER=
POLL_INTERVAL_MS=1200
MAX_POLLS=20

提示:A 策略通常不启用轮询;只压入队链路。

3.2 HTTP Header Manager

  • Content-Type: application/json

  • X-Request-Id: ${__UUID()}

  • Authorization: ${AUTH_HEADER}(如需)

  • X-Dry-Run: true ← 关键

3.3 Sampler:Submit Task (POST)

  • Path:${SUBMIT_PATH}

  • Body:

    {"xxxxx":"dummy","taskName":"mq-smoke"}
    

3.4 线程组(Thread Group)

  • 调试:Threads=5, Ramp-up=10s, Loop Count=1

  • 正式:Threads=50, Ramp-up=60s,勾选 Specify Thread lifetime → Duration=180s

3.5 恒定吞吐定时器(Constant Throughput Timer)

  • Target throughput (samples per minute) = 6000(≈100 QPS)

  • Calculate Throughput based on = this thread group only

小公式:目标 QPS × 60 = 每分钟样本数。比如 100 QPS ⇒ 6000 samples/min。

3.6 断言与 429 处理

  • Response Assertion:Response Code 允许 200 / 202 / 429

  • JSR223 PostProcessor 将 429 记为成功:

    if (prev.getResponseCode() == '429') { prev.setSuccessful(true) 
    }
    

3.7(可选)轮询逻辑(非 Dry-Run 时用)

JSON Extractor

  • 变量:xxxx

  • JsonPath:xxxxxxx

While Controller 条件

${__groovy((vars.get('done')!='true') && ((vars.get('polls') as int ?: 0) < (${MAX_POLLS} as int)))}

轮询定时器:Constant Timer → ${POLL_INTERVAL_MS}

JSR223 PostProcessor

def s = (vars.get('status') ?: vars.get('state') ?: '').toString().toUpperCase()
def code = (vars.get('code') ?: '').toString()
def doneVals = ['DONE','SUCCESS','FINISHED','COMPLETED','OK']
if (doneVals.contains(s) || code == '200') { vars.put('done','true') }
int p = (vars.get('polls') as int ?: 0) + 1
vars.put('polls', String.valueOf(p))

四、执行步骤

  1. 预热(1 分钟 / 5 线程)

    • JMeter View Results Tree 绿灯;

    • SW“服务列表/端点负载”出现。

  2. 正式压测(100 QPS × 3 分钟)

    • 打开 Aggregate Report

    • 同时观看Skywalking

    • RabbitMQ 队列 robot.tempTask.q,Ready 累积曲线。

  3. 非 GUI 推荐(更稳)

    jmeter -n -t async-submit-poll.jmx -l result.jtl -e -o html
    

五、观测与判读

  • JMeter

    • 关注 #Samples / Average / P95 / P99 / Error% / Throughput

    • 示例(多轮实测):Error%=0,P95≈6ms,P99≈9–10ms,Throughput≈41–100/sec(视目标而定)。

  • SkyWalking

    • 关注 Service Load(calls/min)、Success Rate、Apdex、P95/P99。

    • 示例:ruoyi-gateway Load ≈ 2300–2900/min,Success 100%,Apdex≈0.83–0.94;
      ruoyi-robot Load ≈ 2800–4700/min,Success 100%,Apdex≈1.0。

  • RabbitMQ

    • A 策略下消费者禁用,Ready 持续累积是预期;Unacked≈0。


六、SkyWalking“防自杀”设置(很关键)

压测期间,观测系统太“勤奋”也会被打爆,建议:

  1. Java Agentagent.config 增量)

    sample_n_per_3_secs=10
    trace.ignore_path=/external/gs/async/_mqSmoke
    

    采样降低到“每 3 秒 ~10 条”,并忽略高频压测路径。

    2.OAP Server 内存(Windows 示例)

set SW_OAP_OPTIONS=-Xms512m -Xmx1024m -XX:+UseG1GC
bin\oapService.bat stop
bin\oapService.bat start

     3.UI 时间范围

         只看“今天”,避免渲染大量历史数据变卡。

七、常见问题与排错清单

Q1:另一台机器跑出来 Success 只有 50%,Apdex 0.3~0.4?
A:一般是本机资源/网络/观测开销导致:

  • JMeter 用 GUI 跑 + 开很多监听器,CPU 飙高;

  • 网络丢包/代理/防火墙;

  • 没有把 429 记为成功;

  • SW 采样太高/OAP 内存太小;

  • 网关策略不一致(是否强制 Dry-Run?是否命中限流?)。

Q2:SW 过 2 分钟就“白屏/转圈”?

  • 降采样(见上),忽略高频路径;

  • 给 OAP 多点内存;

  • 缩短 UI 时间窗口。

Q3:JMeter 吞吐达不到配置的 QPS?

  • Threads 不够、Ramp-up 太短、Timer 作用域不对;

  • 脚本夹了多余“思考时间”;

  • 机器顶不住:CPU/内存/网络/磁盘。

Q4:网关 403?

  • 确认头 X-Dry-Run: true;未带头被路由策略拦截是对的。

Q5:换一台电脑,为什么 SkyWalking 上看到的成功率、Apdex、负载和上一台不一样?

现象:同一套后端与脚本,不同压测机跑出来的 SW 成功率/Apdex/Load 有明显差异,甚至一台高、一台低。

根因分类 & 快速排查:

  1. 压测端(JMeter)差异

    • GUI 监听器过多、开着 View Results Tree/Graphs,CPU 抢占高 → 真实吞吐达不到配置值。

    • 机器性能/电源模式不同(省电/节能模式、CPU 频率被限)。

    • Java/JMeter 版本/JVM 参数不同,GC/HTTP 堆栈差异。

    • 脚本细节不一致:线程数、Ramp-up、Constant Throughput Timer 的“作用域”不是 this thread group only,或 429 未被标记为成功(JMeter 与 SW 统计口径不一致)。

    • 建议:非 GUI 模式运行 jmeter -n ...;只保留 Aggregate Report;固定 Java 版本;核对 .jmx 差异;把 429 设为成功。

  2. 网络路径差异

    • 一台走 公司代理/本机代理/VPN、一台直连;DNS 解析落到不同网关实例;丢包/抖动导致重传与超时。

    • 建议:两机都 tracert/ping 网关;禁用代理/VPN;固定目标 IP;同时抓一小段 tcpdump/Wireshark 看重传/RTT。

  3. SkyWalking 观测口径差异

    • 时间窗口不同(另一台选了更大范围或不是“今天”);筛选条件(实例/端点)不同。

    • 采样率不同:另一台机器或另一环境的 sample_n_per_3_secs/sampleRate 不一致;或者没有 trace.ignore_path,导致 UI/存储压力大,指标被稀释或 UI 卡顿。

    • OAP 内存不足/存储后端压力大 → 指标迟到/掉点。

    • 建议:两边统一时间范围(Today + 同步 NTP),清空所有筛选;统一 Agent 配置(采样 + 忽略压测路径),OAP 增内存(例如 -Xms512m -Xmx1024m);必要时只看压测时段 2–5 分钟窗口。

  4. 网关/服务端策略差异

    • 限流/熔断/鉴权配置在不同实例不一致;请求头(如 X-Dry-Run:true / Authorization)在一台机器未携带导致 403/429 比例变化。

    • 建议:抓包或开启网关 access log 校验请求头;对比实例配置;将压测流量固定到同一实例(临时关闭负载均衡或用权重/灰度)。

  5. 时钟偏差与统计口径

    • 压测机与 SW/OAP 时钟不同步,导致“每分钟调用数(Load)”按窗口聚合时分桶不齐。

    • 建议:所有机器启用 NTP;SW UI 选择近实时小窗口(例如最近 5 分钟)。

一键对齐清单(两台压测机都做):

  • 非 GUI 模式跑,Aggregate Report 保存到文件;

  • 检查 .jmx 完全一致(线程/Timer 作用域/断言/429 处理);

  • 固定 Java 版本,关闭省电模式;

  • 统一 Agent 配置sample_n_per_3_secs=10trace.ignore_path=/external/gs/async/_mqSmoke

  • OAP-Xms512m -Xmx1024m -XX:+UseG1GC,UI 只看“今天/最近 5 分钟”;

  • 关闭代理/VPN,固定网关实例或检查负载均衡;

  • 所有节点开启 NTP 对时。

结论:压测端供给能力 + 网络路径 + 观测采样/窗口 是造成 SW 指标差异的三大源头。把三者对齐后,两台机器的曲线会非常接近。

八、可复用产物

  • async-submit-poll.jmx:线程组 + 恒定吞吐 + Header + POST +(可选)轮询/断言/JSR223

  • 网关路由与限流规则:/external/gs/async/** + 强制 X-Dry-Run:true + 统一 429

  • SW 配置片段:sample_n_per_3_secstrace.ignore_path、OAP JVM 参数


九、实战结论

  • 在“Dry-Run + 禁消费”的保护模式下,入口链路稳定撑住 100 QPS×3min

    • JMeter Error%=0,P95/P99 低;

    • 网关/业务 Success≈100%、Apdex 高;

    • RabbitMQ Ready 按预期累积,无 Unacked 积压。

http://www.dtcms.com/a/389699.html

相关文章:

  • Linux服务器从零开始-服务器安装配置
  • Python第三节基本数据类型详解与使用指南
  • 【indextts2】之四:ubuntu24.04 经常跑满12G 显存、音色与录制关系很大
  • 【学习笔记】稳定币的技术原理与信任逻辑
  • 为 Elasticsearch Ruby Client 引入 ES|QL 查询生成器
  • 平安养老险陕西分公司积极开展“保障金融权益,助力美好生活”金融教育宣传周活动
  • 多线程的特点与同步、互斥锁
  • 将npm run dev 冷启动从 90s 优化到 8.5s的实践
  • 【附源码】基于SSM的小型银行贷款系统设计与实现
  • X-Forwarded-For
  • 中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
  • GitHub 上 Star 数量前 10 的开源项目管理工具
  • axios函数封装
  • NewSQL——核心原理与内部机制
  • 《从 0 到 1 打通网络服务任督二脉:域名、服务器与多站点配置全攻略》​
  • vue3学习日记(十七):动态路由使用解析
  • SpringBoot 启动流程
  • .NET驾驭Word之力:结构化文档元素操作
  • 解密F5负载均衡:优化网络性能的关键
  • 使用 .NET Core 6 Web API 的 RabbitMQ 消息队列
  • 时空预测论文分享:图时空注意力网络 ConvLSTM 时空演变
  • 千问大模型部署笔记
  • 网络:开源网络协议栈介绍
  • 设计模式(C++)详解—装饰器模式(3)
  • 双重锁的单例模式
  • 管理 Git 项目中子模块的 commit id 的策略
  • 跨境电商API数据采集的流程是怎样的?
  • rust编写web服务07-Redis缓存
  • 第三十三天:高精度运算
  • 写联表查询SQL时筛选条件写在where 前面和后面有啥区别