目录
正在加载目录…
专栏文章
专栏文章
性能工程专栏
1. 高并发系统设计:架构演进与关键技术选型 2. 稳定性与容灾设计:限流、熔断、降级与隔离 3. 全链路压测:容量验证、流量构造与瓶颈定位 4. 秒杀系统设计:库存、防超卖与高并发治理 5. 性能优化方法论:指标分析与分层调优策略 6. 性能测试体系:压测方法、指标与瓶颈定位 7. 容量规划与弹性:水位线、扩缩容与大促保障

性能测试体系:压测方法、指标与瓶颈定位

发布于 2026-08-19 15:02 · 最后编辑于 2026-08-19 15:04 · 字数 3,333 👁 31 次阅读

本文系统梳理性能测试的完整体系:从测试类型(基准/负载/压力/稳定性/浸泡)的本质区别,到测试流程、工具选型(JMeter/Gatling/Locust/wrk 对比)、关键指标(TPS/RT/P99/P999)、瓶颈定位四步法,以及常见性能陷阱。综合了《性能测试实战 30 讲》(高楼)和《高并发架构实战课》(李智慧)的核心内容,强调"没有证据链的分析就是耍流氓"。

目录

章节说明
性能测试类型五种类型的本质区别与适用场景
业务指标 vs 技术指标两层指标的映射关系
核心性能指标详解TPS/RT/P99/P999/并发数的精确定义
性能测试流程六步完整流程
测试工具选型JMeter/Gatling/Locust/wrk/ab 对比
监控体系设计全栈监控计数器图谱
性能瓶颈定位四步法宏观→微观→代码→系统
常见性能陷阱连接池耗尽/GC 停顿/线程死锁/内存泄漏
性能报告关键要素如何写一份有价值的性能报告

性能测试类型

五种类型的本质区别

graph LR
    A["并发数/压力"] -->|"逐步增加"| B["性能测试<br/>验证是否达标"]
    B -->|"继续加压"| C["负载测试<br/>找安全临界值"]
    C -->|"超过临界值"| D["压力测试<br/>找崩溃点"]
    E["固定中等压力"] -->|"持续 N 小时"| F["稳定性测试<br/>发现长时间问题"]
    G["低压力"] -->|"持续数天/周"| H["浸泡测试<br/>发现内存泄漏等"]
    style D fill:#fcc,stroke:#c00
    style B fill:#cfc,stroke:#060
类型目的压力特征通过标准
基准测试(Benchmark)建立性能基线,对比优化效果单接口,低并发,可重复结果稳定,偏差 < 5%
性能测试(Performance)验证系统是否达到设计目标逐步加压到目标 TPSRT 和错误率在目标范围内
负载测试(Load)找到系统安全临界点持续加压直到某指标告警明确最大安全负载
压力测试(Stress)找到系统崩溃点,验证恢复能力超过安全负载继续加压了解极限,验证崩溃后恢复
稳定性测试(Soak/Endurance)发现长时间运行才暴露的问题70-80% 最大负载,持续 1-8 小时无内存泄漏,无性能衰退

**浸泡测试(Soak Test)**是稳定性测试的极端形式,持续数天甚至数周,专门用来发现内存泄漏、连接泄漏、日志文件无限增长等"慢性病"。

performance test types

三个阶段的性能特征

随着并发数增加,系统经历三个阶段:

阶段一(性能测试区间):
  并发数↑ → TPS↑,RT 变化不大
  → 系统在正常工作,资源还有余量

阶段二(负载测试区间):
  并发数↑ → TPS 增加很少甚至下降,RT 明显增加
  → 系统接近瓶颈,资源争用加剧

阶段三(压力测试区间):
  并发数↑ → TPS 迅速下降,RT 急剧增加
  → 系统超载,错误率上升,直到崩溃

业务指标 vs 技术指标

核心原则:技术指标不能脱离业务指标,两者必须有明确的映射关系。

业务指标(运营视角):
  "支持 1000 万用户同时在线"
  "双十一峰值 100 万笔/分钟下单"
  
    ↓ 换算
    
技术指标(工程视角):
  "接口 P99 < 200ms"
  "TPS ≥ 16667(100万/60秒)"
  "错误率 < 0.1%"
  "CPU 使用率 < 70%"

换算示例

业务目标:支持 100 万 DAU,高峰期集中在 2 小时
日均请求量 = 100万 × 50次/人 = 5000万次
高峰期 QPS = 5000万 / (2 × 3600) ≈ 7000 QPS
设计目标 QPS = 7000 × 3(峰值倍数) × 1.5(安全余量) ≈ 30000 QPS

如果只能回答"系统能支持多少 TPS",却无法回答"能否支持 1000 万在线用户",说明技术指标与业务指标脱节了。

核心性能指标详解

