news 2026/10/2 1:39:44

接口QPS与最大吞吐量自测:wrk/JMeter/Locust阶梯压测找拐点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
接口QPS与最大吞吐量自测:wrk/JMeter/Locust阶梯压测找拐点

接口上线前最怕的不是功能跑不通,而是功能全对、一上量就崩。上周帮一个做订单中台的团队做容量评估,他们提交的报告上写着"峰值 QPS 3200,性能良好",结果上线当晚流量刚到 1800 就开始大量超时,监控里 P99 直接飙到 3 秒。复盘下来问题不在服务本身,而在测法——压测客户端和被测服务跑在同一台 4 核开发机上,那 3200 是压测工具自己的天花板,跟接口能力没有半点关系。接口的 QPS 和最大吞吐量这组数字,几乎每个后端、测试、运维都要在某个时刻给出答案,但它没法靠"跑一次看着挺高"就下结论。这篇就把我这些年自测接口 QPS、找最大吞吐量的完整路子拆开讲:怎么理解这几个指标、工具怎么选、脚本怎么写、数据怎么读、拐点怎么定位、坑都在哪里。不管你是刚接触压测的新手,还是已经用过 JMeter 但总感觉结论不踏实的同学,都能照着本文的步骤在自己的环境里复现一遍。

1. 先把 QPS 和吞吐量这几个词对齐

1.1 QPS、TPS、并发数、响应时间到底是什么关系

QPS,Queries Per Second,每秒查询数。它在接口这个语境里指的是被测接口每秒成功处理的请求数量。很多人第一次接触会把它和 TPS 混用,其实两者口径不同:QPS 数的是请求次数,TPS 数的是业务事务数。举个具体例子,一个下单接口内部要查用户、扣库存、写订单三条链路,外部只调用一次,那么这一次调用对外记 1 个 TPS,但内部可能触发 3 次数据库查询,数据库侧就记了 3 个 QPS。所以看报告时先要问清楚这个数字是在哪个层面统计的,网关统计的、应用统计的、数据库统计的往往不是同一回事。

再说并发数和 QPS 的关系,这里有个特别实用的公式,叫利特尔法则,几乎所有容量估算都绕不开它:QPS ≈ 并发数 ÷ 平均响应时间(单位统一成秒)。代入一组数字感受一下,压测客户端保持 50 个并发连接,接口平均响应时间是 20 毫秒,那么理论 QPS 就是 50 ÷ 0.02 = 2500。这个公式的价值在于反向推导:如果你估算出业务峰值需要 5000 QPS,接口平均响应时间是 50 毫秒,那就意味着系统至少要能扛住 250 个并发同时在途,线程池、连接池、数据库连接数都得按这个量级去配。很多线上超时事故,本质就是并发数上去了但连接池还是默认 10 个,请求全堵在池子外面排队,响应时间自然雪崩。

响应时间本身也要拆开看。它包含网络往返、排队等待、业务处理、下游依赖四段。压测环境里客户端和服务常常在同一台机器或同一内网,网络往返接近于零,这就导致压测出来的响应时间比生产环境好看,但生产上多出来的那几毫秒到几十毫秒网络开销,在高并发下会被放大成排队。所以自测时最好让压测客户端和被测服务分开部署,哪怕不在同一台机器,至少不要争抢同一份 CPU 和网卡。

1.2 为什么"最大吞吐量"不是一个绝对值

这是最容易踩的认知坑。很多人把最大吞吐量理解成"我把并发拉到最高,QPS 冲到多少就是多少"。实际上这条曲线不是单调上升的:并发从 10 加到 100,QPS 可能从 500 涨到 4000;加到 200,QPS 到 5500,响应时间开始从 20 毫秒爬到 40 毫秒;继续加到 400,QPS 不但不涨反而回落到 4800,响应时间直接冲到 500 毫秒,错误率开始抬头。这中间那个"QPS 不再增长"的点,才是真正意义上的最大吞吐量,也叫拐点或饱和点。

