性能测试通常由专业的测试团队负责,那还需要我们开发学这个干嘛呢?了解性能测试的指标、分类以及工具等知识有助于我们更好地去写出性能更好的程序,另外作为开发这个角色,如果你会性能测试的话,相信也会为你的履历加分不少。

本文结合了笔者的实践经验以及向测试团队请教所得,除此之外,我还借鉴了一些优秀书籍,希望对你有帮助。

平时大家说“压测”,很多时候其实是在泛指性能测试相关工作:用工具模拟用户请求,观察系统在不同压力下的吞吐量、响应时间、错误率和资源使用情况。实际做压测时,先说清楚这次要验证什么。

不同角色看网站性能

不同角色看网站性能

用户

用户

当用户打开一个网站的时候,最关注的是什么?当然是 网站响应速度的快慢。比如我们点击了淘宝的主页,淘宝需要多久将首页的内容呈现在我的面前,我点击了提交订单按钮需要多久返回结果等等。

所以,用户在体验我们系统的时候往往根据你的响应速度的快慢来评判你的网站的性能。

开发人员

开发人员

用户与开发人员都关注速度,这个速度实际上就是我们的系统 处理用户请求的速度。

开发人员一般情况下很难直观的去评判自己网站的性能,我们往往会根据网站当前的架构以及基础设施情况给一个大概的值,比如:

项目架构是分布式的吗?

用到了缓存和消息队列没有?

高并发的业务有没有特殊处理?

数据库设计是否合理?

系统用到的算法是否还需要优化?

系统是否存在内存泄漏的问题?

项目使用的 Redis 缓存多大?服务器性能如何?用的是机械硬盘还是固态硬盘?

……

测试人员

测试人员

测试人员一般会根据性能测试工具来测试,然后一般会做出一个表格。这个表格可能会涵盖下面这些重要的内容:

响应时间;

请求成功率;

吞吐量;

……

运维人员

运维人员

运维人员会倾向于根据 基础设施和资源的利用率 来判断网站的性能,比如我们的服务器资源使用是否合理、数据库资源是否存在滥用的情况、当然,这是传统的运维人员,现在 Devops 火起来后,单纯干运维的很少了。我们这里暂且还保留有这个角色。

性能测试需要注意的点

性能测试需要注意的点

几乎没有文章在讲性能测试的时候提到这个问题,大家都会讲如何去性能测试,有哪些性能测试指标这些东西。

明确压测目标

明确压测目标

压测之前先想清楚目标,否则很容易变成“工具跑起来了,报告也生成了,但不知道结论是什么”。

常见目标有三类:

上线前容量评估:验证当前机器规格、实例数量、数据库配置和缓存策略是否能支撑预期流量。

优化后效果验证:确认慢 SQL、缓存、线程池、JVM 参数或架构调整之后,吞吐量、RT、错误率是否真的变好了。

瓶颈定位:通过逐步加压观察 CPU、内存、GC、数据库连接池、线程池、MQ 堆积等指标,找出最先扛不住的环节。

这三类目标对应的压测方法不一样。容量评估关注峰值水位和安全冗余,优化验证关注前后对比,瓶颈定位则需要更细的监控和链路追踪。

了解系统的业务场景

了解系统的业务场景

性能测试之前更需要你了解当前的系统的业务场景。 对系统业务了解得不够深刻,我们很容易犯测试方向偏执的错误,从而导致我们忽略了对系统某些更需要性能测试的地方进行测试。

比如我们的系统可以为用户提供发送邮件的功能,用户配置成功邮箱后只需输入相应的邮箱之后就能发送,系统每天大概能处理上万次发邮件的请求。很多人看到这个可能就直接开始使用相关工具测试邮箱发送接口,但是,发送邮件这个场景可能不是当前系统的性能瓶颈,这么多人用我们的系统发邮件,还可能有很多人一起发邮件,单单这个场景就这么人用,那用户管理可能才是性能瓶颈吧!

历史数据非常有用

历史数据非常有用

当前系统所留下的历史数据非常重要,一般情况下,我们可以通过一些历史数据初步判定这个系统哪些接口调用的比较多、哪些服务承受的压力最大,这样的话,我们就可以针对这些地方进行更细致的性能测试与分析。

