Skip to content
Go back

JMeter压测方法论——从QPS概念到渐进式性能优化

JMeter 压测方法论:从 QPS 概念到渐进式性能优化

一句话结论(30s)

JMeter 压测的核心不是”跑出最高 QPS”,而是用渐进式加压找到性能拐点——因为只有先分清楚 QPS(HTTP 请求数)与 TPS(业务事务数)、看 P99 而非 Average,再配合客户端 + 服务端双视角监控,才能定位”为什么慢”,而不是只知道”慢了”。

核心原理(2min)

底层深入(5-10min)

QPS vs TPS:别再混用了

这是压测的第一个分水岭——概念不清,后面的所有数据解读都是错的。

QPS(Queries Per Second):每秒查询数。关注的是”请求”的数量,不关心请求里有没有写操作。一个 GET /user?id=1 是一个 query,一个 POST /order 也是一个 query。QPS 是 HTTP 层面的指标。

TPS(Transactions Per Second):每秒事务数。关注的是”业务动作”的完成数量。一次”下单”涉及扣库存、写订单表、扣优惠券、发消息——这是一个事务,不是一个请求。TPS 是业务层面的指标。

关系:一个事务可能需要多个请求才能完成,所以 TPS <= QPS。在对读多写少的系统做压测时,二者差距不大(一个请求就是一个事务);在压测下单这类写密集场景时,TPS 通常远低于 QPS。

💡 思考穿插:为什么 TPS 一定 ≤ QPS,而不是反过来? 因为 QPS 数的是「发出去的请求」,TPS 数的是「完成的业务动作」,而一个业务动作往往由多个请求组成(下单 = 扣库存 + 写订单 + 发消息),所以业务数 ≤ 请求数。读多写少时一个请求就是一个事务,两者才接近;写密集场景一个事务要打好几个请求,TPS 就被 QPS 远远甩开。

**并发数(Concurrency)**不等于 QPS。并发数是”同时在进行中的请求数”,QPS 是”每秒完成的请求数”。公式:

QPS = 并发数 / 平均响应时间(秒)

如果一个请求平均 100ms 完成,100 个并发线程 → QPS = 100 / 0.1 = 1000。如果响应时间飙升到 1 秒,同样 100 个并发 → QPS 降到 100。

Thread Group 配置策略

JMeter 的 Thread Group 是压测的引擎,三个核心参数:

Number of Threads (users): 100   → 并发线程数
Ramp-up period (seconds): 20     → 在 20 秒内逐渐启动这 100 个线程
Loop Count: 10                   → 每个线程执行 10 遍

Ramp-up 的隐藏技巧

Ramp-up = 20 意味着每秒启动 100/20 = 5 个线程。这个值很关键:

经验值:Ramp-up = 线程数的 1/5 到 1/10。100 个线程用 10-20 秒启动。

不要用 Loop Count = Forever

很多教程教人设 Loop Count = Infinite 然后手动停止——这会导致测试结果受”停止时机”影响,不可复现。用固定次数 + Duration 组合:比如 Loop Count = 100,Scheduler Duration = 300 秒——跑完或超时即停,结果稳定。

聚合报告:别只看 Average

Label   #Samples  Average  Min  Max   Std.Dev  Error%  Throughput  Received KB/sec  Sent KB/sec
/login  5000      45       12   3200  89       0.00%   98.3/sec     15.2              3.1

Throughput 就是 QPS(99 行/秒)。Average(平均响应时间)是最具误导性的指标——10 个请求 9 个 10ms、1 个 2000ms,平均 209ms,能反映什么?

💡 思考穿插:为什么「平均 209ms」反而掩盖了真相? 因为平均值对极端值不敏感——那 1 个 2000ms 的长尾被 9 个 10ms 稀释成了「看起来还行」的 209ms。但用户体验恰恰由最差的那批请求决定,所以要看 P99(99% 的请求在多少 ms 内完成)才能把长尾延迟暴露出来。平均值回答「整体快不快」,P99 回答「最慢的那批用户痛不痛」。

真正重要的三个指标:

  1. P99(99 分位):99% 的请求在多少 ms 内完成。上述例子中 P99 可能是 2000ms——虽然只有 1% 的用户受影响,但这 1% 的用户体验是灾难性的。
  2. Std.Dev(标准差):响应时间的波动程度。标准差大说明系统不稳定——有时快有时慢,通常指向 GC 停顿、锁竞争、网络抖动。
  3. Error%:错误率。高于 0.1% 就要排查——是超时?还是业务逻辑 bug?

监控体系:JMeter + Prometheus + Grafana