关键点在于,最大吞吐量必须带着约束条件才有意义。我会在报告里统一写成这样的口径:在 P99 响应时间低于 200 毫秒、错误率低于 0.1%、服务端 CPU 不超过 70% 的前提下,单实例稳定支撑 4800 QPS。这样一句话,运维才知道可以拿它去做容量规划,业务方也知道超过它会发生什么。相反,只写一个孤零零的"最高 12000 QPS",既不知道当时的延迟是多少,也不知道错误率有多高,这种数字对于扩容决策几乎零价值。

还有一层常见误解是把"压测工具报出来的 QPS"直接当成服务能力。压测工具本身也是程序,它要建连接、拼报文、发数据、收响应、算统计,这些都要消耗 CPU 和内存。当客户端先于服务端到达瓶颈时,你看到的就是客户端极限,而不是服务极限。判断方法很简单:跑压测时同时盯着压测机和服务器的 CPU,如果压测机 CPU 先打满,那这轮数据直接作废。

1.3 动手之前先把手头这些东西准备好

自测接口性能不是打开工具就压,前期准备决定了结果可不可信。我一般按四块来准备。

第一块是环境对齐。被测服务的机器规格、JVM 或运行时参数、数据库和缓存的规格要和目标环境尽量一致。如果只能在开发环境压,那就必须接受结论要打折,并在报告里显著标注。数据库里的数据量也关键,订单表 1 万行和 1 亿行,同一个 SQL 的执行计划都可能不同,压出来的数字没法比。

第二块是数据准备。接口如果带缓存,每次请求都命中同一批热点数据,压出来的 QPS 会虚高得离谱。所以参数一定要打散,用户 ID、商品 ID 这类字段要随机化,随机池至少几千个,保证缓存命中率接近真实。如果接口涉及写操作,还要考虑数据的清理和隔离,不然压测几轮下来把测试库塞满了。

第三块是监控布点。压测时只看一个 QPS 数字等于盲人摸象。至少要同时采集:服务端 CPU、内存、GC 次数和停顿时间、线程池活跃数、数据库连接池使用率、数据库 QPS、慢查询数量、网卡流量。这些数据是后面判断瓶颈在哪的唯一依据。

第四块是明确目标。是测一个单接口的极限,还是测一条业务链路的整体吞吐?是想找拐点做容量规划,还是只做版本间的性能回归对比?目标不同,脚本和判据都不一样,这一步想清楚了后面能省一半时间。

2. 压测工具怎么选,别一上来就开大

2.1 主流工具的横向对比

工具没有最好,只有合适。我常用的几款放在一起对比一下,方便你按场景挑。

工具协议支持并发模型单机能力脚本能力上手成本
abHTTP单进程多线程低,几千 QPS 就见顶几乎无极低
wrkHTTPepoll + 多线程高,单机几十万 QPSLua低
JMeterHTTP、TCP、JDBC、gRPC 等线程池,一请求一线程中,受 JVM 和线程数限制GUI 配置 + 少量代码中
LocustHTTP 为主,可扩展协程(gevent)中高,可分布式Python中
k6HTTP、WebSocket、gRPCGo 协程高JavaScript中
自写脚本任意看实现低到中任意看人

ab 适合做一次十秒钟的快速试探,确认接口通、看看数量级,别用它出正式结论,因为它是单进程模型,并发一高自身就先饱和。wrk 是我做接口摸底的首选,C 语言实现、基于事件循环,一台 8 核机器压出十几万 QPS 很轻松,代价是它只支持 HTTP 且报告比较简单,复杂业务链路写起来吃力。

JMeter 的优势在于生态全,参数化、断言、关联、多协议、报告图表都有现成组件,适合做有业务逻辑的链路压测,缺点是一个虚拟用户对应一个线程,几千并发就开始吃内存,单机很难撑住上万并发。Locust 用 Python 协程,同样内存能跑更多并发用户,脚本灵活性也好,写业务链路很自然,缺点是单进程受 GIL 影响需要多进程或多机。k6 是近几年比较顺手的,脚本用 JS,命令行体验和指标输出都很清爽,适合塞进 CI 做性能回归。

2.2 选型背后的判断逻辑