另外,这些地方也就像这个系统的一个短板一样,优化好了这些地方会为我们的系统带来质的提升。

请求模型要尽量贴近真实流量

请求模型要尽量贴近真实流量

单接口压测只能说明这个接口在当前条件下的表现,不能直接代表系统真实容量。真实用户访问系统时,通常会先登录、浏览、查询、下单、支付或提交表单,不同接口之间有顺序、比例和数据依赖。

因此,压测脚本最好按照真实业务链路来设计:高频接口多打一点,低频接口少打一点;读多写少的系统不要把写接口比例放得过高;需要鉴权、库存、订单状态流转的接口,也要提前准备合法的测试数据。否则,测出来的 QPS 很高,线上一到真实流量还是可能出问题。

先准备退出机制

先准备退出机制

压测尤其是线上压测,开始前必须准备好停止手段和保护阈值,例如最大 QPS、最大并发数、错误率阈值、P99 RT 阈值、CPU/内存阈值、数据库连接池阈值等。

一旦系统出现大量 5xx、RT 持续飙升、队列堆积不下降、数据库连接耗尽等情况,要能快速停止压测或自动熔断。压测的目的是发现问题,不是把线上服务打穿。

常见性能指标

常见性能指标

做压测时,指标不能孤立看。RT、吞吐量和并发数放在一起,才能判断系统是在正常处理请求,还是已经开始排队。

QPS、并发数与 RT 的关系

响应时间

响应时间

响应时间要先区分观测位置。对于后端接口压测,响应时间 RT(Response Time) 通常指压测客户端发出请求到收到响应的时间,包含网络传输、网关、服务端处理和响应返回。对于前端页面性能,用户体感还会受到 HTML 解析、JS 执行、资源加载、渲染和交互延迟影响,这类问题更适合用 Lighthouse、Chrome DevTools、WebPageTest 等工具分析。

平均 RT 只能看整体趋势。用户体感更容易被 P95、P99 这些长尾请求影响。接口压测和线上监控一般会同时看 P50、P90、P95、P99;核心链路在请求样本足够大时,再看 P999。

例如 P99 = 500ms,表示 99% 的请求能在 500ms 内返回。剩下 1% 的慢请求,可能来自 Cache Miss、慢 SQL 或 GC STW,它们会长时间占用连接和工作线程。并发高的时候,线程池被占满,新请求开始排队或被拒绝;上游再继续超时重试,流量还会被放大。大量超快响应会拉低平均值,长尾问题就被平均 RT 掩盖掉了。

下面只是一个低延迟在线接口的示例目标,不是所有系统通用标准:

分位值示例目标说明P90< 200ms大多数用户的正常体验P99< 500ms长尾体验与线程占用风险P999< 1s高流量核心链路的极端长尾

分位值示例目标说明

分位值

示例目标

说明

P90< 200ms大多数用户的正常体验

P90

< 200ms

大多数用户的正常体验

P99< 500ms长尾体验与线程占用风险

P99

< 500ms

长尾体验与线程占用风险

P999< 1s高流量核心链路的极端长尾

P999

< 1s

高流量核心链路的极端长尾

阈值最好从业务 SLO、用户体验要求、上游超时时间、下游依赖能力和历史基线里推出来。另外,P999 对样本量比较敏感。如果一次测试只有几千个请求,P999 很容易被偶发抖动影响;如果接口本身 QPS 很低,P999 的统计意义也有限。这类接口可以先看 P99,或者拉长压测时间收集更多样本。

失败模式:当下游偶发抖动导致 P999 飙升时,如果上游没有合理超时、限流和熔断,请求会长时间占用连接、线程或协程。进一步叠加重试后,可能出现线程池耗尽、连接池耗尽、队列堆积、错误率上升,严重时才可能触发 OOM。

失败模式:当下游偶发抖动导致 P999 飙升时,如果上游没有合理超时、限流和熔断,请求会长时间占用连接、线程或协程。进一步叠加重试后,可能出现线程池耗尽、连接池耗尽、队列堆积、错误率上升,严重时才可能触发 OOM。

并发数

并发数

并发数指系统在同一时刻正在处理的请求数量。

并发数反映了系统的 负载能力。需要注意区分以下概念:

并发用户数:同时在线的用户数量。

并发请求数:同一时刻系统正在处理的请求数量。