TPS、QPS、RPS 的区别

指标全称含义易混淆点
TPSTransactions Per Second每秒完成的事务数(含请求+响应+业务处理)最准确的业务吞吐量指标
QPSQueries Per Second原指 MySQL 每秒查询数,被借用表示 HTTP 请求数与 TPS 概念有重叠,需看上下文
RPSRequests Per Second每秒 HTTP 请求数,不考虑业务完整性可能一个 TPS 对应多个 RPS
HPSHits Per Second每秒点击数(压测工具层面)最底层,一次用户操作可能产生多个 Hit

关系HPS ≥ RPS ≥ TPS(从压测工具视角到业务视角,数量逐渐减少)

响应时间的正确度量

为什么用 P99 而不用平均值

10 次请求的响应时间(ms):
  [10, 10, 10, 10, 10, 10, 10, 10, 10, 1000]

平均值 = 109ms(看起来很差)
P50(中位数) = 10ms
P90 = 10ms
P99 = 1000ms(1% 的用户体验极差)
P999 = 1000ms
百分位含义适用场景
P50(中位数)50% 用户的响应时间了解典型体验
P9090% 用户的响应时间一般性能目标
P9999% 用户的响应时间用户体验质量目标
P99999.9% 用户的响应时间关键业务、金融场景

推荐目标值

  • 普通 API:P99 < 200ms
  • 核心交易接口:P99 < 100ms
  • 数据库查询:P99 < 10ms
  • 缓存读取:P99 < 5ms

并发数与 TPS 的关系

Little's Law(利特尔定律):
  并发数 = TPS × 平均响应时间(秒)

示例:
  TPS = 1000,平均 RT = 100ms = 0.1s
  → 并发数 = 1000 × 0.1 = 100 个并发请求在处理中

实践含义:线程池大小应该与并发数匹配,而非与 TPS 匹配。

性能测试流程

flowchart TD
    A["1. 制定性能目标<br/>(SLO 定义)"] --> B["2. 准备测试环境<br/>(数据/监控/工具)"]
    B --> C["3. 设计测试场景<br/>(业务模型/参数化数据)"]
    C --> D["4. 执行测试<br/>(基准→负载→压力→稳定性)"]
    D --> E["5. 分析结果<br/>(七步法/证据链)"]
    E --> F{"达到目标?"}
    F -->|否| G["6. 优化<br/>(代码/配置/架构)"]
    G --> D
    F -->|是| H["输出性能报告"]
    style A fill:#e3f2fd
    style H fill:#cfc,stroke:#060

第一步:制定性能目标(SLO)

SLO(Service Level Objective)应该包含:

  • 吞吐量目标:峰值 TPS 是多少?
  • 延迟目标:P99 RT 不超过多少?
  • 错误率目标:错误率不超过多少?
  • 资源目标:CPU/内存使用率上限是多少?

第二步:准备测试环境

环境准备清单

  • [ ] 铺底数据(模拟生产级数据量,影响缓存命中率和索引效率)
  • [ ] 参数化数据文件(压测账号、商品 ID 等,避免数据热点失真)
  • [ ] 监控工具就位(Prometheus + Grafana,或云厂商监控)
  • [ ] 日志收集(ELK 或等价方案)
  • [ ] 链路追踪(SkyWalking / Jaeger)

铺底数据的重要性

反例:用 100 条测试数据压测
→ 所有数据都在 Buffer Pool 中,缓存命中率 100%
→ 测出来的 TPS 比生产高 5-10 倍,完全失真

正例:用生产级数据量(如 1000 万条)铺底
→ 缓存命中率与生产接近
→ 测试结果可信

第三步:设计测试场景

业务模型建立

从生产环境监控中获取各接口的真实 QPS 比例
示例(电商):
  浏览首页 : 搜索 : 商品详情 : 加购 : 下单 : 支付
    100   :  80  :    60   :  20  :  10  :  8
压测时按此比例分配并发数,而非每个接口独立压测

参数化数据设计

  • 用户 ID 列表(避免单个用户被限流)
  • 商品 ID 列表(避免单商品热点,使数据分布接近真实)
  • 地址数据、优惠券数据等

测试工具选型

工具语言协议支持学习曲线适用场景
JMeterJavaHTTP/HTTPS/JDBC/JMS/TCP 等中(GUI)功能最全,企业级首选
GatlingScala DSLHTTP/WebSocket中(代码)高并发,报告美观
LocustPythonHTTP(可扩展)低(Python)快速原型,分布式
wrkCHTTP低(命令行)极简基准测试,最大性能
ab (ApacheBench)CHTTP极低快速验证,不适合复杂场景
k6JavaScriptHTTP/WebSocket/gRPC低(JS)现代化,CI/CD 集成友好

JMeter 核心用法