我给团队定过一条简单的选型线:先摸底,再链路,最后回归。第一轮摸底用 wrk,目标是用最小成本确认接口有几个数量级的能力,以及服务端瓶颈大概在 CPU、数据库还是下游调用。第二轮做真实业务链路,用 JMeter 或 Locust,把登录、鉴权、多接口编排、参数化和断言都放进去,这时候压的是系统而不是单个 URL。第三轮是版本回归,用 k6 或者 wrk 固定脚本,每次发版前跑一遍,对比基线看有没有性能退化。

还有个容易被忽略的点是压测形态。真实流量不是一瞬间灌进来的,是有爬坡过程的。所以脚本里一定要有 ramp-up,JMeter 里就是那个 Ramp-up 秒数,Locust 里是-r参数控制每秒启动用户数。很多人把 Ramp-up 设成 1 秒,等于开局就对服务发动总攻,这样测出来的不是稳态吞吐,而是冷启动压力,结论会偏保守很多。

2.3 客户端瓶颈这个隐形杀手

我见过最多的"假数据"就来自客户端瓶颈,这里单独拎出来说。第一个表现是 CPU 打满,压测机的 CPU 先到 100%,服务端还在悠闲地喝茶。第二个表现是端口耗尽,Linux 默认本地端口范围大约 3 万多个,如果用的是短连接,每秒建连几百上千次,几十秒后端口就进入 TIME_WAIT 状态被占用,客户端开始报 "Cannot assign requested address"。这时候你可以用netstat -an | grep TIME_WAIT | wc -l看堆积量,用sysctl net.ipv4.ip_local_port_range看可用端口范围。

对应的处理办法有三条:改用长连接(HTTP keep-alive),让一个连接跑多个请求,端口复用率大幅提升;调整内核参数,把本地端口范围开大、启用 TIME_WAIT 复用;多机分布式压测,把并发摊到几台机器上。第三条最稳,也最接近真实场景,因为生产环境的流量本来就来自成千上万个客户端。

另外文件描述符也要提前放开。默认ulimit -n常常是 1024,压测客户端开几千个连接会直接报 "Too many open files"。压测前先执行ulimit -n 65535,服务端同理,尤其是 Nginx、Netty 这类基于事件循环的服务。

3. 三个工具的实际操作,从摸底到链路

3.1 用 wrk 做第一轮快速摸底

安装很简单,Ubuntu 上apt install wrk,macOS 上brew install wrk,想用最新版就拉源码make一下。

基础命令长这样:

wrk -t8 -c200 -d60s --latency http://127.0.0.1:8080/api/order/query

参数逐个解释:-t8是开 8 个线程,经验值是等于压测机的 CPU 核数,超了反而因为线程切换互相拖累;-c200是保持 200 个 HTTP 连接;-d60s是持续压 60 秒;--latency让它输出完整的延迟分布,没有这个参数你只能看到平均值,看不到长尾。

如果接口是 POST 且需要 JSON 报文,就用 Lua 脚本扩展:

wrk.method = "POST" wrk.body = '{"userId":10001,"page":1,"size":20}' wrk.headers["Content-Type"] = "application/json" wrk.headers["Authorization"] = "Bearer test-token-xxx"

固定报文有个问题,所有请求打同一个用户,缓存全命中。要贴近真实就得让参数随机,用 request 函数动态拼装:

local counter = 0 request = function() counter = counter + 1 local uid = 100000 + (counter % 5000) local body = string.format('{"userId":%d,"page":1,"size":20}', uid) return wrk.format("POST", nil, {["Content-Type"]="application/json"}, body) end

用wrk -t8 -c200 -d60s --latency -s post.lua http://127.0.0.1:8080/api/order/query跑起来,输出大概是这样:

Running 1m test @ http://127.0.0.1:8080/api/order/query 8 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 18.42ms 12.31ms 312.00ms 88.21% Req/Sec 2.71k 421.30 4.12k 72.35% Latency Distribution 50% 14.02ms 75% 21.88ms 90% 32.41ms 99% 102.36ms 1284632 requests in 60.02s, 486.21MB read Socket errors: connect 0, read 0, write 0, timeout 12 Requests/sec: 21403.55 Transfer/sec: 8.10MB

读这份报告有几个重点。Requests/sec是总 QPS,21403 这个数字要和服务端监控交叉验证。Latency Distribution比平均值重要得多,99% 分位 102 毫秒意味着有 1% 的请求超过一百毫秒,如果业务要求 P99 小于 100 毫秒,这个接口在 200 并发下已经踩线了。Socket errors那一行千万别忽略,哪怕只有 12 个 timeout,也说明连接层已经有压力了。