最大并发数:系统能够承受的最大并发请求数,超过此值系统可能出现性能下降或崩溃。

RPS、QPS 和 TPS

RPS、QPS 和 TPS

RPS(Requests Per Second):服务器每秒处理的 HTTP/RPC 请求数,更适合接口压测。

QPS(Queries Per Second):服务器每秒处理的查询次数,常用于数据库查询、搜索查询,也常被宽泛地当作请求吞吐量。

TPS(Transactions Per Second):服务器每秒完成的业务事务数,例如一次下单、一次支付、一次注册。

QPS vs TPS:TPS 更偏业务语义。比如一次下单可以算 1 个 TPS,但它背后可能包含查询库存、创建订单、扣减库存、调用支付 4 次 HTTP/RPC 请求,对后端服务来说可能就是 4 RPS,还可能产生更多次数据库 QPS。

QPS vs TPS:TPS 更偏业务语义。比如一次下单可以算 1 个 TPS,但它背后可能包含查询库存、创建订单、扣减库存、调用支付 4 次 HTTP/RPC 请求,对后端服务来说可能就是 4 RPS,还可能产生更多次数据库 QPS。

吞吐量

吞吐量

吞吐量 指系统单位时间内处理的请求数量。RPS、QPS、TPS 都是常用量化指标。

Little's Law(利特尔法则):在系统未饱和的稳态下,平均在途请求数 ≈ 吞吐量 × 平均响应时间。这里的并发数指系统中正在处理或排队的平均请求数,不是在线用户数;RT 使用平均响应时间,不是 P99;吞吐量使用完成请求的平均速率。

平均在途请求数 ≈ 吞吐量 × 平均响应时间

例如平均 QPS 为 500,平均 RT 为 200ms(0.2s),系统内平均在途请求数约为 500 × 0.2 = 100。

500 × 0.2 = 100

这个公式只能用在线性响应区间。并发继续上去后,CPU 调度、锁争用(Lock Contention)和排队时间会一起放大,RT 往往非线性上升,吞吐量到达拐点后进入平台期甚至下降。图里的“曲棍球棒曲线”(Hockey Stick Curve)就是在说明这个现象:拐点之后 QPS 不升反降,系统已经进入非线性区。

QPS 与并发数的曲棍球棒曲线

所以,生产容量不能只靠公式推算,还要通过全链路压测验证真实极限。

系统活跃度指标

系统活跃度指标

PV(Page View)

PV(Page View)

访问量,即页面浏览量或点击量,衡量网站用户访问的网页数量;在一定统计周期内用户每打开或刷新一个页面就记录 1 次,多次打开或刷新同一页面则浏览量累计。PV 从网页打开的数量/刷新的次数的角度来统计的。

UV(Unique Visitor)

UV(Unique Visitor)

独立访客,统计 1 天内访问某站点的用户数。1 天内相同访客多次访问网站,只计算为 1 个独立访客。UV 是从用户个体的角度来统计的。

DAU(Daily Active User)

DAU(Daily Active User)

日活跃用户数量,指一天内登录或使用产品的用户数(去重)。

MAU(Monthly Active Users)

MAU(Monthly Active Users)

月活跃用户人数,指一个月内登录或使用产品的用户数(去重)。

实战计算示例

实战计算示例

生产级容量评估:只用 DAU 乘固定系数估峰值,很容易低估活动流量。整点秒杀、大促开抢这类场景,峰值通常和日均流量不是一个量级。可以先用历史日均请求量估算日均 QPS,再结合业务峰值倍数(如日均的 3~10 倍,视业务而定)得到初步峰值 QPS,最后根据 Little's Law 反推目标并发数并加上 1.5~2 倍安全冗余。这个结果只能作为压测目标的起点,最终还要通过 全链路压测(结合真实流量录制与回放,如 GoReplay)摸底真实吞吐量,而不是停在纸面估算。

生产级容量评估:只用 DAU 乘固定系数估峰值,很容易低估活动流量。整点秒杀、大促开抢这类场景,峰值通常和日均流量不是一个量级。可以先用历史日均请求量估算日均 QPS,再结合业务峰值倍数(如日均的 3~10 倍,视业务而定)得到初步峰值 QPS,最后根据 Little's Law 反推目标并发数并加上 1.5~2 倍安全冗余。这个结果只能作为压测目标的起点,最终还要通过 全链路压测(结合真实流量录制与回放,如 GoReplay)摸底真实吞吐量,而不是停在纸面估算。