不要只看 JMeter 的报告。 JMeter 告诉你”客户端看到的”(请求花了多久),但它不知道”服务端发生了什么”。一个请求从 50ms 变成 5000ms——是数据库慢查询?是 GC?是线程池满了?JMeter 回答不了。

标准三层监控:

第一层:JMeter(客户端视角)— 看吞吐、延迟、错误率
第二层:Prometheus + Grafana(服务端视角)— 看 CPU、内存、GC、线程池、连接池
第三层:慢查询日志 / Arthas / JFR(代码视角)— 定位到具体方法

关键服务端指标

指标含义告警阈值
JVM Heap Used堆内存使用> 80%
GC Pause TimeGC 停顿时间> 200ms
Thread Pool Active活跃线程数接近 max
DB Connection Active数据库活跃连接数接近 max
Tomcat busy threads繁忙线程接近 maxThreads

A/B 测试:Nginx 流量分割

压测不能在生产环境跑——你在上面打 1000 QPS,真实用户再加 500 QPS,数据库直接被双倍流量打崩。

正确做法:Nginx 做流量分割,把所有 /压测用户ID/ 的请求转发到专门的压测节点。

upstream stress_backend {
    server 10.0.1.10:8080;  # 压测专用节点
}

upstream prod_backend {
    server 10.0.1.1:8080;
    server 10.0.1.2:8080;
}

server {
    location /api/ {
        if ($http_x_stress_test = "true") {
            proxy_pass http://stress_backend;
        }
        proxy_pass http://prod_backend;
    }
}

压测节点和线上节点配置完全一致(同样的 CPU、内存、JVM 参数),只是流量隔离。

渐进式压测三阶段

阶段一:基线测试

用低并发(10 线程)跑一遍,记录 QPS 和 P99。这是基准线——后面的所有优化效果都跟基线对比。

阶段二:梯度加压

10 → 50 → 100 → 200 → 500 → 1000(线程数)

每档跑 5 分钟,观察 QPS 是否线性增长、响应时间是否突然飙升。拐点就是瓶颈——在这个点之前加线程能提升 QPS,过了这个点再加线程 QPS 反而下降。

💡 思考穿插:为什么过了拐点,加线程 QPS 反而下降? 因为系统的处理能力有上限——通常是数据库连接池、CPU、锁之一先被打满。拐点之前,加线程能「填满」空闲资源、提升吞吐;拐点之后资源已耗尽,再加线程只会让请求排队、上下文切换变多,响应时间飙升,单位时间内真正完成的请求反而变少。拐点不是「跑不动了」,而是「资源边界到了」。

拐点的常见原因:

阶段三:优化对比

找到瓶颈、优化、用同样的压测参数再跑一次。对比优化前后的 QPS 和 P99。优化的效果必须有数据说话,不能说”我觉得快了”。

总结

JMeter 压测是一个”提出假设 → 加压验证 → 数据定位 → 优化 → 再验证”的闭环,不是跑一下就完了。核心原则:

  1. 区分 QPS 和 TPS——说清楚你在测什么
  2. 别看 Average,看 P99——长尾延迟才是体验杀手
  3. 客户端 + 服务端双视角——JMeter 告诉你”慢了”,Prometheus 告诉你”为什么慢了”
  4. 渐进式加压——找到拐点比跑出最高 QPS 更有意义

章末提问

追问 1:QPS 和 TPS 到底有什么区别?压测时该看哪个?

结论先行:QPS 是 HTTP 层的每秒请求数,TPS 是业务层的每秒事务数,TPS ≤ QPS;测接口吞吐看 QPS,测业务流程(如下单)看 TPS。因为 一个事务可能包含多个请求,混用会让数据解读错位——把 TPS 当 QPS 会高估吞吐,把 QPS 当 TPS 会低估业务处理能力。

追问 2:为什么压测要看 P99 而不是 Average?

结论先行:因为平均值会被少数极快请求稀释,掩盖长尾;P99 反映的是「最差 1% 用户的体验」,而这批用户往往才是流失的关键。因为 一个 2000ms 的长尾 + 9 个 10ms,平均 209ms 完全看不出系统其实已经不稳,只有分位数才能暴露「最慢的那批请求到底有多慢」。

追问 3:为什么渐进式加压找到拐点,比压出最高 QPS 更有意义?

结论先行:因为最高 QPS 只是「打满那一刻的数字」,拐点才是系统真实容量的边界。因为 过了拐点再加压,QPS 不升反降、延迟飙升,说明已到瓶颈;只有找到拐点,才知道是该扩容、还是该优化连接池 / CPU / 锁,而不是盲目堆并发去追求一个虚高的数字。


Share this post on:

Previous Post
Java AIO与io_uring——为什么AIO在Linux上是"伪异步"?
Next Post
JDBC批量插入——从1000次网络IO到1次