提示:wrk 的线程数不是越大越好。-t超过 CPU 核数后,多个事件循环互相抢占,QPS 可能不升反降。想提高单机压力,优先加-c而不是加-t。

跑完一轮别急着下结论,按 50、100、200、400、800 的梯度各跑一轮,把每轮的服务端 CPU 和 P99 记下来,拐点基本就浮出来了。这一步是后面所有分析的基础数据。

3.2 用 JMeter 搭一条带参数的完整链路

当接口需要先登录拿 token、或者一个业务动作要串好几个接口时,JMeter 更省事。非 GUI 模式是唯一推荐的方式,GUI 只用来编辑脚本:

jmeter -n -t plan.jmx -l result.jtl -e -o ./report

-n表示非 GUI,-t指定脚本文件,-l输出原始结果,-e -o在结束后生成 HTML 报告目录。GUI 模式跑压测时,界面的图形刷新本身就会吃掉大量 CPU 和内存,你会发现 GUI 跑出来的数字永远比命令行低一截。

脚本里几个关键配置我列一下。线程组设置为:线程数 300,Ramp-up 60 秒,循环次数选"永远",勾选调度器设置持续时间 300 秒。这个组合的意思是 300 个虚拟用户在 60 秒内逐渐启动,然后持续压 5 分钟,最后平滑停止。Ramp-up 设 60 秒是为了模拟真实爬坡,避免开局冲击。

参数化用 CSV Data Set Config,准备一个users.csv,里面放几千行 userId 和 token,配置好文件路径、变量名、是否循环。这样每个虚拟用户取到不同的参数,缓存命中率才能接近真实。

报文的构造要靠 HTTP Request 组件加 HTTP Header Manager,断言建议至少加两类:响应码断言(200)和响应体断言(比如检查返回 JSON 里code字段等于 0)。只断言 200 是不够的,业务失败的接口往往也返回 200,但 body 里写着错误码,这类错误在统计里会被算成成功,直接污染结果。

结果解读主要看聚合报告里的几列:Samples 是总请求数,Average 是平均响应时间,90% Line 和 99% Line 是分位数,Error % 是错误率,Throughput 就是 QPS。这里要特别注意 Error % 这一栏,JMeter 只把连接失败和断言失败算作错误,如果断言写得不严,错误率会虚低。

3.3 用 Locust 写更贴近业务的压测脚本

需要模拟复杂用户行为时,Locust 的代码化描述最舒服。一个典型的脚本:

from locust import HttpUser, task, between class OrderUser(HttpUser): wait_time = between(0.1, 0.5) host = "http://127.0.0.1:8080" def on_start(self): resp = self.client.post("/api/login", json={"user": "u1001", "pwd": "test"}) self.token = resp.json().get("token", "") self.headers = {"Authorization": f"Bearer {self.token}"} @task(3) def query_order(self): self.client.get("/api/order/query?userId=1001", headers=self.headers, name="查询订单") @task(1) def create_order(self): payload = {"skuId": 2001, "num": 1} self.client.post("/api/order/create", json=payload, headers=self.headers, name="创建订单")

wait_time定义每个虚拟用户两次任务之间的思考时间,between(0.1, 0.5)表示随机停 0.1 到 0.5 秒,这个参数非常关键,它决定了用户的行为节奏是否真实。@task(3)和@task(1)的权重表示查询和创建的比例是 3 比 1,用权重还原真实流量配比。name参数让多个不同参数值的请求在报告里聚合成同一行,不然每个 URL 都会单独统计,报告会乱成一团。

无界面模式启动:

locust -f locustfile.py --headless -u 500 -r 20 -t 5m --csv=result --processes 4

-u 500是总用户数,-r 20是每秒启动 20 个用户,5 分钟爬满 500 用户,--csv输出结果文件,--processes 4开 4 个 worker 进程绕开 GIL 限制。跑完后result_stats.csv里就是每个接口的请求数、失败数、中位数、P95、QPS,直接用表格工具打开就能做分析。

