压测要回答的问题
压测不是为了得到一个漂亮的 QPS 数字,而是为了回答三个问题:当前配置下的容量拐点在哪、瓶颈在哪个组件、限流和扩容的阈值该设成多少。没有这三个答案,压测报告上那个“峰值 12000 TPS”除了贴在大屏上没有任何用处。
所以压测的第一件事是写下目标,比如“在 P99 小于 300ms、错误率低于 0.1% 的前提下,单集群能承载 8000 TPS 的下单请求”。目标里必须同时包含吞吐和延迟,只有吞吐没有延迟,等于说“能接住但接得很难看”。
模型:场景、数据、发压机
场景要贴近真实比例。纯读接口压到 10 万 TPS 很容易,但线上的读写在同一个连接池、同一个数据库上互相争抢,8:2 的混合场景往往才是真实瓶颈。思考时间(think time)也不能省,全程满压会把连接池和队列的排队效应放大到不真实。
数据决定结果是否可信。用同一个 userId 压秒杀,第二次开始全部命中“已参与”判断,测出来的是一段几乎不访问数据库的逻辑。参数化至少要做到:用户 ID 分散、商品 ID 覆盖热点与冷门、部分请求故意命中不存在的 ID 来验证穿透保护。
发压机是最容易被忽略的瓶颈。JMeter 的线程模型偏重,单机跑到几千 TPS 就可能自己先满 CPU;wrk 基于事件循环,单机打几十万 RPS 的简单 GET 不成问题,但脚本复杂度和结果分析能力弱得多。无论哪种,都要盯着发压机的 CPU、网络带宽和 TIME_WAIT 数量。
JMeter 与 wrk 的分工
| 工具 | 适合 | 不适合 |
|---|---|---|
| JMeter | 多接口串行流程、参数化、断言、结果报告 | 单接口极限吞吐(线程开销大) |
| wrk | 简单 HTTP 接口的吞吐与延迟分布 | 复杂业务流、依赖上下文的场景 |
一个比较高效的做法是:用 wrk 找单接口的极限和延迟分布,用 JMeter 做端到端业务流和混合场景,两者结果互相印证。
-- seckill.lua:每个请求换一个用户,避免全部命中同一份缓存
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
local counter = 0
request = function()
counter = counter + 1
local uid = (counter % 20000) + 1
wrk.body = string.format('{"activityId":9001,"userId":%d,"quantity":1}', uid)
return wrk.format("POST", "/api/seckill")
end
# 8 个线程、400 连接、压 120 秒,输出延迟分布
wrk -t8 -c400 -d120s --latency -s ./seckill.lua http://gateway.internal/api/seckill
# JMeter 无界面模式,阶梯加压,结果落 jtl 并生成 HTML 报告
jmeter -n -t seckill.jmx -l result.jtl -e -o report \
-Jthreads=500 -Jramp=60 -Jduration=300 -Jhost=gateway.internal
JMeter 的 -J 参数可以覆盖 jmx 里的 ${__P(threads,100)} 占位符,把压测参数外置,同一份脚本就能跑不同量级,不必每次改文件。
结果怎么读
先看延迟分布,再看吞吐。平均响应时间是最没用的指标,一次 2 秒的停顿会被十万次 20ms 稀释得看不见。要看 P90、P99、P999 和最大值,并且和错误率一起看。
判断是否饱和有一个简单方法:继续增加并发。如果 TPS 不再上升而延迟线性上升,说明已经饱和,多出来的并发只是在排队。真正的容量拐点就在这条曲线的拐弯处,而不是压到系统崩掉的那个点。
定位瓶颈时,四类指标要同时看:应用侧的线程池活跃数与队列长度、JVM 的 GC 停顿、数据库的连接数与慢查询、以及中间件的连接池和超时。压测期间打开链路追踪,直接看耗时集中在哪一跳,比逐层猜测快得多。
# 压测期间同步采样,事后与压测曲线对齐
jstat -gcutil 1 5000 60 > gc-during-test.log
mysql -h 10.0.3.11 -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW GLOBAL STATUS LIKE 'Threads_running';"
常见坑
发压机先扛不住。 客户端 CPU 打满、端口耗尽、带宽跑满,压出来的曲线是客户端的形状,不是服务端的。
不预热就压。 JVM 需要 JIT 预热、连接池需要建连、缓存需要填充,前 30 秒的数据通常完全不能看。正式采样前先低压力跑两分钟。
只在单机压。 单实例 2000 TPS 不代表集群能线性扩到 10 个实例,网关、数据库、Redis 都是共享瓶颈,扩容测试必须在集群层面做。
压测数据污染生产。 用真实用户和真实商品下单,压完留下几万条脏订单。压测流量要有明确标记,落到可以清理的独立数据上。
没有限流就压到底。 压测本身会把下游打挂,影响同集群的其他服务。压之前先把限流阈值调好,让系统在被压垮之前先拒绝。
报告只写一个数字。 没有环境说明、没有数据规模、没有延迟分布、没有瓶颈结论的压测报告,三个月后没人能复现,也无法用于容量规划。
评论