GoReplay

性能测试分类

性能测试分类

测试类型目的常见做法基准测试(Benchmark)建立性能基线,方便前后对比固定环境、固定脚本、固定数据负载测试(Load Test)验证预期负载下是否达标按预估流量模型逐步加压到目标水位容量测试(Capacity Test)找到当前系统可承载上限逐步加压,观察吞吐拐点和资源瓶颈压力测试(Stress Test)观察超限后的退化、保护和恢复超过预期负载,验证限流、熔断、自愈稳定性测试(Soak/Endurance)验证长时间运行稳定性以接近真实负载持续运行较长时间峰值测试(Spike Test)验证突增流量承受能力短时间快速升压,再快速回落,观察恢复情况

测试类型目的常见做法

测试类型

目的

常见做法

基准测试(Benchmark)建立性能基线,方便前后对比固定环境、固定脚本、固定数据

基准测试(Benchmark)

建立性能基线,方便前后对比

固定环境、固定脚本、固定数据

负载测试(Load Test)验证预期负载下是否达标按预估流量模型逐步加压到目标水位

负载测试(Load Test)

验证预期负载下是否达标

按预估流量模型逐步加压到目标水位

容量测试(Capacity Test)找到当前系统可承载上限逐步加压,观察吞吐拐点和资源瓶颈

容量测试(Capacity Test)

找到当前系统可承载上限

逐步加压,观察吞吐拐点和资源瓶颈

压力测试(Stress Test)观察超限后的退化、保护和恢复超过预期负载,验证限流、熔断、自愈

压力测试(Stress Test)

观察超限后的退化、保护和恢复

超过预期负载,验证限流、熔断、自愈

稳定性测试(Soak/Endurance)验证长时间运行稳定性以接近真实负载持续运行较长时间

稳定性测试(Soak/Endurance)

验证长时间运行稳定性

以接近真实负载持续运行较长时间

峰值测试(Spike Test)验证突增流量承受能力短时间快速升压,再快速回落,观察恢复情况

峰值测试(Spike Test)

验证突增流量承受能力

短时间快速升压,再快速回落,观察恢复情况

性能测试分类:压力水位怎么区分

这几类测试主要差在加压加到哪里:负载测试看目标流量能不能扛住;容量测试继续往上压,找拐点;压力测试越过预期负载,看系统怎么失败、怎么恢复。

基准测试

基准测试

基准测试强调环境、脚本、数据和参数固定,方便比较优化前后的变化。比如同一接口在同一台机器上,改索引前后分别跑一轮,看平均 RT、P99、吞吐量和 CPU 水位是否变化。

负载测试

负载测试

负载测试是在预期流量模型下验证系统是否达标。比如按照线上历史比例组合登录、查询、下单、支付等请求,逐步加压到目标 QPS,看响应时间、错误率和资源使用是否满足要求。

容量测试

容量测试

容量测试会继续加大请求压力,直到服务器的某个资源达到饱和,或者系统响应时间、错误率已经不满足要求。它更关注当前架构和资源配置下的承载上限。

压力测试

压力测试

压力测试会故意把流量推过设计水位。这个阶段可以接受快速失败,比如返回 429 或 503;不能接受请求一直挂着,连接不释放、线程池排队、队列越积越多,上游还在继续重试。测试时要看令牌桶、信号量、队列长度限制、熔断降级这些保护措施有没有真的生效;流量撤掉后,连接、线程、队列和吞吐量能不能回到正常水平。

稳定性测试

稳定性测试

稳定性测试主要看长时间趋势。系统刚启动时一切正常,跑几个小时后才暴露的问题,通常就藏在这里。可以用接近线上比例的流量跑几个小时、一天或更久,重点盯趋势:堆内存是否只涨不回落、Full GC 是否变密、连接池是否长期打满、线程数是否持续增长、MQ 或本地队列是否越积越多、错误率是否一点点抬头。7×24 小时只是高要求场景里的常见做法,不是固定答案。

峰值测试

峰值测试