有个细节提醒一下,Locust 默认每个请求都会记一条统计,如果压的是静态资源或者高频小接口,统计本身的开销不能忽略。另外on_start里做的登录动作,每个虚拟用户只执行一次,这部分请求量在总报告中占比很小,做容量结论时记得把它剔除。

3.4 阶梯加压找拐点,这步最关键

单次压测只能告诉你"这个并发下是什么表现",找拐点必须做阶梯测试。我惯用的梯度是 50、100、200、400、800、1200,每一档持续 3 分钟,前一档结束到下一档开始之间空 30 秒,让系统从积压中恢复。压测顺序一定要从低到高,因为从高到低时系统带着上一轮的连接和缓存状态,数据不可比。

每档记录一份数据,填到这样的表里:

并发数实测 QPS平均 RTP95 RTP99 RT错误率服务端 CPU
50235021ms45ms88ms0%18%
100421024ms52ms96ms0%34%
200618032ms78ms140ms0.01%55%
400732055ms160ms320ms0.12%78%
8007480107ms380ms890ms1.80%94%
12006950173ms720ms2100ms6.40%98%

这张表读完的结论很清晰:从 400 并发到 800 并发,QPS 只涨了 2%,但 P99 从 320 毫秒涨到 890 毫秒,错误率从 0.12% 涨到 1.8%,这是典型的拐点信号。再往上到 1200 并发,QPS 直接掉头向下,说明线程争抢和排队已经超过收益。所以这个接口的稳定容量应该定在 400 到 500 并发之间,对外报的口径可以写成"满足 P99 < 200 毫秒、错误率 < 0.1% 的条件下,单实例支撑约 7000 QPS"。

判断拐点不需要什么高深算法,就盯三条线:QPS 曲线走平或掉头、P99 曲线明显上扬、错误率开始突破阈值。三者只要出现两条,就说明已经过拐点了,前一个档位就是安全容量。

4. 数据怎么看,把曲线翻译成能落地的结论

4.1 为什么必须盯分位数而不是平均值

我见过太多报告只写"平均响应时间 35 毫秒",这个数字掩盖的信息量太大了。假设 100 个请求里 95 个是 20 毫秒,5 个是 500 毫秒,平均值算下来是 44 毫秒,看起来还不错,但实际有 5% 的用户在等半秒以上。换成 P95 或 P99 来看,问题立刻暴露。

分位数的含义要说清楚:P99 等于 102 毫秒,意思是 99% 的请求在 102 毫秒内返回,剩下 1% 比它慢。业务上不同的分位对应不同的用户感知,一般接口性能建设按这个档次来定:P99 控制在 200 毫秒以内算良好,500 毫秒是警戒线,超过 1 秒用户就会明显感觉到卡。核心交易链路要求更严,P99 最好压到 100 毫秒以内。

还有一个隐性指标是最大响应时间。它反映的是最坏情况,虽然只占极小比例,但如果偶尔冒出几秒的请求,通常意味着某处发生了锁等待、GC 停顿或者连接池饥饿,这类问题的根因往往比平均值更能暴露系统隐患。我在看报告时习惯把 Max 和 P99 一起看,如果 Max 远高于 P99,比如 P99 是 100 毫秒而 Max 是 5 秒,说明系统存在偶发的长尾抖动,值得单独排查。

错误率也要区分类型。连接超时、读超时、HTTP 5xx、业务错误码,这几种的根因完全不同。连接超时通常是服务端 accept 队列满了或者线程池耗尽,读超时多半是业务处理太慢,5xx 要看应用日志,业务错误码则可能是参数问题或下游限流。把错误按类型拆开统计,比一个笼统的 1.8% 有用得多。

4.2 容量估算:把压测数字换算成机器数量

压测拿到单机容量之后,下一步是算需要多少台机器。这里有个标准的换算路径。先算业务峰值 QPS:假如一个接口日均调用量 2000 万次,业务有明显的高峰时段,峰值系数取 5(电商晚高峰通常在 3 到 8 倍之间,社交类可能更高),那么峰值 QPS 约等于 2000 万 × 5 ÷ 86400 ≈ 1157。