JMeter 测试计划结构:
测试计划
  └── 线程组(模拟并发用户)
        ├── HTTP 请求默认值(公共配置)
        ├── CSV 数据文件(参数化)
        ├── HTTP 请求(业务接口)
        │     ├── 断言(验证响应正确性)
        │     └── 提取器(关联,提取 token 等)
        └── 监听器(聚合报告/后端监听器)

JMeter + InfluxDB + Grafana 监控架构

JMeter 压测 → 实时写入 InfluxDB → Grafana 可视化
                                    ↑
                              实时查看 TPS/RT/错误率趋势

工具选型建议

flowchart TD
    A["需要什么?"] --> B{"复杂业务场景<br/>(登录/关联/断言)?"}
    B -->|是| C["JMeter 或 Gatling"]
    B -->|否| D{"需要分布式<br/>大规模压测?"}
    D -->|是| E["Gatling / Locust / 自研"]
    D -->|否| F{"快速验证接口性能?"}
    F -->|是| G["wrk 或 ab"]
    F -->|否| H["k6(CI/CD 集成)"]

监控体系设计

全栈监控计数器图谱

性能测试时必须建立覆盖全技术栈的监控,确保瓶颈无处可藏:

业务层
  ├── 接口成功率(业务错误码统计)
  ├── 下单成功率、支付成功率等业务指标
  └── 消息队列积压量

应用层
  ├── JVM 堆内存使用率(Eden/Old/Metaspace)
  ├── GC 频率和停顿时间(Young GC / Full GC)
  ├── 线程池状态(活跃线程数/队列积压/拒绝数)
  └── 数据库连接池使用率

中间件层
  ├── 数据库:慢查询数、连接数、锁等待
  ├── Redis:命中率、内存使用率、延迟
  ├── MQ:消费延迟、积压量、消费速率
  └── HTTP/RPC:上下游调用成功率、RT

OS 层
  ├── CPU 使用率(user/sys/iowait 分项)
  ├── 内存使用率(used/cached/swap)
  ├── 磁盘 IO(util/await/iops)
  └── 网络(带宽利用率/包重传率)

Prometheus + Grafana 监控架构

应用 → Micrometer → Prometheus(Pull 拉取)→ Grafana(可视化)
OS   → node_exporter → Prometheus
JVM  → jmx_exporter → Prometheus
MySQL → mysqld_exporter → Prometheus
Redis → redis_exporter → Prometheus

关键 Grafana 面板

  1. TPS/QPS 趋势图:观察压力增加时吞吐量变化
  2. RT 分位数图:P50/P90/P99 的变化趋势
  3. 错误率图:超过 1% 立即告警
  4. 资源使用率热力图:CPU/内存/IO 使用情况
  5. JVM GC 图:GC 频率和停顿时间

性能瓶颈定位四步法

宏观层:整体系统视角

第一步:确认问题现象

现象可能层次下一步
TPS 上不去,CPU 低(< 30%)IO 阻塞 / 数据库瓶颈查连接池、慢查询
TPS 上不去,CPU 高(> 80%)计算密集 / 锁竞争查线程堆栈、火焰图
TPS 正常,RT 飙高某依赖服务超时查链路追踪,找慢服务
错误率升高资源耗尽 / 下游故障查应用日志、下游状态
内存持续增长内存泄漏JVM Heap Dump 分析

微观层:定向组件分析

第二步:定向到具体组件

数据库定向

-- 查看当前慢查询
SHOW PROCESSLIST;

-- 查看锁等待
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 超过 1s 记录

JVM 定向

# 查看 GC 情况
jstat -gcutil <pid> 1000  # 每秒输出一次 GC 统计

# 查看线程状态分布
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c

# 生成 Heap Dump(内存泄漏分析)
jmap -dump:format=b,file=/tmp/heap.hprof <pid>

代码层:热点代码分析

第三步:找到热点代码

# async-profiler 生成火焰图(最推荐)
./profiler.sh -e cpu -d 30 -f flamegraph.html <pid>

# Arthas 在线诊断(无需重启)
# 查看方法调用耗时
trace com.example.OrderService createOrder -n 5

# 监控方法调用统计
monitor com.example.OrderService createOrder -c 5

火焰图解读

  • 横轴:CPU 时间占比(越宽越耗时)
  • 纵轴:调用栈(越高越深)
  • 关注最宽的平顶:这是最耗 CPU 的代码

系统层:OS 资源分析

第四步:OS 层资源分析

# CPU 使用情况(分项)
top -H                    # 线程级 CPU
vmstat 1                  # 整体资源
mpstat -P ALL 1           # 每个核的使用率

# IO 等待分析
iostat -x 1               # 磁盘 IO 利用率
iotop -o                  # 进程级 IO 使用

