前置知识: Python

性能测试方法

8 min入门

从一次大促事故学性能测试:用 k6 完成第一次负载测试,读懂 P95 与吞吐量,理清负载、压力、稳定性测试的目标与工具选择。

前置知识:知道 HTTP 接口的请求/响应长什么样;装好 Node.js 或任一运行时 (k6 是独立的单个二进制,装完即用)。不需要先学 JMeter——本文用更轻的 k6 做第一次动手,JMeter 与 LoadRunner 的定位在工具地图一节讲清。

读完本文你应当能够:区分负载/压力/稳定性/尖峰四类测试目标;给一个本地 服务跑第一次负载测试并读懂分位延迟;说清”并发用户数”为什么不是”在线 人数”。接口「响应对不对」的功能那一半见《接口测试方法》。

1. 场景:大促开始十分钟,下单接口雪崩

复盘一次典型事故:营销活动十点开始,流量是平时的五倍,十点十分下单 接口超时告警,用户看到的是转圈和报错,订单量归零。事后复盘发现两个 事实:

  1. 这个接口从来没被压测过。团队对”它能扛多少”一无所知,容量规划 靠拍脑袋。
  2. 单接口功能测试全绿。每个接口单发请求都正常——直到一千个人同时 发。性能问题不是功能 bug,是”数量上去之后才暴露”的另一类 bug。

性能测试的全部意义就一句话:在用户替你发现容量上限之前,先在受控 环境里自己找到它。

2. 先分清四类测试,别上来就压

“压测”是口语,专业上先分清目标,因为不同目标的加压方式完全不同:

类型目标加压方式典型问题
负载测试预期负载下表现是否达标固定在预期并发日常高峰顶得住吗
压力测试找到系统的极限与崩溃形态阶梯递增直到崩上限多少,怎么崩的
稳定性测试长时间运行是否衰减中等负载跑数小时起有没有内存泄漏
尖峰测试突发流量冲击后的恢复瞬时拉高再回落大促瞬时流量扛得住吗

新手最常见错误:想做负载测试,实际跑了压力测试(一直加压到崩), 拿到一个”极限 TPS”却回答不了”日常高峰的响应时间达标吗”。

3. 关键指标:并发用户数不等于在线人数

指标说明
响应时间看 P95/P99 分位,不看平均值——平均 200ms 可能掩盖着 P99 3s 的长尾
吞吐量单位时间处理的请求数(TPS/QPS)
并发用户数同一时刻正在发请求的用户数。不等于在线数:在线用户大部分时间在浏览,只有一小部分在发请求
错误率失败请求占比
资源占用服务端 CPU、内存——判断瓶颈在哪台机器

“并发 1000”到底怎么估?典型换算:在线 10 万人,其中同时发请求的占 1% 到 5%,即 1000 到 5000 并发。拍脑袋拍的是这个百分比,而它应该来自 对生产访问日志的统计,不是经验直觉。

4. 动手:用 k6 跑第一次负载测试

4.1 准备一个被测服务