然后留安全余量。生产环境要考虑单机故障、发布期间的滚动重启、突发热点,一般按单机容量的 50% 到 70% 来规划。假设单机在 P99 约束下能稳定支撑 4000 QPS,取 60% 作为安全水位就是 2400 QPS,那么需要的机器数是 1157 ÷ 2400 ≈ 0.5,向上取整加冗余,至少部署 2 台,跨可用区的话就是每个可用区 2 台。

这套算法看着简单,但有几个容易错的地方。一是峰值系数不能拍脑袋,最好从历史监控里取真实数据,把过去三个月每天的峰值拉出来,取 P95 作为参考。二是单机容量必须是在同等配置、同等数据量、同等依赖条件下测出来的,开发机上测的数字拿到生产用,误差可能有好几倍。三是别忘了一致性哈希和负载均衡的影响,如果接口依赖本地缓存,扩容后缓存命中率会下降,单机实际能力反而变低,这时候要么改用集中式缓存,要么把容量估算调保守一些。

4.3 别只看自己的接口,下游才是隐藏的雷

这一点是我踩过最深的坑。有一次压测一个查询接口,QPS 轻松跑到 9000,报告写得漂漂亮亮,上线后一周数据库主库 CPU 飙到 90%。原因很简单:这个接口每调一次要打 1 次 Redis 和 2 次数据库查询,9000 QPS 的接口压力等于给数据库灌了 18000 QPS,而压测时只盯着接口的 QPS 看,完全没算下游的账。

所以压测期间必须同步采集下游指标:数据库的 QPS、活跃连接数、慢查询数量、主从延迟;Redis 的命中率、连接数、大 key 情况;下游 RPC 服务的响应时间和错误率。把这些和接口 QPS 放在同一张时间轴上,你会发现很多"接口性能问题"其实是下游传导上来的。

还有一个隐蔽情况是连接池。应用侧的数据库连接池如果配了 20 个连接,而每个请求平均占用 20 毫秒,那么连接池理论上只能支撑 20 ÷ 0.02 = 1000 QPS,超过这个数请求就开始排队等连接。压测时表现为 QPS 上不去、响应时间缓慢爬升、服务端 CPU 却不高,这时候去看连接池的等待时间指标,一眼就能定位。HikariCP 的poolWaitTime、Druid 的WaitThreadCount都是关键指标,压测时务必打开。

5. 常见问题与排查实录

5.1 一份可以直接对照的速查表