峰值测试适合模拟活动开场、整点秒杀、消息推送这类突然涌进来的流量。压测时先把流量在很短时间内拉高,再降回正常水位,看限流、排队、降级有没有按预期触发,以及流量回落后 RT、错误率、线程池和队列能不能降下来。

全链路压测

全链路压测

单接口压测只能告诉你某个接口在当前脚本下能跑到多少吞吐。复杂系统出问题时,瓶颈经常不在入口接口本身:网关鉴权没问题,库存服务开始排队;订单接口还能返回,MQ 已经堆积;应用 CPU 不高,数据库连接池先被打满。一次下单可能经过网关、鉴权、商品、库存、订单、支付、消息队列、缓存和数据库,任何一个环节先到瓶颈,最终都会反映到用户请求上。

全链路压测:先把护栏搭好

做全链路压测时,流量从系统入口进来,沿着真实业务链路往后走,每个服务、组件和基础设施都会被带上压力。这种方式比单接口压测更接近线上流量,也更容易误伤真实数据和服务,所以先把几件事准备好:

压测数据隔离:使用压测账号、压测标识、影子表或影子库,避免污染真实订单、支付、库存等核心数据。

压测流量识别:让网关、服务、日志、监控、链路追踪都能识别压测流量,便于统计和快速止损。

链路监控完整:同时观察应用指标、机器指标、数据库指标、缓存指标、MQ 堆积、慢 SQL、GC 和线程池状态。

熔断和限流可用:压测前确认限流、降级、熔断、队列长度限制等保护机制已经生效。

压测结果可复盘:记录每一轮压测的并发数、QPS、P90/P99、错误率、资源水位和瓶颈点,方便和优化后的结果对比。

如果业务还没到需要线上全链路压测平台的阶段,先用 JMeter、Gatling、wrk 等工具把核心链路脚本跑清楚,能定位瓶颈、能保护数据、能快速停止,就已经能覆盖大部分容量评估和瓶颈定位场景。等业务复杂到需要频繁线上压测,再考虑专门的平台化建设。

常用性能测试工具

常用性能测试工具

后端常用

后端常用

既然系统设计涉及到系统性能方面的问题,那在面试的时候,面试官就很可能会问:你是如何进行性能测试的?

推荐几个比较常用的性能测试工具:

工具语言/脚本特点适用场景JMeterJava,GUI + CLI功能全面,GUI 适合编写和调试脚本,正式压测建议用 CLI企业复杂场景、接口链路压测GatlingJava、JavaScript、TypeScript、Scala、Kotlin代码驱动,报告和 CI 集成友好开发团队维护压测脚本、CI/CD 集成k6JavaScript 脚本,Go 引擎指标、阈值和 CI 集成友好API 压测、SLO 回归LocustPythonPython 编写用户行为,支持分布式扩展Python 团队、复杂用户行为建模wrkC,支持 Lua 脚本轻量高性能HTTP 接口高并发基准测试abC简单轻量,Apache 自带快速冒烟或粗略基准测试LoadRunner商业套件协议支持和管理能力强企业级大规模测试

工具语言/脚本特点适用场景

工具

语言/脚本

特点

适用场景

JMeterJava,GUI + CLI功能全面,GUI 适合编写和调试脚本,正式压测建议用 CLI企业复杂场景、接口链路压测

JMeter

Java,GUI + CLI

功能全面,GUI 适合编写和调试脚本,正式压测建议用 CLI

企业复杂场景、接口链路压测

GatlingJava、JavaScript、TypeScript、Scala、Kotlin代码驱动,报告和 CI 集成友好开发团队维护压测脚本、CI/CD 集成

Gatling

Java、JavaScript、TypeScript、Scala、Kotlin

代码驱动,报告和 CI 集成友好

开发团队维护压测脚本、CI/CD 集成

k6JavaScript 脚本,Go 引擎指标、阈值和 CI 集成友好API 压测、SLO 回归

k6

JavaScript 脚本,Go 引擎

指标、阈值和 CI 集成友好

API 压测、SLO 回归

LocustPythonPython 编写用户行为,支持分布式扩展Python 团队、复杂用户行为建模

Locust

Python

Python 编写用户行为,支持分布式扩展

Python 团队、复杂用户行为建模

wrkC,支持 Lua 脚本轻量高性能HTTP 接口高并发基准测试

wrk

C,支持 Lua 脚本