任何本地 HTTP 服务都行。最快的办法是起一个公开的练习靶站(如 https://test.k6.io),或本地 docker run 一个 nginx。第一次练习 不要压公司的真实环境——压测本质是制造故障,打到生产等于事故。

4.2 写第一个场景

k6(Grafana 开源)用 JavaScript 写场景,安装后一个文件就能跑:

javascript
// loadtest.js:模拟 20 个虚拟用户持续访问 30 秒
import http from 'k6/http';
import { check } from 'k6';

export const options = {
  vus: 20,              // 虚拟用户数(并发)
  duration: '30s',
  thresholds: {         // 及格线:不达标则命令退出码非 0,可直接进 CI
    http_req_failed: ['rate<0.01'],                       // 错误率 < 1%
    http_req_duration: ['p(95)<500'],                     // P95 < 500ms
  },
};

export default function () {
  const res = http.get('https://test.k6.io/');
  check(res, {
    '状态码 200': (r) => r.status === 200,
    '响应包含标题': (r) => r.body.includes('test.k6.io'),
  });
}
bash
k6 run loadtest.js

4.3 读懂输出

跑完终端会给出各指标的统计,重点三行:

plaintext
http_req_duration..............: avg=180ms min=95ms med=170ms max=890ms p(90)=310ms p(95)=402ms
http_req_failed................: 0.00%
iterations.....................: 3241   107.9/s
  • p(95)=402ms:95% 的请求在 402ms 内完成——这才是”用户体感”的口径。
  • http_req_failed:失败率 0%,check 的断言失败也算在内。
  • iterations 后面的 107.9/s 就是实测吞吐量。

把 vus 从 20 逐步调到 50、100、200,记录每个档位的 P95 和吞吐量: 吞吐量还在涨,说明没到瓶颈;吞吐量不涨而延迟暴涨,说明到了。 这条曲线就是容量规划的原始材料。阶梯加压找拐点的完整方法论见 《压力测试与稳定性测试》。

4.4 目标值从哪来

上面的 p(95)<500 是编的。真实项目的及格线来自三处,优先级从高到低:

  1. 业务 SLO:产品/运维约定的服务目标(与《监控与可观测性》里的 SLO 是同一套数,压测验收线应当与线上 SLO 一致)。
  2. 生产实测基线:从监控里取当前 P95,留出 30% 到 50% 余量。
  3. 通用经验参考线(响应时间 P95 < 500ms、错误率 < 0.1%、CPU < 70%) ——只在什么数据都没有时用于起步。

5. 工具地图:k6、JMeter、LoadRunner

三款工具覆盖了从开源到商业的光谱,选型看团队栈和预算:

工具形态强项适合
k6JS 脚本、单二进制阈值即代码、CI 集成顺滑API 压测、流水线内
JMeterGUI 编排、Java协议广、生态老牌复杂场景、已有积累
LoadRunner商业套件协议最全、企业级分析与报告大型企业、特殊协议

本模块《JMeter》专讲 JMeter 的线程组、采样器与分布式压测,这里只给 LoadRunner 留一个概念锚点——它的三个核心词汇在任何工具里都有对应物:

  • 事务(transaction):把一组请求包成一个计时单元,对应 k6 的 group 或 JMeter 的事务控制器;
  • 思考时间(think time):模拟用户操作间的停顿,不加它测出的是 “机器人极限”而非真实负载;
  • 参数化(parameterization):每个虚拟用户用不同账号/数据,避免 缓存让结果失真。LoadRunner 脚本用 C 写,事务与思考时间长这样:
c
Action()
{
    lr_start_transaction("login");              // 事务开始计时

    web_submit_data("login",
        "Action=https://api.example.com/login",
        "Method=POST", ITEMDATA,
        "Name=username", "Value={username}", ENDITEM,   // {username} 是参数化
        "Name=password", "Value={password}", ENDITEM,
        LAST);

    lr_think_time(3);                           // 模拟用户填表单的 3 秒
    lr_end_transaction("login", LR_PASS);       // 事务结束
    return 0;
}

6. 坑点与自检

  1. 用 GUI 模式施压。JMeter 图形界面自身扛不住上千并发,数据失真。 施压一律命令行非 GUI 模式(jmeter -n -t plan.jmx -l result.jtl), GUI 只用来编脚本。
  2. 压测环境与生产差异过大。单机小内存容器压出的”极限”毫无参考 价值。环境配置(机器规格、数据库数据量、网络)应尽量对齐生产, 至少按比例缩放并记录差异。
  3. 忘了思考时间与参数化。所有虚拟用户拿同一账号疯狂连发,测出的 是缓存与锁的极限,不是用户负载。参数化 + 思考时间是默认项。
  4. 只压单接口。真实流量是接口组合与数据关联(登录、浏览、下单、 支付)。单接口达标不等于整链路达标,核心路径要按比例混合压。
  5. 压测没有监控。只看压测工具的响应时间,不看服务端 CPU/内存/ 连接数,崩了也不知道瓶颈在哪一层。压测时服务端监控必须同时开着。
  6. 对生产环境直接压。没有隔离环境、没有限流保护、没有干路预案 就对生产施压,等于自己制造事故。

自检清单:

  • 能说出当前项目四类性能测试分别的目标与加压方式
  • 响应时间口径是 P95/P99,且及格线能追溯到 SLO 或生产基线
  • 并发数估算来自访问日志统计而非拍脑袋
  • 压测脚本有参数化与思考时间,施压用非 GUI 模式

7. 练习

  1. 对 https://test.k6.io 依次用 vus=1/10/50/100 各跑 30 秒,记录 P95 与吞吐量,画出”并发 - 吞吐量”关系,标出吞吐量不再增长的拐点。
  2. 给第 1 题的脚本加 thresholds,把 P95 及格线设为你实测基线的 1.3 倍,然后故意把 vus 加到远超拐点,观察阈值失败时的退出码。
  3. 用访问日志(或现成统计)估算你所在项目高峰期的真实并发数,对照 当前压测覆盖的并发档位,评估缺口。

8. 下一步

  • JMeter 线程组、采样器、分布式施压:见《JMeter》。
  • 阶梯加压找拐点、内存泄漏观察、稳定性方法论:见《压力测试与稳定性测试》。
  • 接口功能测试的三层断言与异常语义:见《接口测试方法》。
  • 接口自动化进 CI 与契约测试:见《API 自动化测试》《API 测试工具链与契约测试》。
  • 压测指标与线上监控对齐:见《监控与可观测性》(devops 模块)。