现象最可能的原因排查手段处理办法
QPS 上不去,压测机 CPU 100%客户端瓶颈top 看压测机,服务端监控对比换 wrk、多机分布式、加长连接
Cannot assign requested address本地端口耗尽`netstat -angrep TIME_WAIT
Too many open files文件描述符不足ulimit -n压测前和服务端都调到 65535
QPS 平稳但 RT 缓慢上涨连接池或线程池排队看 pool wait、线程池活跃数调大池上限,检查池是否被慢请求占满
第一轮快、第二轮慢缓存预热或 JIT 未预热对比首轮和次轮单独跑预热轮次,数据丢弃不计
服务端 CPU 不高但吞吐上不去下游依赖或锁竞争链路追踪、线程栈 dump优化慢 SQL、拆锁、加缓存
结果每次波动很大环境干扰、GC、其他进程争抢多轮重复取中位数独占压测机,固定环境变量
错误率突然跳升限流、熔断、连接被拒看错误类型分布和应用日志确认是否有流控规则生效,调整阈值

这张表里我最想强调的是最后两条。压测结果波动大往往不是被测系统的问题,而是压测环境有别的进程在抢资源,比如同一台机器上还跑着 CI 任务或者别人在编译。至于错误率突然跳升,一定要先确认是不是触发了限流,很多框架默认带了 QPS 上限或者熔断规则,压到一定程度就被拦下来了,这时候你测到的根本不是系统极限,而是策略上限。

5.2 关于预热和稳态的实操心得

JVM 应用有个特点,前几分钟性能会明显差于稳态,原因是类加载、即时编译、连接池初始化、缓存填充都在这个阶段发生。我第一次压测的时候没经验,直接拿 60 秒的开局数据算 QPS,结果比后来测的稳态数字低了快 40%,白高兴一场以为系统有问题。

正确的做法是先跑一轮预热,比如用目标并发的 50% 压两分钟,然后正式收敛数据。或者在脚本里做个标记,把前 30 秒的数据单独排除。Locust 和 k6 都能在报告阶段过滤时间段,wrk 就得靠分段跑或者用脚本解析原始结果。

数据层面的预热同样重要。如果接口依赖缓存,冷缓存下的表现和热缓存差好几个数量级。压测前先用脚本把热点数据刷一遍缓存,或者干脆接受冷启动数据,但要单独标注,别和稳态数据混在一起。我一般会给报告写两个数字:冷启动首分钟 QPS 和稳态 QPS,两者都写清楚,看的人自然能判断。

5.3 几件让我少走弯路的小事

第一,压测脚本和被测系统的版本号、配置、镜像 ID 一定要记录在报告里。我遇到过一次版本回归对比,两组数据差了 15%,排查半天才发现是其中一次压测前有人改了连接池配置,白折腾一整天。

第二,写压测结论时永远带上约束条件。不要写"最大 QPS 12000",要写"在 200 并发、P99 < 200ms、错误率 < 0.1% 条件下,单实例最大吞吐约 12000 QPS"。这个习惯一旦养成,和运维、业务的沟通成本会低很多。

第三,别在生产环境随随便便做全量压测。真要压生产,要么用影子流量做旁路压测,要么挑业务低峰期、限定压测时长和流量上限,并且提前把下游的扩容和降级方案准备好。我曾经见过一次压测把数据库主库打挂,连带影响了真实用户,这种事故的修复代价远高于压测本身带来的收益。

6. 我在实际项目里沉淀下来的几件事

做接口性能自测这些年,我最大的体会是:这个活儿的产出不是一个数字,而是一条边界。你需要清楚地知道系统在什么条件下表现良好、越过哪条线之后会出现什么后果、以及距离业务峰值还有多少缓冲。只给一个孤立的 QPS 数字,等于什么都没说。

所以我现在做容量评估会固定交付三样东西。第一是一张阶梯压测表,把每个并发档位的 QPS、分位数、错误率、资源使用率全部列出来,让看的人自己能看到拐点在哪。第二是一份瓶颈清单,写清楚当前瓶颈在 CPU、数据库、连接池还是下游调用,以及各项的水位百分比。第三是一份扩容建议,按业务峰值算出需要的机器数和安全水位。这三样加起来,才算是把"最大吞吐量"这件事讲明白了。

还有个小技巧分享给做性能回归的同学:把压测脚本连同环境准备脚本一起放进代码仓库,用 CI 定时跑,每次记录到基线库。这样任何一次提交引起的性能退化都能在发版前被发现,而不是等到线上报警。基线值不要取单次结果,取近十次的滑动中位数,这样能过滤掉环境抖动带来的噪声。

如果是刚接触这块的同学,我建议的入门路径是:先用 wrk 把一个最简单的查询接口从 50 并发压到 400 并发,手工记录一份阶梯表,感受一下 QPS 曲线是怎么走平的;然后换成你自己的真实接口,加上参数化和鉴权;最后再考虑上 JMeter 或 Locust 做链路编排。走完这三步,接口 QPS 和最大吞吐量的自测基本就通了。至于那些更细的调优,比如 JVM 参数、GC 策略、连接池算法,属于把边界往外推的功夫,得等你能稳定测出可信数据之后再去折腾,顺序反了容易南辕北辙。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 1:36:16

ApkTool电脑版反编译实战:从资源解码到回编译签名

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 1:35:37

魔兽RPG地图逆向实战:破解三国列传武将招募限制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 1:34:48

359张街景车辆图数据集:YOLO训练实战与避坑指南

简介&#xff1a;面向YOLO系列目标检测实战的车辆检测数据集&#xff0c;聚焦城市街道场景中的公交车、轿车、卡车、面包车四类目标&#xff0c;适用于目标检测算法训练与效果验证&#xff0c;适合入门至进阶的计算机视觉开发者直接使用。压缩包共1088个文件&#xff0c;包含36…

作者头像 李华
网站建设 2026/10/2 1:33:12

Multisim仿真24秒倒计时电路:555秒源与74LS192预置设计全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华