轻量高性能

HTTP 接口高并发基准测试

abC简单轻量,Apache 自带快速冒烟或粗略基准测试

ab

C

简单轻量,Apache 自带

快速冒烟或粗略基准测试

LoadRunner商业套件协议支持和管理能力强企业级大规模测试

LoadRunner

商业套件

协议支持和管理能力强

企业级大规模测试

没记错的话,除了 LoadRunner,上面几款都有开源或免费版本。

选型建议:

快速验证:使用 ab 或 wrk 进行简单的接口压测。

ab
wrk

复杂场景:使用 JMeter,支持录制脚本、参数化、断言等功能。

JMeter

代码驱动:使用 Gatling 或 k6,适合开发人员维护脚本并接入 CI。

Gatling
k6

复杂用户行为建模:使用 Locust,适合用 Python 描述用户行为。

Locust

前端常用

前端常用

工具主要用途Chrome DevTools Performance录制并分析页面运行时性能、长任务、JS 执行和渲染瓶颈Lighthouse自动审计页面性能、可访问性、最佳实践、SEO 等,并生成报告WebPageTest从真实浏览器、不同网络或地域视角测试页面加载体验Fiddler/Charles抓包、改包、调试 HTTP 请求

工具主要用途

工具

主要用途

Chrome DevTools Performance录制并分析页面运行时性能、长任务、JS 执行和渲染瓶颈

Chrome DevTools Performance

录制并分析页面运行时性能、长任务、JS 执行和渲染瓶颈

Lighthouse自动审计页面性能、可访问性、最佳实践、SEO 等,并生成报告

Lighthouse

自动审计页面性能、可访问性、最佳实践、SEO 等,并生成报告

WebPageTest从真实浏览器、不同网络或地域视角测试页面加载体验

WebPageTest

从真实浏览器、不同网络或地域视角测试页面加载体验

Fiddler/Charles抓包、改包、调试 HTTP 请求

Fiddler/Charles

抓包、改包、调试 HTTP 请求

常见的性能优化策略

常见的性能优化策略

优化前先把请求链路跑清楚:入口网关、应用线程池、缓存、数据库、MQ、外部接口,哪个地方先排队,就先看哪里。

下面是一些性能优化时,我经常拿来自问的一些问题:

优化方向检查项缓存系统是否需要缓存?热点数据是否已缓存?架构系统架构本身是不是就有问题?是否需要读写分离、分库分表?并发系统是否存在死锁的地方?锁的粒度是否合理?内存系统是否存在内存泄漏?GC 是否频繁?数据库数据库索引使用是否合理?是否存在慢 SQL?算法核心算法的时间复杂度是否可以优化?IO是否存在不必要的网络调用?是否可以批量操作?

优化方向检查项

优化方向

检查项

缓存系统是否需要缓存?热点数据是否已缓存?

缓存

系统是否需要缓存?热点数据是否已缓存?

架构系统架构本身是不是就有问题?是否需要读写分离、分库分表?

架构

系统架构本身是不是就有问题?是否需要读写分离、分库分表?

并发系统是否存在死锁的地方?锁的粒度是否合理?

并发

系统是否存在死锁的地方?锁的粒度是否合理?

内存系统是否存在内存泄漏?GC 是否频繁?

内存

系统是否存在内存泄漏?GC 是否频繁?

数据库数据库索引使用是否合理?是否存在慢 SQL?

数据库

数据库索引使用是否合理?是否存在慢 SQL?

算法核心算法的时间复杂度是否可以优化?

算法

核心算法的时间复杂度是否可以优化?

IO是否存在不必要的网络调用?是否可以批量操作?

IO

是否存在不必要的网络调用?是否可以批量操作?

写在最后

写在最后

如果内容对你有帮助的话,欢迎顺手给 JavaGuide 点一个免费的 Star 支持一下:GitHub | Gitee。

GitHub

Gitee

JavaGuide 已持续维护近七年,累计 6100+ 次提交,来自 620+ 位贡献者共同完善。你的 Star、反馈和 PR,都是这个项目继续更新的动力。

如果你正在准备后端/AI 应用开发面试,也可以了解一下我的知识星球,里面包括后端和 AI 实战项目、简历优化、一对一提问和高频考点资料,已经持续维护六年。

知识星球