# 网络分析
ss -s                     # TCP 连接统计
netstat -an | grep TIME_WAIT | wc -l  # TIME_WAIT 数量

常见性能陷阱

1. 连接池耗尽

现象:TPS 上不去,CPU 很低,应用日志出现"Cannot get a connection, pool error Timeout"

根因:连接池大小不足,或下游数据库响应慢导致连接被长时间占用

排查

// HikariCP 监控连接池状态
HikariDataSource ds = (HikariDataSource) dataSource;
HikariPoolMXBean poolMXBean = ds.getHikariPoolMXBean();
int active = poolMXBean.getActiveConnections();   // 活跃连接数
int idle = poolMXBean.getIdleConnections();        // 空闲连接数
int waiting = poolMXBean.getThreadsAwaitingConnection();  // 等待获取连接的线程数
// 如果 waiting > 0,说明连接池已满

修复:适当增大连接池(注意不要超过数据库 max_connections),同时优化慢 SQL

2. GC 停顿导致 RT 波动

现象:P99 RT 异常高,但 P50 正常;监控中 RT 周期性飙高

根因:Full GC 或 G1 GC 的 Mixed GC 停顿时间过长

排查

# 查看 GC 日志
grep -E "GC pause|Full GC" gc.log | tail -50

# 关键指标:
# Young GC 停顿 > 100ms → 需要调优 Eden 大小
# Full GC 出现 → 严重问题,需要查内存泄漏或堆大小配置

修复

  • 增大堆内存(-Xmx
  • 切换到 G1 GC 并设置停顿目标(-XX:MaxGCPauseMillis=100
  • 排查内存泄漏(大对象、缓存无限增长)

3. 线程死锁

现象:服务完全不响应,CPU 为 0,线程数正常

排查

# 检测死锁
jstack <pid> | grep -A 20 "deadlock"

# 或使用 jconsole / VisualVM 图形界面查看

预防

  • 加锁顺序一致(总是按相同顺序获取多个锁)
  • 使用 tryLock 带超时,避免无限等待
  • 使用 ReentrantLock 替代 synchronized,支持中断

4. 内存泄漏

现象:内存使用率持续增长,Full GC 越来越频繁,最终 OOM

常见原因

1. 静态集合无限增长(static Map/List 只加不删)
2. 缓存没有设置 TTL 或大小上限
3. ThreadLocal 没有 remove(线程池场景)
4. 监听器/回调注册后没有注销
5. 数据库连接/IO 流没有关闭

排查

# 生成 Heap Dump
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>

# 用 MAT(Memory Analyzer Tool)分析
# 关注:Dominator Tree(哪些对象占用最多内存)
# 关注:Leak Suspects(MAT 自动识别的泄漏嫌疑)

5. 数据库连接泄漏

现象:系统运行一段时间后 DB 连接数持续增长,最终超过 max_connections

常见原因

// 反例:异常路径没有关闭连接
Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
// 如果这里抛异常,conn 永远不会关闭!
conn.close();

// 正例:try-with-resources 自动关闭
try (Connection conn = dataSource.getConnection();
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery(sql)) {
    // 使用 rs
}  // 自动关闭

性能报告关键要素

一份有价值的性能报告应该包含:

1. 测试背景与目标

- 测试目的:本次压测的业务背景(大促前验证?新功能上线?)
- 性能目标:SLO 定义(TPS ≥ X,P99 < Yms,错误率 < Z%)
- 测试范围:哪些接口/链路被测试

2. 测试环境说明

- 服务器配置:CPU 核数、内存大小、磁盘类型
- 部署方式:单机/集群,几个实例
- 数据规模:铺底数据量
- 与生产环境的差异说明

3. 测试结果摘要

场景目标 TPS实测 TPSP99 RT错误率结论
商品详情查询5000520045ms0%通过
用户下单1000980180ms0.1%接近临界
支付50051095ms0%通过

4. 性能瓶颈分析(证据链)

现象:下单接口在 TPS > 800 时 P99 RT 开始超过 200ms
证据:
  1. 数据库慢查询日志:order 表 INSERT 操作平均耗时 15ms → 80ms
  2. 数据库监控:lock_waits 从 0 升至 50/s
  3. EXPLAIN:order 表缺少 (user_id, status) 联合索引,全表扫描
结论:order 表锁竞争和全表扫描是瓶颈

5. 优化建议与后续计划

优先级 P0(必须修复):
  - 添加 order 表 (user_id, status) 联合索引
  - 预计效果:P99 RT 降至 100ms 以内

优先级 P1(建议修复):
  - 下单接口增加本地缓存,减少 DB 查询
  - 预计效果:TPS 提升 30%

参考资料

← 返回列表

评论 (0)

暂无评论,来留下第一条吧。
登录注册 后才能发表评论