性能测试培训 · 通用篇
流程方法 + 实战技巧 · 适用于任何产品的完整8步性能测试流程
步骤1:需求分析 —— "到底要扛多少量?"
💡 这是整个性能测试最重要的一步
超过 60% 的"无效压测"都是因为跳过了需求分析。没搞清楚目标就开始压,测了个寂寞。
1.1 为什么需求分析如此重要?
性能测试不是"压到死"就完事的。你需要先回答三个核心问题:
- 系统要服务多少用户? —— 日活、月活、注册用户数
- 高峰期是什么时候? —— 早高峰、午高峰、促销活动
- 用户会做什么操作? —— 浏览为主还是交易为主
1.2 数据来源
| 数据来源 | 获取方法 | 适用阶段 |
|---|---|---|
| 生产日志 | 分析 Nginx/应用 access log 的 QPS 峰值 | 已上线系统 |
| Google Analytics / 埋点 | 查看用户行为路径、停留时长 | 已上线系统 |
| 运营预估 | 向运营团队要推广计划、预期用户增长 | 新系统上线前 |
| 竞品对标 | 参考同类产品的公开数据 | 新系统上线前 |
| 业务方口述 | 直接问产品/运营"上线后预估多少人用" | 通用 |
1.3 峰值 QPS 计算方法
📐 二八法则计算公式
峰值 QPS = (总请求数 × 80%) / (总时长 × 20%) × 峰值系数(3~5倍) 解读:80% 的请求集中在 20% 的时间段内,再乘以峰值系数留余量。
示例
电商场景 —— 双十一大促
已知条件:
→ 日活用户 50 万
→ 平均每人每天 20 次 API 请求
→ 活动期间流量预估翻 3 倍
计算过程:
→ 日总请求 = 500,000 × 20 × 3 = 30,000,000(3000万)
→ 80% 集中在 20% 时间 = 24,000,000 / (24h × 20% = 4.8h = 17,280s)
→ 平均 QPS = 24,000,000 / 17,280 ≈ 1,389
→ 峰值 QPS(×3 系数)≈ 4,167
→ 保守目标:5,000 QPS
→ 同时在线并发用户 ≈ 5,000 ~ 10,000示例
社交 App 场景 —— 新版本发布
已知条件:
→ 注册用户 200 万,日活率 30% = 60 万日活
→ 新版本发布当天推送通知,预计 40% 打开 = 24 万集中访问
→ 推送后 1 小时内是高峰
→ 每人平均 30 次请求(浏览信息流、发消息)
计算过程:
→ 高峰期总请求 = 240,000 × 30 = 7,200,000
→ 集中在 1 小时 = 7,200,000 / 3,600s = 2,000 QPS
→ 峰值(×3)= 6,000 QPS
→ 目标:系统要扛住 6,000+ QPS示例
金融交易场景 —— 股市开盘
已知条件:
→ 活跃交易用户 10 万
→ 9:30 开盘瞬间,80% 用户同时刷新行情
→ 每人每秒 2~3 次请求(行情查询 + 下单)
计算过程:
→ 峰值并发 = 100,000 × 80% = 80,000 人
→ 峰值 QPS = 80,000 × 2.5 = 200,000 QPS
→ 极端场景:需要水平扩展 + CDN + 本地缓存1.4 常见错误
⚠️ 需求分析阶段的典型错误
- 拍脑袋:"我们就压 1000 并发吧" —— 没有数据支撑
- **混淆概念:**把"注册用户数"当"并发用户数" —— 100 万注册用户 ≠ 100 万并发
- **忽略增长:**只按当前流量测,没有预留 3~6 个月增长空间
- **忽略突发:**只算平均值,没考虑秒杀/推送等瞬时脉冲
- 照搬别人:"XX 公司压了 10 万并发" —— 业务场景不同
1.5 需求分析产出物
完成需求分析后,你应该输出一份性能测试需求文档,包含:
| 项目 | 内容 |
|---|---|
| 目标 QPS | 如:系统需稳定支撑 5,000 QPS |
| 目标并发数 | 如:支撑 2,000 同时在线用户 |
| 响应时间目标 | 如:P95 < 500ms,P99 < 1s |
| 错误率目标 | 如:< 0.1% |
| 稳定性要求 | 如:连续运行 8 小时无性能衰减 |
| 数据来源 | 说明以上数据的推算依据 |
课堂练习
练习:为你当前负责的项目做一次需求分析
- 找到你项目的日活用户数(没有的话估算)
- 用二八法则计算峰值 QPS
- 确定响应时间和错误率的目标值
- 写出完整的性能需求表格 提示:如果完全没有数据,先用竞品分析或运营预估。重要的是有数据支撑,而不是拍脑袋。
步骤2:识别核心场景 —— "测哪些接口?"
资源有限,不可能所有接口都测。二八法则再次生效:20% 的接口承载了 80% 的流量。
2.1 如何找到核心场景
📖 场景识别的三种方法
- **日志分析法:**从 access log 统计各接口调用量 Top 20
- **用户旅程法:**画出核心用户路径(注册→登录→核心操作→退出)
- **业务优先级法:**和产品经理确认哪些功能不能挂
2.2 场景建模 —— 按真实比例分配流量
示例
电商平台场景建模
场景 1(50% 流量):商品浏览
→ GET /api/home-feed 首页推荐
→ GET /api/products?page=1 商品列表
→ GET /api/product/:id 商品详情
场景 2(20% 流量):搜索
→ GET /api/search?q=xxx 搜索
→ GET /api/search/suggest 搜索联想
场景 3(15% 流量):用户相关
→ POST /api/login 登录
→ GET /api/user/profile 个人中心
→ PUT /api/user/address 修改地址
场景 4(10% 流量):交易核心
→ POST /api/cart/add 加购物车
→ POST /api/order/create 创建订单
→ POST /api/payment/pay 支付
场景 5(5% 流量):其他
→ GET /api/notifications 消息通知
→ POST /api/feedback 用户反馈示例
社交 App 场景建模
场景 1(40% 流量):信息流
→ GET /api/feed 刷信息流
→ GET /api/post/:id 查看帖子详情
→ POST /api/post/:id/like 点赞
场景 2(25% 流量):即时通讯
→ WebSocket /ws/chat 聊天长连接
→ POST /api/message/send 发消息
→ GET /api/conversations 会话列表
场景 3(20% 流量):用户互动
→ GET /api/user/:id 查看他人主页
→ POST /api/follow 关注
→ GET /api/comments 查看评论
场景 4(15% 流量):内容发布
→ POST /api/post/create 发帖
→ POST /api/upload 上传图片/视频2.3 常见错误
⚠️ 场景建模的常见坑
- **只测首页:**首页有 CDN 缓存,性能很好不说明什么
- **只测 GET:**写操作(POST/PUT/DELETE)才是真正考验数据库的
- **所有接口平均分配:**真实流量不是均匀分布的
- **忽略登录态:**未登录请求有缓存,登录后每次都要鉴权
- **忽略关联性:**查看购物车和下单是有先后关系的
课堂练习
练习:为一个电商网站设计测试场景
- 列出该网站最核心的 10 个 API
- 按照你认为的真实流量比例分配权重
- 画出至少两条完整的"用户旅程"(如:浏览→加购→下单→支付)
- 标注哪些接口是读操作、哪些是写操作
步骤3:环境准备 —— "在哪里测?"
3.1 测试环境要求
| 要素 | 要求 | 为什么重要 |
|---|---|---|
| 服务器配置 | 和生产环境尽量一致 | 4核8G 测出来的结果不能代表 16核32G 的表现 |
| 数据库 | 同版本、同配置、接近真实数据量 | 100 条数据测索引是没有意义的 |
| 中间件 | Redis/MQ/Nginx 版本和配置一致 | Redis 内存不同、连接数配置不同都会影响结果 |
| 网络 | 压测机和被测服务同机房/同网段 | 跨机房的网络延迟会成为瓶颈假象 |
| 测试数据 | 预埋足量数据(账号、业务数据) | 所有人用同一个账号 → 缓存命中 100% → 失真 |
| 监控 | 装好 Prometheus + Grafana 或同类监控 | 压测时必须能实时看到服务器指标 |
3.2 环境隔离
⚠️ 三条铁律
- 绝对不要在生产环境压测 —— 会直接影响真实用户
- 压测环境不要和开发/测试环境共享数据库 —— 压测会把库压崩
- 压测前通知相关方 —— 运维、DBA、网络团队都要知道
3.3 压测机准备
发起压力的机器本身也可能成为瓶颈:
| 检查项 | 说明 |
|---|---|
| CPU | 压测机 CPU 使用率不应超过 70%,否则瓶颈在压测机 |
| 内存 | JMeter 等 Java 工具吃内存很厉害,至少 4G+ |
| 网络 | 千兆网卡跑满 ≈ 120MB/s,如果响应体大需注意 |
| 文件描述符 | Linux 默认 1024,高并发需要调到 65535+ |
| 端口范围 | 默认可用端口约 28,000 个,大量短连接可能耗尽 |
# Linux 系统调优(在压测机上执行)
# 查看当前限制
ulimit -n
# 临时调大文件描述符
ulimit -n 65535
# 调大端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# 允许 TIME_WAIT 状态的连接复用
sysctl -w net.ipv4.tcp_tw_reuse=1课堂练习
练习:搭建一个最小压测环境
- 准备一台被测机(可以用本地 Docker 跑个 Nginx + 简单 API)
- 准备一台压测机(可以是你的开发机)
- 检查两台机器的网络连通性(
ping、curl) - 在被测机上安装基础监控(
top、htop先用起来) - 确认文件描述符等系统参数已调优
步骤4:工具选型 —— "用什么工具?"
工具选型没有"最好的",只有"最适合的"。选型取决于你的团队技术栈、测试场景、集成需求。
4.1 主流工具对比
| 维度 | JMeter | k6 | Locust | wrk/hey/ab |
|---|---|---|---|---|
| 语言 | Java(GUI + XML) | JavaScript | Python | 命令行参数 |
| 上手难度 | 中等 | 低 | 低 | 极低 |
| 资源消耗 | 高 | 极低 | 中 | 极低 |
| 协议支持 | HTTP/JDBC/JMS/FTP/... | HTTP/WS/gRPC | HTTP(可扩展) | 仅 HTTP |
| 分布式 | 内置 | k6 Cloud | 内置 | 不支持 |
| CI/CD 集成 | 一般 | 很好 | 很好 | 很好 |
| 脚本可维护性 | XML 不友好 | JS 代码,版本可控 | Python 代码,版本可控 | 无脚本 |
| 实时 Web UI | GUI 本身就是 | 需搭配 Grafana | 内置 | 无 |
| 生态和插件 | 极其丰富 | 丰富 | 中等 | 无 |
4.2 工具选型决策表
📖 根据你的场景选工具
| 你的情况 | 推荐工具 | 原因 |
|---|---|---|
| 团队用 Java,企业项目 | JMeter | 生态最全,企业级功能完善 |
| 需要嵌入 CI/CD Pipeline | k6 | CLI 友好,结果输出标准化 |
| 团队用 Python | Locust | Python 生态,逻辑灵活 |
| 只是快速验证一个接口 | wrk / hey | 一行命令搞定 |
| 需要测 WebSocket | k6 / Artillery | 原生支持 WS 协议 |
| 需要复杂业务逻辑 | Locust / k6 | 代码编写,逻辑灵活 |
| 需要测数据库/消息队列 | JMeter | 有 JDBC/JMS Sampler |
| 预算有限,不想付费 | 任意(都开源) | 以上都是免费开源工具 |
课堂练习
练习:为以下场景选择合适的工具,并说明理由
- 一个 Python + Flask 写的内部 API 服务,团队 3 人
- 一个微服务架构的电商平台,需要压测 50+ 接口,CI/CD 集成
- 紧急上线前 1 小时,需要快速验证首页 API 能不能扛住
步骤5:编写测试脚本 —— "模拟真实用户"
5.1 核心原则:模拟真实用户行为
⚠️ 最大的错误:把性能测试做成了 DDoS 攻击
1000 个线程无间隔循环请求 GET /api/home —— 这不是用户行为。真实用户会浏览、思考、点击、等待,中间有停顿。
5.2 脚本编写要点
| 要点 | 说明 |
|---|---|
| 加思考时间 | 每次请求之间加 2~10 秒随机等待 |
| 参数化 | 不同用户用不同账号、搜索不同关键词 |
| 关联 | 登录返回的 Token 传给后续请求 |
| 断言 | 不只看状态码 200,要验证响应内容正确 |
| 错误处理 | 遇到错误要记录,不要默默跳过 |
5.3 k6 脚本示例(电商下单流程)
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', function () {
return JSON.parse(open('./test-users.json'));
});
export const options = {
stages: [
{ duration: '2m', target: 50 }, // 2分钟加到50并发
{ duration: '5m', target: 50 }, // 保持50并发5分钟
{ duration: '2m', target: 100 }, // 加到100
{ duration: '5m', target: 100 }, // 保持100并发5分钟
{ duration: '2m', target: 0 }, // 逐步减到0
],
thresholds: {
http_req_duration: ['p(95)<500'], // P95 < 500ms
http_req_failed: ['rate<0.01'], // 错误率 < 1%
},
};
export default function () {
const user = users[__VU % users.length];
const BASE = 'https://api.example.com';
// Step 1: 登录
const loginRes = http.post(`${BASE}/api/login`, JSON.stringify({
username: user.username,
password: user.password,
}), { headers: { 'Content-Type': 'application/json' } });
check(loginRes, { '登录成功': (r) => r.status === 200 });
const token = loginRes.json('data.token');
sleep(Math.random() * 3 + 2); // 思考 2~5 秒
// Step 2: 浏览商品列表
const headers = { Authorization: `Bearer ${token}` };
const listRes = http.get(`${BASE}/api/products?page=1`, { headers });
check(listRes, { '商品列表加载': (r) => r.status === 200 });
sleep(Math.random() * 5 + 3); // 思考 3~8 秒
// Step 3: 查看商品详情
const productId = listRes.json('data.items.0.id');
const detailRes = http.get(`${BASE}/api/product/${productId}`, { headers });
check(detailRes, { '商品详情': (r) => r.status === 200 });
sleep(Math.random() * 3 + 2);
// Step 4: 加入购物车
const cartRes = http.post(`${BASE}/api/cart/add`, JSON.stringify({
product_id: productId, quantity: 1,
}), { headers: { ...headers, 'Content-Type': 'application/json' } });
check(cartRes, { '加购成功': (r) => r.status === 200 });
sleep(Math.random() * 2 + 1);
// Step 5: 创建订单
const orderRes = http.post(`${BASE}/api/order/create`, JSON.stringify({
cart_id: cartRes.json('data.cart_id'),
}), { headers: { ...headers, 'Content-Type': 'application/json' } });
check(orderRes, { '下单成功': (r) => r.status === 200 || r.status === 201 });
sleep(Math.random() * 2 + 1);
}5.4 常见错误
⚠️ 脚本编写常见坑
- **没有思考时间:**结果严重偏悲观
- **硬编码数据:**所有用户查同一个商品 → 缓存命中率 100%
- **忽略 Cookie/Session:**接口依赖登录态,但脚本没传 Token
- **没有断言:**返回 500 但因为"有响应"就当成功了
- **没有处理依赖:**下单依赖加购的 cart_id,但没有提取
课堂练习
练习:编写一个完整的压测脚本
- 选择 k6 或 Locust(根据你熟悉的语言)
- 模拟一个用户的完整操作流程(至少 3 个步骤)
- 包含:思考时间、参数化、关联、断言
- 配置阶梯式加压(ramp-up)
步骤6:分阶段执行 —— "逐步加压"
6.1 五阶段执行法
基准测试 → 负载测试 → 压力测试 → 稳定性测试 → 峰值测试
| 阶段 | 并发数 | 持续时间 | 目的 | 关注指标 |
|---|---|---|---|---|
| 基准测试 | 1 个用户 | 跑完整流程 | 建立性能基准线 | 每个接口的 RT |
| 负载测试 | 10→50→100→200→500 | 每级 5~10 分钟 | 找拐点 | RT 变化趋势、错误率 |
| 压力测试 | 超过目标 → 直到崩溃 | 逐步加 | 找天花板 | 最大 QPS、崩溃表现 |
| 稳定性测试 | 目标并发(如 500) | 4~8 小时 | 找隐藏问题 | 内存增长、连接泄漏 |
| 峰值测试 | 低 → 瞬间拉到极高 | 脉冲式 | 抗突发能力 | 恢复时间 |
6.2 每个阶段的详细操作
基准测试(Baseline)
操作:
1 个用户跑完整业务流程 3~5 遍 **记录:**每个接口的响应时间(这是零压力下的最佳值) **目的:**后续所有测试结果都和这个基准对比
负载测试(Load Test)
操作:
阶梯式加压 → 10 → 50 → 100 → 200 → 目标并发 每级保持 5~10 分钟,等系统稳定后记录数据 **重点看:**RT 曲线在哪个阶段开始上升(那个点就是"拐点")
压力测试(Stress Test)
操作:
继续加压超过目标 → 500 → 800 → 1000 → ... **目的:**找到系统天花板 —— 开始出现大量错误或直接崩溃的点 **注意:**记录崩溃时的表现(OOM?连接拒绝?响应超时?)
稳定性测试(Soak Test)
操作:
在目标并发下持续跑 4~24 小时 **目的:**找内存泄漏、连接泄漏、日志磁盘打满等长时间运行才会暴露的问题 **重点看:**内存是否持续增长、GC 频率是否增加、RT 是否逐渐变大
峰值测试(Spike Test)
操作:
从 50 并发瞬间拉到 1000,保持 2 分钟,然后降回 50 **目的:**模拟秒杀、推送通知等瞬时流量 **重点看:**高峰期是否有雪崩?降下来后多久恢复正常?
示例
阶梯式加压的典型数据记录表
| 并发数 | QPS | P50(ms) | P95(ms) | P99(ms) | 错误率 | CPU | 内存 |
|---|---|---|---|---|---|---|---|
| 10 | 95 | 45 | 120 | 180 | 0% | 15% | 2.1G |
| 50 | 450 | 55 | 150 | 220 | 0% | 42% | 2.3G |
| 100 | 880 | 65 | 200 | 350 | 0.01% | 68% | 2.5G |
| 200 | 1200 | 95 | 380 | 650 | 0.1% | 85% | 3.0G |
| 500 | 1350 | 250 | 1200 | 3500 | 2.5% | 98% | 3.8G |
| **结论:**拐点在 200 并发(RT 开始显著上升),天花板约 1350 QPS,CPU 是瓶颈。 |
课堂练习
练习:执行一次完整的分阶段压测
- 选择一个本地服务或公开 API
- 按照 5 个阶段依次执行(基准 → 负载 → 压力 → 稳定性 → 峰值)
- 记录每个阶段的关键数据
- 画出"并发数 vs 响应时间"的曲线,找到拐点
步骤7:分析结果 & 调优 —— "找到瓶颈"
7.1 两边同时看
压测时必须同时观察客户端和服务端:
| 视角 | 看什么 | 说明 |
|---|---|---|
| 客户端 (压测工具) | 响应时间 P50/P95/P99 | 是否达标 |
| 错误率 | 是否在容忍范围内 | |
| QPS/TPS | 是否达到目标 | |
| 并发数 vs RT 曲线 | 拐点在哪里 | |
| 服务端 (监控系统) | CPU | 超 80% → 计算瓶颈 |
| 内存 | 持续增长 → 内存泄漏 | |
| 磁盘 IO | 高 → 数据库读写瓶颈 | |
| 网络 | 带宽打满 → 加带宽或压缩 | |
| 数据库慢查询 | 有 → 加索引优化 SQL | |
| 连接池 | 耗尽 → 加大池或优化复用 |
7.2 调优的正确流程
压测 → 发现问题 → 定位瓶颈 → 制定方案 → 实施修改 → 再次压测 → 对比数据
💡 调优的核心原则:控制变量
每次只改一个地方,然后重新压测对比。如果同时改了 3 个地方,你不知道是哪个生效了。
7.3 常见调优手段
| 瓶颈类型 | 调优手段 | 预期效果 |
|---|---|---|
| CPU 打满 | 优化代码热点(循环、序列化)、升级配置、水平扩容 | QPS 提升 50%~200% |
| 内存不够 | 查泄漏(heap dump 分析)、减少对象创建、调整 GC 策略 | 稳定性改善 |
| 数据库慢 | 加索引、优化 SQL、读写分离、加 Redis 缓存 | RT 降低 50%~90% |
| 连接不够 | 加大连接池、使用连接复用、异步非阻塞 | 并发能力翻倍 |
| 网络瓶颈 | 启用 Gzip、减少响应体大小、使用 CDN | 带宽减少 60%+ |
| 第三方限流 | 加队列缓冲、降级策略、协商提高配额 | 减少 429 错误 |
示例
调优前后对比示例
| 指标 | 调优前 | 调优后 | 改善 |
|---|---|---|---|
| QPS | 800 | 2,200 | +175% |
| P95 RT | 1,200ms | 350ms | -71% |
| P99 RT | 3,500ms | 800ms | -77% |
| 错误率 | 2.5% | 0.05% | -98% |
| CPU 峰值 | 98% | 65% | -33% |
| **做了什么:**给 3 条慢 SQL 加了索引 + 高频查询加了 Redis 缓存(TTL 60s) |
课堂练习
练习:做一次"测→找→改→再测"的调优循环
- 对一个接口做基准测试,记录 P95 响应时间
- 尝试一种优化手段(如加缓存、优化 SQL)
- 用完全相同的压测条件重新测试
- 对比优化前后的数据,计算改善百分比
步骤8:输出结论 —— "能不能上线?"
8.1 最终结论模板
✅ 性能测试最终结论(示例)
测试目标:系统需支撑 2,000 并发用户、3,000 QPS
测试环境:4C8G × 3 节点 + MySQL 8C16G + Redis 4G
测试结果:
✓ 系统稳定支撑 2,500 并发用户,QPS 达到 3,500
✓ P95 响应时间 320ms(目标 < 500ms)✅ 达标
✓ P99 响应时间 680ms(目标 < 1s)✅ 达标
✓ 错误率 0.03%(目标 < 0.1%)✅ 达标
✓ 持续运行 8 小时无内存泄漏、无性能衰减
✓ 性能拐点在 2,000 并发,天花板在 3,000 并发
✓ 峰值测试:瞬时 5,000 并发有 5% 超时,2 分钟内恢复
瓶颈分析:
→ 主要瓶颈在 MySQL 查询(已通过加索引+缓存解决)
→ 次要瓶颈在应用层 GC(已调整 JVM 参数)
结论:✅ 满足上线要求,可以发布
风险提示:
→ 如果日活超过 80 万,建议提前做数据库读写分离
→ 促销活动期间建议临时加 2 个应用节点8.2 好结论 vs 差结论
| 差结论 | 好结论 |
|---|---|
| "测了 1000 并发没问题" | "系统在 1000 并发下 P95=320ms、错误率 0.03%,达标" |
| "性能还行" | "QPS 达到 3500,超过目标 3000 的 17%" |
| "数据库有点慢" | "order 表缺少 user_id 索引,200 并发时慢查询占比 35%" |
| "建议优化" | "建议给 order 表 user_id 字段加索引,预计 RT 降低 60%" |
💡 结论要有"数据+分析+建议"
不只是堆数据,要分析原因,给出可执行的建议。领导/开发看你的报告后,应该知道下一步该做什么。
课堂练习
练习:写一份性能测试结论 假设你做了以下压测(数据虚构):
- 目标:支撑 500 并发、1000 QPS、P95 < 800ms
- 实际:500 并发时 QPS=950、P95=720ms、错误率 0.2%
- 800 并发时 P95=2500ms、错误率 5%
- CPU 在 500 并发时 75%,800 并发时 98% 请写出完整的结论,包含:是否达标、瓶颈在哪、优化建议、风险提示。
JMeter 入门
工具简介
Apache JMeter 是最老牌的性能测试工具,2003 年首次发布,基于 Java 开发。它有 GUI 界面,支持的协议最多(HTTP/HTTPS/FTP/JDBC/JMS/LDAP/SOAP 等),插件生态极其丰富。适合企业级项目和需要测试非 HTTP 协议的场景。
适用场景
- 企业级项目,需要 GUI 方便非开发人员使用
- 需要测试数据库(JDBC)、消息队列(JMS)等非 HTTP 协议
- 需要丰富的插件(如 JP@GC 系列监控插件)
- 团队技术栈是 Java
安装方法
# 前提:安装 Java 8+
java -version
# 方式 1:下载二进制包
# 访问 https://jmeter.apache.org/download_jmeter.cgi
# 下载 apache-jmeter-5.6.3.zip
unzip apache-jmeter-5.6.3.zip
cd apache-jmeter-5.6.3/bin
# 启动 GUI(仅用于编写脚本,不要用 GUI 压测)
./jmeter.sh # Linux/Mac
jmeter.bat # Windows
# 命令行模式执行(推荐!)
./jmeter -n -t test-plan.jmx -l result.jtl -e -o report/
# 方式 2:Mac Homebrew
brew install jmeter最小可用示例
JMeter 使用 XML 格式的 .jmx 文件,通常用 GUI 创建后导出。以下是纯命令行操作:
# 1. 打开 JMeter GUI
./jmeter.sh
# 2. 在 GUI 中操作:
# Test Plan → 右键 Add → Threads → Thread Group
# 设置:线程数=10, 循环=100
# Thread Group → Add → Sampler → HTTP Request
# 设置:Server=httpbin.org, Path=/get
# Thread Group → Add → Listener → View Results Tree
# Thread Group → Add → Listener → Summary Report
# 保存为 test.jmx
# 3. 命令行执行(正式压测必须用命令行模式)
jmeter -n -t test.jmx -l result.jtl -e -o html-report/
# 参数说明:
# -n 非 GUI 模式
# -t 测试计划文件
# -l 结果日志文件
# -e 生成 HTML 报告
# -o 报告输出目录优缺点
| 优点 | 缺点 |
|---|---|
| 协议支持最全 | Java 编写,资源消耗大 |
| GUI 友好,门槛低 | .jmx 是 XML,版本管理困难 |
| 插件极其丰富 | 启动慢,占内存多 |
| 分布式模式内置 | 复杂逻辑用 BeanShell 不方便 |
| 社区大,资料多 | CI/CD 集成不如 k6 方便 |
k6 入门
工具简介
k6 是 Grafana Labs 开发的现代性能测试工具,用 Go 编写核心引擎、JavaScript 编写测试脚本。它极其轻量(单个二进制文件)、资源消耗低、对开发者友好,原生支持与 CI/CD 和 Grafana 集成。
适用场景
- 开发者友好的团队,习惯写代码而非用 GUI
- 需要嵌入 CI/CD Pipeline(GitHub Actions/GitLab CI)
- HTTP/WebSocket/gRPC 测试
- 需要与 Grafana 集成做可视化
安装方法
# Mac
brew install k6
# Linux (Debian/Ubuntu)
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
--keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D68
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
| sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6
# Docker
docker run --rm -i grafana/k6 run - <script.js
# Windows
choco install k6
# 或 winget install k6最小可用脚本
// file: basic-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 10, // 10 个虚拟用户
duration: '30s', // 持续 30 秒
thresholds: {
http_req_duration: ['p(95)<500'], // P95 < 500ms
http_req_failed: ['rate<0.01'], // 错误率 < 1%
},
};
export default function () {
const res = http.get('https://httpbin.org/get');
check(res, {
'状态码 200': (r) => r.status === 200,
'响应时间 < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}
// 运行:k6 run basic-test.js阶梯式加压脚本
// file: staged-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 20 }, // 1分钟内加到 20 VU
{ duration: '3m', target: 20 }, // 保持 20 VU 三分钟
{ duration: '1m', target: 50 }, // 加到 50
{ duration: '3m', target: 50 }, // 保持 50
{ duration: '1m', target: 100 }, // 加到 100
{ duration: '3m', target: 100 }, // 保持 100
{ duration: '2m', target: 0 }, // 降到 0
],
};
export default function () {
const res = http.get('https://httpbin.org/get');
check(res, { 'OK': (r) => r.status === 200 });
sleep(Math.random() * 3 + 1); // 1~4 秒思考时间
}
// 运行并输出 JSON 结果:
// k6 run --out json=results.json staged-test.js优缺点
| 优点 | 缺点 |
|---|---|
| Go 核心极其轻量,资源消耗低 | 不支持浏览器端性能测试(需要 k6 browser) |
| JS 脚本,开发者友好 | 不能安装 npm 包(受限的 JS 运行时) |
| CLI 模式天然适合 CI/CD | 没有内置 GUI |
| 原生支持 Grafana 输出 | 分布式需要 k6 Cloud(付费) |
| 单机能模拟几万 VU | 非 HTTP 协议支持有限 |
Locust 入门
工具简介
Locust 是用 Python 编写的性能测试框架,以"蝗虫"命名,寓意"像蝗虫群一样发起攻击"。它使用 Python 代码定义用户行为,内置 Web UI 可以实时观察结果,分布式扩展非常容易。
适用场景
- 团队技术栈是 Python
- 测试逻辑复杂,需要灵活的代码控制
- 需要实时 Web UI 监控
- 需要简单的分布式压测
安装方法
# pip 安装
pip install locust
# 验证安装
locust --version
# Docker 方式
docker run -p 8089:8089 -v $PWD:/mnt/locust locustio/locust \
-f /mnt/locust/locustfile.py最小可用脚本
# file: locustfile.py
from locust import HttpUser, task, between
class WebUser(HttpUser):
wait_time = between(1, 5) # 每次请求间等待 1~5 秒
host = "https://httpbin.org"
@task(3) # 权重 3,被选中的概率是其他的 3 倍
def view_homepage(self):
self.client.get("/get")
@task(1) # 权重 1
def view_post(self):
self.client.get("/headers")
def on_start(self):
"""每个虚拟用户启动时执行一次"""
pass
# 运行方式 1:Web UI 模式(浏览器打开 http://localhost:8089)
# locust -f locustfile.py
# 运行方式 2:命令行模式(无 UI)
# locust -f locustfile.py --headless -u 100 -r 10 -t 5m
# -u 100 总用户数
# -r 10 每秒启动 10 个用户
# -t 5m 持续 5 分钟带登录和业务逻辑的脚本
# file: locustfile_ecommerce.py
from locust import HttpUser, task, between, SequentialTaskSet
import json
class ShoppingFlow(SequentialTaskSet):
"""顺序执行的任务集:登录 → 浏览 → 加购 → 下单"""
def on_start(self):
resp = self.client.post("/api/login", json={
"username": "testuser",
"password": "testpass"
})
self.token = resp.json().get("token", "")
self.headers = {"Authorization": f"Bearer {self.token}"}
@task
def browse_products(self):
self.client.get("/api/products", headers=self.headers)
@task
def view_detail(self):
self.client.get("/api/product/1001", headers=self.headers)
@task
def add_to_cart(self):
self.client.post("/api/cart/add",
json={"product_id": 1001, "quantity": 1},
headers=self.headers)
@task
def create_order(self):
self.client.post("/api/order/create",
json={"cart_id": "latest"},
headers=self.headers)
self.interrupt() # 结束这个任务集,重新开始
class EcommerceUser(HttpUser):
wait_time = between(2, 5)
host = "https://api.example.com"
tasks = [ShoppingFlow]优缺点
| 优点 | 缺点 |
|---|---|
| Python 代码,逻辑灵活 | Python 性能不如 Go/Java |
| 内置 Web UI,实时可视化 | 单机并发能力有限(几千 VU) |
| 分布式简单(master-worker) | 只支持 HTTP/HTTPS(需扩展才支持其他) |
| 代码即测试,版本管理方便 | 缺少内置的断言框架 |
| 社区活跃,文档清晰 | 报告功能较简单 |
命令行工具(wrk / hey / ab)
工具简介
当你只需要快速验证一个接口的基本性能时,这些命令行工具是最快的选择。一行命令就能得到 QPS、延迟分布等关键数据。
wrk —— 高性能 HTTP 基准测试工具
# 安装
brew install wrk # Mac
apt install wrk # Ubuntu
# 基本用法
wrk -t4 -c100 -d30s https://httpbin.org/get
# -t4 4 个线程
# -c100 100 个并发连接
# -d30s 持续 30 秒
# 输出示例:
# Running 30s test @ https://httpbin.org/get
# 4 threads and 100 connections
# Thread Stats Avg Stdev Max +/- Stdev
# Latency 45.32ms 12.10ms 250ms 85.20%
# Req/Sec 552.31 120.45 1.2k 72.00%
# 66120 requests in 30s, 28.5MB read
# Requests/sec: 2204.00
# Transfer/sec: 973KB
# 带 Lua 脚本的高级用法(POST 请求)
wrk -t4 -c100 -d30s -s post.lua https://api.example.com/loginhey —— Go 编写的 HTTP 压测工具
# 安装
brew install hey # Mac
go install github.com/rakyll/hey@latest # Go
# 基本用法
hey -n 10000 -c 100 https://httpbin.org/get
# -n 10000 总共发 10000 个请求
# -c 100 100 个并发
# POST 请求
hey -n 5000 -c 50 -m POST \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"pass"}' \
https://api.example.com/login
# 输出包含:延迟分布(P10/P25/P50/P75/P90/P95/P99)、状态码分布ab(Apache Bench)—— Apache 自带
# 大部分系统自带
ab -n 10000 -c 100 https://httpbin.org/get
# -n 10000 总请求数
# -c 100 并发数
# POST 请求
ab -n 5000 -c 50 -p data.json -T "application/json" \
https://api.example.com/login命令行工具对比
| 工具 | 语言 | 优势 | 局限 |
|---|---|---|---|
| wrk | C | 性能最高,支持 Lua 脚本 | 只支持 Linux/Mac |
| hey | Go | 输出详细延迟分布,跨平台 | 不支持脚本扩展 |
| ab | C | 系统自带,无需安装 | 功能最简单,单线程 |
📖 什么时候用命令行工具?
- 快速验证:上线前 5 分钟,用
hey验证一下接口 - 基准对比:优化前后用
wrk快速对比 QPS - CI 冒烟测试:在 Pipeline 里加一步
hey验证 **不适合:**复杂业务流程、需要登录态、多接口混合场景
课堂练习
练习:用命令行工具对公开 API 做基准测试
- 安装
hey(或wrk) - 对
https://httpbin.org/get做基准测试:100 并发、持续 30 秒 - 记录 QPS、P50/P95/P99 延迟
- 改成 200 并发重新测,对比结果差异
- 用 POST 方法测试
https://httpbin.org/post,观察和 GET 的性能差异
操作系统层监控
性能测试时,操作系统层是最基础的监控层。你需要实时观察 CPU、内存、磁盘、网络四大维度。
CPU 监控
| 指标 | 含义 | 健康范围 | 异常说明 |
|---|---|---|---|
| %user | 用户态 CPU(应用代码运行) | < 70% | 高 → 应用逻辑是瓶颈 |
| %system | 内核态 CPU(系统调用、网络处理) | < 30% | 高 → 可能大量系统调用/上下文切换 |
| %iowait | CPU 等待磁盘 IO 完成 | < 10% | 高 → 磁盘是瓶颈(慢查询/大量日志写入) |
| %steal | 被虚拟化宿主机抢占 | < 5% | 高 → 云主机被超卖,换机器 |
| %idle | 空闲 CPU | > 20% | 接近 0 → CPU 已饱和 |
# 查看 CPU 使用率(每 1 秒刷新)
top -d 1
# 或更友好的
htop
# 查看 CPU 各维度详情
vmstat 1
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 0 0 245612 52840 1024000 0 0 0 12 800 600 35 5 55 5 0
# mpstat 查看每个 CPU 核心
mpstat -P ALL 1
# 查看上下文切换(context switch)
vmstat 1 | awk '{print $12, $13}' # cs 和 us 列内存监控
| 指标 | 含义 | 说明 |
|---|---|---|
| used | 已使用内存 | 应用 + 缓存实际占用 |
| cached | 文件缓存(可回收) | Linux 会尽量用内存做缓存,这部分可回收 |
| buffer | 磁盘缓冲 | 写操作的缓冲区 |
| swap | 交换分区(用磁盘当内存) | 一旦使用 swap 性能会急剧下降 |
| available | 实际可用内存 | free + 可回收的 cached/buffer |
⚠️ 看内存不能只看 "free"
Linux 的 free 经常显示很低,但这不代表内存不够。要看 available( = free + cached 可回收部分)。真正的告警线是 available < 10% 或 swap 使用 > 0。
# 查看内存
free -h
# total used free shared buff/cache available
# Mem: 16Gi 6.2Gi 1.8Gi 256Mi 8.0Gi 9.2Gi
# Swap: 4.0Gi 0B 4.0Gi
# 持续监控内存变化(观察是否有泄漏)
watch -n 1 free -h磁盘监控
| 指标 | 含义 | 健康范围 |
|---|---|---|
| IOPS | 每秒读写次数 | 取决于磁盘类型(SSD: 数万, HDD: 数百) |
| 吞吐量 | 每秒读写数据量(MB/s) | SSD: 200~3000 MB/s, HDD: 50~200 MB/s |
| 队列长度(avgqu-sz) | 等待处理的 IO 请求数 | < 2 为佳,> 10 说明磁盘忙不过来 |
| %util | 磁盘忙碌时间占比 | < 80% |
| await | IO 请求平均等待时间(ms) | SSD < 2ms, HDD < 10ms |
# iostat 查看磁盘 IO
iostat -xdm 1
# Device r/s w/s rMB/s wMB/s await %util
# sda 120 450 1.2 8.5 3.2 65%
# iotop 查看哪个进程占 IO 最多
sudo iotop -o网络监控
| 指标 | 含义 | 说明 |
|---|---|---|
| 带宽使用 | 进出流量(MB/s) | 千兆网卡上限约 120 MB/s |
| 连接数 | TCP 活跃连接总数 | 每个连接占用文件描述符 |
| ESTABLISHED | 已建立的连接 | 正在通信的连接 |
| TIME_WAIT | 等待关闭的连接 | 大量 TIME_WAIT → 短连接太多 |
| CLOSE_WAIT | 对端已关闭但本端未关闭 | 大量 CLOSE_WAIT → 应用没正确关闭连接(泄漏) |
# 查看网络流量(需安装 iftop)
sudo iftop
# 查看 TCP 连接状态分布
ss -s
# 或
netstat -ant | awk '{print $6}' | sort | uniq -c | sort -rn
# 典型输出:
# 5200 ESTABLISHED
# 1800 TIME_WAIT
# 50 CLOSE_WAIT ← 如果这个数字持续增长,说明有连接泄漏
# 20 LISTEN常用监控命令汇总
| 命令 | 用途 | 常用参数 |
|---|---|---|
| top / htop | 整体概览(CPU/内存/进程) | top -d 1 每秒刷新 |
| vmstat | CPU/内存/IO/上下文切换 | vmstat 1 每秒采样 |
| iostat | 磁盘 IO 详情 | iostat -xdm 1 |
| netstat / ss | 网络连接 | ss -s 连接统计 |
| iftop | 实时网络流量 | sudo iftop -i eth0 |
| sar | 历史性能数据(系统活动报告) | sar -u 1 10 CPU、 sar -r 内存 |
| dstat | 多合一监控(CPU+IO+网络) | dstat -cdnm |
课堂练习
练习:在服务器上执行系统监控
- 在一台 Linux 服务器上打开
htop,识别 CPU 占用最高的进程 - 用
free -h查看内存,计算available占total的百分比 - 用
ss -s查看 TCP 连接状态分布,是否有大量 TIME_WAIT 或 CLOSE_WAIT - 用
iostat -xdm 1观察磁盘,看%util是否接近 100%
应用层监控
操作系统指标告诉你"机器怎么样",但不告诉你"应用怎么样"。应用层监控深入到代码和运行时层面。
线程 / 协程
| 语言 | 关注指标 | 工具 |
|---|---|---|
| Java | 线程数、BLOCKED/WAITING 状态线程 | jstack 、VisualVM、Arthas |
| Go | Goroutine 数量 | pprof 、 runtime.NumGoroutine() |
| Python | 线程数、协程数(asyncio) | py-spy 、内置 threading.active_count() |
| Node.js | Event Loop 延迟、活跃 Handle 数 | clinic.js 、 --inspect |
连接池使用率
💡 连接池是最常见的应用层瓶颈之一
| 连接池类型 | 关注什么 | 告警条件 |
|---|---|---|
| 数据库连接池 | 活跃连接数 / 最大连接数 | 使用率 > 80% |
| HTTP 连接池 | 等待获取连接的线程数 | 有线程等待超时 |
| Redis 连接池 | 已用 / 最大连接数 | 使用率 > 80% |
# Java:查看 HikariCP 连接池(通过 JMX 或日志)
# 配置 HikariCP 打印连接池状态:
# spring.datasource.hikari.maximum-pool-size=20
# spring.datasource.hikari.leak-detection-threshold=30000
# Go:database/sql 连接池监控
# db.Stats() 返回 MaxOpenConnections, InUse, Idle, WaitCount, WaitDurationGC(垃圾回收)监控
| 语言 | GC 特点 | 关注指标 | 问题表现 |
|---|---|---|---|
| Java | Full GC 会 STW(Stop The World) | GC 频率、GC 停顿时间、老年代使用率 | Full GC 频繁 → 长时间卡顿 |
| Go | GC 停顿短(通常 < 1ms) | GC 停顿、堆大小变化 | 内存分配速率过高 |
| Python | 引用计数 + 分代回收 | 循环引用数量 | 大对象释放慢 |
# Java GC 日志分析
# 启动参数加:
# -Xlog:gc*:file=gc.log:time,uptime,level,tags
# 或旧版本:
# -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
# 使用 Arthas(阿里开源 Java 诊断工具)
java -jar arthas-boot.jar
dashboard # 实时看线程、内存、GC 等请求队列与错误日志
- **请求队列长度:**Tomcat 的 acceptCount、Nginx 的 listen backlog —— 队列满了请求就被拒绝
- **错误日志频率:**压测时观察错误日志的增长速度 —— 如果每秒几百条错误日志,可能磁盘 IO 就被日志打满了
数据库层监控
活跃连接数
# MySQL
SHOW STATUS LIKE 'Threads_connected'; -- 当前连接数
SHOW VARIABLES LIKE 'max_connections'; -- 最大连接数
SHOW PROCESSLIST; -- 每个连接在做什么
# PostgreSQL
SELECT count(*) FROM pg_stat_activity; -- 当前连接数
SHOW max_connections; -- 最大连接数慢查询
📖 慢查询是数据库层最常见的性能杀手
# MySQL:开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过 1 秒记为慢查询
SET GLOBAL slow_query_log_file = '/var/log/mysql-slow.log';
# 查看慢查询日志
mysqldumpslow -s t -t 10 /var/log/mysql-slow.log # 按时间排序 Top 10
# 分析执行计划
EXPLAIN SELECT * FROM orders WHERE user_id = 12345;
# 关注:type(ALL 是全表扫描=灾难)、rows(扫描行数)、Extra(Using filesort 不好)
# 典型的慢查询修复:
# 问题:SELECT * FROM orders WHERE user_id = 12345; → 全表扫描 50 万行
# 解决:ALTER TABLE orders ADD INDEX idx_user_id (user_id);
# 效果:50 万行扫描 → 3 行扫描,RT 从 2s 降到 5ms锁等待
# MySQL:查看当前锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
SELECT * FROM information_schema.INNODB_TRX;
# 典型场景:大事务持有锁太久,其他请求排队等待
# 解决:缩小事务范围、避免在事务中调用外部 API缓冲池命中率
# MySQL InnoDB Buffer Pool 命中率
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
# 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) × 100%
# 健康值:> 99%
# 低于 95% 说明 buffer pool 太小,数据频繁从磁盘读取主从复制延迟
# MySQL:查看从库延迟
SHOW SLAVE STATUS\G
# 关注:Seconds_Behind_Master
# 健康值:< 1s
# 超过 10s → 读写分离场景下用户可能读到旧数据课堂练习
练习:分析一个数据库的健康状况
- 连接一个 MySQL 数据库
- 查看当前连接数和最大连接数
- 开启慢查询日志,执行一些查询后查看慢日志
- 用
EXPLAIN分析一条查询的执行计划 - 计算 InnoDB Buffer Pool 命中率
中间件层监控
Redis 监控
| 指标 | 含义 | 健康范围 | 异常处理 |
|---|---|---|---|
| used_memory | 已使用内存 | < maxmemory 的 80% | 检查大 Key、设置合理过期时间 |
| keyspace_hits/misses | 缓存命中率 | > 95% | 低 → 缓存策略有问题 |
| connected_clients | 当前客户端连接数 | < maxclients 的 80% | 连接泄漏 → 检查应用连接池配置 |
| blocked_clients | 阻塞的客户端数 | 0 | > 0 → 有 BLPOP 等阻塞操作 |
| 大 Key | 单个 Key 超过 10KB | 无大 Key | 拆分大 Key,避免 HGETALL 大 Hash |
# Redis 实时监控
redis-cli INFO stats
redis-cli INFO memory
# 命中率计算
# hits / (hits + misses) × 100%
# 查找大 Key
redis-cli --bigkeys
# 实时命令监控(注意:生产环境慎用 MONITOR)
redis-cli MONITOR消息队列监控(Kafka/RabbitMQ)
| 指标 | 含义 | 告警条件 |
|---|---|---|
| 消息堆积量(Lag) | 未消费的消息数 | 持续增长 → 消费速度跟不上生产 |
| 消费速度 | 每秒消费消息数 | 低于生产速度 → 需要增加消费者 |
| 端到端延迟 | 消息从生产到被消费的时间 | > 10s 需关注 |
| 消费者组状态 | Rebalancing/Stable | 频繁 Rebalance → 消费者不稳定 |
Nginx / 网关监控
| 指标 | 含义 | 告警条件 |
|---|---|---|
| Active Connections | 当前活跃连接数 | 接近 worker_connections 配置 |
| Request Queue | 等待处理的请求数 | 持续 > 0 → 后端处理不过来 |
| 5xx 错误数 | 上游返回 5xx 的频率 | > 0 就需要排查 |
| upstream_response_time | 后端实际处理时间 | 和直接访问后端对比判断网关开销 |
# Nginx 启用 stub_status
# 在 nginx.conf 中添加:
# location /nginx_status {
# stub_status on;
# allow 127.0.0.1;
# deny all;
# }
# 访问查看
curl http://localhost/nginx_status
# Active connections: 350
# server accepts handled requests
# 12456789 12456789 45678901
# Reading: 5 Writing: 120 Waiting: 225Prometheus + Grafana
架构简介
被监控目标(Exporter) → Prometheus(采集+存储) → Grafana(展示+告警)
Prometheus 定时拉取(Pull)各个 Exporter 暴露的指标数据,存储在时序数据库中。Grafana 从 Prometheus 查询数据并以图表展示。
核心概念
| 概念 | 说明 | 示例 |
|---|---|---|
| Metric(指标) | 被监控的数值 | http_requests_total |
| Label(标签) | 给指标加维度 | http_requests_total |
| Counter | 只增不减的计数器 | 总请求数、总错误数 |
| Gauge | 可增可减的瞬时值 | 当前连接数、内存使用量 |
| Histogram | 数据分布(自动计算分位数) | 请求延迟分布 |
| PromQL | Prometheus 查询语言 | 用于查询和聚合指标 |
常用 PromQL 查询
# QPS(最近 5 分钟的请求速率)
rate(http_requests_total[5m])
# 按接口分组的 QPS
sum(rate(http_requests_total[5m])) by (path)
# P95 延迟
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
# 错误率
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) * 100
# CPU 使用率
100 - (avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 内存使用率
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100常用 Exporter
| Exporter | 监控对象 |
|---|---|
| Node Exporter | Linux 操作系统(CPU/内存/磁盘/网络) |
| MySQL Exporter | MySQL(连接数/查询/缓冲池) |
| Redis Exporter | Redis(内存/命中率/连接数) |
| Nginx Exporter | Nginx(连接/请求/上游) |
| JMX Exporter | Java 应用(线程/GC/堆内存) |
| cAdvisor | Docker 容器(CPU/内存/网络) |
推荐 Grafana 看板
| 看板 ID | 名称 | 用途 |
|---|---|---|
| 1860 | Node Exporter Full | Linux 系统全指标看板 |
| 7362 | MySQL Overview | MySQL 核心指标 |
| 763 | Redis Dashboard | Redis 核心指标 |
| 12708 | Nginx Ingress | Nginx 核心指标 |
课堂练习
练习:搭建最小 Prometheus + Grafana 监控
- 用 Docker Compose 启动 Prometheus + Grafana + Node Exporter
- 配置 Prometheus 抓取 Node Exporter
- 在 Grafana 导入看板 ID 1860
- 执行一次压测,观察 Grafana 上 CPU/内存/网络的实时变化 提示:Docker Compose 是最快的搭建方式,一个 YAML 文件搞定。
瓶颈定位方法论
5 层定位法
从外到内、逐层排查:
第1层:压测工具 → 第2层:操作系统 → 第3层:应用层 → 第4层:数据库 → 第5层:外部依赖
第1层:看压测工具(客户端视角)
| 症状 | 初步判断 |
|---|---|
| RT 缓慢上升 | 服务端处理能力饱和 |
| QPS 达到上限不再增长 | 有某个资源是瓶颈 |
| 大量 Connection Refused | 连接数上限(Nginx/应用/DB) |
| 大量 Timeout | 后端处理超时或外部调用超时 |
| 大量 5xx 错误 | 应用抛异常/OOM/进程被 Kill |
第2层:看操作系统
| 症状 | 判断 | 工具 |
|---|---|---|
| CPU > 80%(user 高) | 应用计算密集 → 优化代码或扩容 | top / htop |
| CPU > 80%(iowait 高) | 磁盘 IO 瓶颈 → 查慢查询/日志 | vmstat / iostat |
| 内存持续增长 | 内存泄漏 | free -h 持续观察 |
| Swap 被使用 | 物理内存不够了 | free -h |
| 网络带宽打满 | 响应体太大/连接数太多 | iftop / sar -n |
| 大量 TIME_WAIT | 短连接太多 → 启用连接复用 | ss -s |
第3层:看应用层
| 症状 | 判断 | 工具 |
|---|---|---|
| 线程全部 BLOCKED/WAITING | 锁竞争或等待外部 IO | jstack / pprof |
| 连接池耗尽 | 下游处理慢,连接等待超时 | 应用日志/监控 |
| Full GC 频繁 | 内存不够/内存泄漏 | GC 日志 / jstat |
| 请求队列堆积 | 处理速度跟不上请求速度 | Tomcat/Netty 监控 |
第4层:看数据库
| 症状 | 判断 | 工具 |
|---|---|---|
| 慢查询多 | 缺索引/SQL 不优 | 慢查询日志 + EXPLAIN |
| 锁等待 | 大事务/热点行 | SHOW PROCESSLIST |
| 连接数打满 | 连接池太大或泄漏 | SHOW STATUS |
| 主从延迟 | 主库写压力大 | SHOW SLAVE STATUS |
第5层:看外部依赖
| 症状 | 判断 | 处理方式 |
|---|---|---|
| 第三方 API 响应慢 | 对方服务端问题 | 加超时控制 + 降级策略 |
| 第三方返回 429 | 触发限流 | 加队列缓冲 / 协商配额 |
| DNS 解析慢 | DNS 服务器问题 | 配置本地 DNS 缓存 |
常见误判案例
💡 误判1:CPU 不高但性能差
**现象:**200 并发时 QPS 不涨了,CPU 只有 40%,内存充足。 **原因:**线程全在等待 IO(慢查询/外部 API/锁等待)。CPU 不忙是因为在"等"而不是在"算"。 **排查:**用 jstack 看线程状态 → 发现 80% 线程在 WAITING 状态等数据库返回。 **解决:**优化慢查询 + 减少外部调用 + 改为异步非阻塞。
💡 误判2:加 CPU 不管用
**现象:**从 4 核升级到 16 核,QPS 只提升了 10%。 **原因:**瓶颈不在 CPU。可能在数据库(锁竞争)、网络(带宽不够)、或应用层(全局锁/单线程处理)。 **教训:**先定位瓶颈再扩容,否则花了钱没效果。
💡 误判3:错误率低但用户投诉体验差
**现象:**错误率 0.01%,看起来完美。但用户投诉"很卡"。 **原因:**平均 RT 正常(200ms),但 P99 高达 8 秒。1% 的用户每次等 8 秒。 **教训:**看 P95/P99,不看平均值。少数用户的极端体验也很重要。
定位工具
| 工具 | 用途 | 使用场景 |
|---|---|---|
| 火焰图(Flame Graph) | 可视化 CPU 时间分布 | 找到代码中哪个函数最耗 CPU |
| 慢查询日志 | 记录执行时间超过阈值的 SQL | 数据库瓶颈定位 |
| Thread Dump(jstack) | 查看所有线程的调用栈 | 找锁竞争、线程阻塞 |
| Heap Dump(jmap) | 内存快照分析 | 内存泄漏定位 |
| pprof(Go) | CPU/内存/阻塞分析 | Go 应用的全方位性能分析 |
| py-spy(Python) | 采样式 CPU 分析 | Python 应用无侵入式性能分析 |
| Arthas(Java) | 实时诊断工具 | 在线定位 Java 应用问题 |
# 生成 Java 火焰图
# 使用 async-profiler
./asprof -d 30 -f flamegraph.html <pid>
# Go pprof
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Python py-spy
py-spy record -o flamegraph.svg --pid <pid>常见性能问题清单
应用层常见问题(20条)
| # | 问题 | 症状 | 解决方案 |
|---|---|---|---|
| 1 | N+1 查询 | 一个列表页执行几百条 SQL | 使用 JOIN 查询或批量查询 |
| 2 | 连接池太小 | 请求排队等连接、超时增多 | 调大连接池并监控使用率 |
| 3 | 同步阻塞调用 | 线程在等待外部 IO | 改为异步非阻塞 |
| 4 | 没有缓存 | 每次请求都查数据库 | 加 Redis 缓存热点数据 |
| 5 | 缓存穿透 | 查不存在的数据每次打到 DB | 布隆过滤器 / 缓存空值 |
| 6 | 缓存雪崩 | 大量缓存同时过期 | 随机化过期时间 |
| 7 | 缓存击穿 | 热点 Key 过期后大量请求打到 DB | 互斥锁 / 永不过期 + 异步更新 |
| 8 | 内存泄漏 | 内存持续增长直到 OOM | Heap Dump 分析、修复泄漏点 |
| 9 | 频繁 Full GC | 周期性卡顿 | 调整 JVM 参数、减少对象创建 |
| 10 | 日志写太多 | 磁盘 IO 高、磁盘空间不足 | 降低日志级别、异步写日志 |
| 11 | 序列化/反序列化慢 | JSON 解析占大量 CPU | 换高性能库(Jackson → fastjson2) |
| 12 | 正则表达式灾难性回溯 | 特定输入导致 CPU 100% | 优化正则或限制输入长度 |
| 13 | 全局锁/同步块过大 | 并发提升但 QPS 不涨 | 缩小同步范围、使用无锁数据结构 |
| 14 | 线程池满 | 请求被拒绝(RejectedExecution) | 调大线程池 / 加队列缓冲 |
| 15 | 大对象传输 | 带宽高、RT 长 | 分页、压缩、按需加载 |
| 16 | 没有超时控制 | 下游慢时级联阻塞 | 设置合理的超时 + 熔断 |
| 17 | 重复计算 | CPU 高,同样的计算做多次 | 结果缓存 / Memoization |
| 18 | 连接泄漏 | CLOSE_WAIT 持续增长 | 确保 finally 中关闭连接 |
| 19 | 无限循环 / 死循环 | 单个请求 CPU 100% | 代码审查、加循环上限 |
| 20 | 依赖服务无降级 | 一个下游挂了整个系统跟着挂 | 熔断器 + 降级策略 |
数据库层常见问题(15条)
| # | 问题 | 症状 | 解决方案 |
|---|---|---|---|
| 1 | 缺少索引 | 全表扫描、慢查询 | 分析 EXPLAIN 加索引 |
| 2 | 索引失效 | 有索引但没用上 | 避免函数包裹列、隐式类型转换 |
| 3 | 大事务 | 锁持有时间长 | 拆分事务、减少事务内操作 |
| 4 | 热点行竞争 | 大量请求更新同一行 | 乐观锁 / 分桶计数 |
| 5 | SELECT * | 返回无用列浪费带宽和内存 | 只查需要的列 |
| 6 | 大表无分区 | 数据量上亿后查询极慢 | 按时间/ID 分表分区 |
| 7 | 连接数不够 | Connection Refused | 加大 max_connections、优化连接池 |
| 8 | Buffer Pool 太小 | 频繁磁盘读取 | 调大 innodb_buffer_pool_size |
| 9 | 深分页(OFFSET 大) | OFFSET 10000 时很慢 | 改用游标分页 WHERE id > last_id |
| 10 | 子查询代替 JOIN | 子查询性能远差于 JOIN | 改写为 JOIN |
| 11 | 主从延迟高 | 从库读到旧数据 | 写后读走主库、减少主库压力 |
| 12 | 临时表/文件排序 | Using temporary; Using filesort | 优化 ORDER BY 走索引 |
| 13 | 死锁 | 两个事务相互等待 | 统一加锁顺序、缩小事务 |
| 14 | binlog 过大 | 磁盘打满 | 调整 expire_logs_days |
| 15 | 未参数化查询 | 每次都要 hard parse | 使用 Prepared Statement |
网络层常见问题(10条)
| # | 问题 | 症状 | 解决方案 |
|---|---|---|---|
| 1 | 带宽不足 | 传输速率到顶、RT 变长 | 升级带宽 / 压缩响应 |
| 2 | DNS 解析慢 | 首次请求特别慢 | 本地 DNS 缓存 / 用 IP 直连 |
| 3 | SSL 握手开销 | HTTPS 首次连接慢 | 启用 TLS Session 复用 |
| 4 | 大量短连接 | TIME_WAIT 堆积 | 启用 Keep-Alive 长连接 |
| 5 | 跨区域调用 | 延迟高(50~200ms RTT) | 就近部署 / CDN |
| 6 | TCP 丢包重传 | 偶发性延迟飙升 | 检查网络质量 |
| 7 | 端口耗尽 | Cannot assign requested address | 调大端口范围 / 连接复用 |
| 8 | Nginx worker_connections 不够 | 502 Bad Gateway | 增大 worker_connections |
| 9 | 负载均衡不均 | 部分节点 CPU 高,部分空闲 | 调整负载均衡算法 |
| 10 | 防火墙/安全组限制 | 连接被拒 | 检查安全组端口开放 |
调优策略与验证
调优的正确流程
①测 → ②找瓶颈 → ③制定方案 → ④改 → ⑤再测 → ⑥对比
⚠️ 调优的第一条铁律:先证明有瓶颈,再优化
"感觉可能慢"不是优化理由。没有数据支撑的优化 = 瞎改。先压测拿到数据,证明确实有问题,再动手。
调优手段分类
| 类别 | 手段 | 成本 | 效果 |
|---|---|---|---|
| 代码优化 | 修复 N+1 查询 | 低 | 可能提升 10 倍 |
| 加缓存 | 低~中 | 减少 DB 压力 80%+ | |
| 优化算法复杂度 | 中 | 视情况而定 | |
| 改同步为异步 | 中~高 | 并发能力翻倍 | |
| 配置调优 | 调大连接池 | 极低 | 消除连接等待 |
| 调整 JVM/GC 参数 | 低 | 减少 GC 停顿 | |
| 调整数据库参数 | 低 | 提升缓冲池命中率 | |
| 架构优化 | 读写分离 | 中 | 读性能翻倍 |
| 微服务拆分 | 高 | 独立扩容各组件 | |
| 加消息队列削峰 | 中 | 平滑突发流量 | |
| 硬件升级 | 垂直扩容(加 CPU/内存) | 中 | 有上限(Amdahl 定律) |
| 水平扩容(加机器) | 高 | 线性提升(如果无共享瓶颈) |
如何验证调优效果(控制变量法)
📖 控制变量法三步走
- **固定环境:**同一台机器、同一份数据、同一份压测脚本
- **只改一个变量:**每次只改一处(如只加索引),不要同时改多处
- **多次测取均值:**至少跑 3 次取平均值,避免偶发因素干扰
示例
调优效果验证示例
优化项:给 orders 表的 user_id 加索引
测试条件(完全一致):
→ 100 并发、持续 5 分钟、阶梯式加压
→ 80% 查询 + 20% 写入
→ 测试数据:100 万条订单
优化前(3次平均):
→ QPS: 320 | P95: 1,200ms | P99: 3,500ms | 错误率: 0.8%
→ 慢查询数: 456/min | CPU: 45% | iowait: 35%
优化后(3次平均):
→ QPS: 1,150 | P95: 180ms | P99: 450ms | 错误率: 0.01%
→ 慢查询数: 2/min | CPU: 62% | iowait: 5%
结论:
→ QPS 提升 259%
→ P95 降低 85%
→ 慢查询减少 99.6%
→ iowait 从 35% 降到 5%(磁盘 IO 瓶颈消除)
→ CPU 从 45% 升到 62%(说明以前 CPU 在等 IO,现在真正干活了)课堂练习
练习:完成一次完整的调优验证
- 选择一个有数据库查询的接口
- 用
EXPLAIN检查是否有全表扫描 - 压测记录优化前的 QPS 和 P95(至少 3 次取均值)
- 加索引后,用完全相同的条件再压测 3 次
- 对比优化前后数据,计算改善百分比
测试数据管理
数据量要接近真实
⚠️ 测试数据量是最容易被忽略的"失真因素"
生产数据库 100 万条记录 → 测试环境只有 100 条 → 测试结果毫无意义。因为索引在小数据量下几乎不产生差异,分页在小数据量下不会触发全表扫描。
| 数据类型 | 推荐量级 | 说明 |
|---|---|---|
| 用户表 | 和生产同量级,或至少 10 万+ | 模拟真实的登录/查询场景 |
| 业务数据(订单等) | 至少 100 万条 | 大表查询的性能差异在百万级才体现 |
| 搜索索引 | 和生产同量级 | Elasticsearch/向量数据库的性能和数据量强相关 |
| 文件/图片 | 模拟真实大小 | 1KB 的假图片和 5MB 的真图片差别很大 |
数据脱敏方法
| 字段类型 | 脱敏方式 | 示例 |
|---|---|---|
| 姓名 | 随机中文名生成 | 张三 → 李明华 |
| 手机号 | 保留前 3 后 4 中间随机 | 138****1234 |
| 身份证 | 保留地区码,后面随机 | 110101199001011234 → 110101199503076789 |
| 邮箱 | 用户名随机化 | test_user_001@example.com |
| 地址 | 用模板生成 | XX省XX市XX区XX路XX号 |
| 密码 | 统一设为测试密码 | Test@123456 |
参数化数据准备
# 生成测试用户数据(Python 示例)
import json
import random
import string
users = []
for i in range(10000):
users.append({
"username": f"testuser_{i:05d}",
"password": "Test@123456",
"email": f"testuser_{i:05d}@example.com",
"phone": f"138{random.randint(10000000, 99999999)}"
})
with open("test-users.json", "w") as f:
json.dump(users, f, ensure_ascii=False, indent=2)
# 生成搜索关键词数据(覆盖各种长度和类型)
keywords = ["手机", "iPhone 15", "笔记本电脑", "机械键盘", "无线耳机",
"运动鞋 男", "连衣裙 夏季", "儿童书包", "厨房置物架", "蓝牙音箱"]数据隔离与清理
- **压测数据标记:**给压测创建的数据加前缀(如
perf_test_),压测后方便批量清理 - **使用独立数据库:**压测环境用独立的数据库实例,不和开发/测试环境共享
- **每轮压测前重置:**确保每次压测的初始数据状态一致,结果才可对比
- **自动化清理脚本:**压测结束后自动清理测试数据,避免积累
分布式压测
什么时候需要分布式压测?
📖 判断标准:压测机自己是不是瓶颈
如果压测时发现压测机本身的 CPU > 80%、内存不够、或网络带宽打满了 → 说明瓶颈在压测机,需要多台机器分布式发压。
各工具的分布式方案
| 工具 | 分布式方式 | 注意事项 |
|---|---|---|
| JMeter | Master-Slave 模式:一台 Master 控制多台 Slave | 需要配置 remote_hosts ,数据要分发到每台 Slave |
| k6 | k6 Cloud(付费)或自建 k6-operator(K8s) | 开源版单机就很强(Go 语言优势) |
| Locust | Master-Worker 模式: locust --master + locust --worker | Worker 可以在不同机器上启动 |
# Locust 分布式示例
# 主节点(控制台 + Web UI)
locust -f locustfile.py --master --expect-workers 3
# 工作节点1(另一台机器)
locust -f locustfile.py --worker --master-host 192.168.1.100
# 工作节点2
locust -f locustfile.py --worker --master-host 192.168.1.100
# 工作节点3
locust -f locustfile.py --worker --master-host 192.168.1.100# JMeter 分布式示例
# 1. 在每台 Slave 上启动 JMeter Server
cd apache-jmeter/bin
./jmeter-server
# 2. 在 Master 上配置 jmeter.properties
# remote_hosts=192.168.1.201,192.168.1.202,192.168.1.203
# 3. Master 启动远程测试
./jmeter -n -t test.jmx -r -l result.jtl
# -r 表示启用所有远程 hosts分布式压测注意事项
- **时间同步:**所有压测机的时钟要同步(NTP),否则数据对不上
- **数据分片:**参数化数据要按 Worker 分片,不能所有 Worker 用同一份(否则所有 Worker 用同一个用户登录)
- **结果汇总:**最终要把所有 Worker 的结果合并成一份总报告
- **网络一致:**所有压测机应该在同一网络,到被测服务的延迟要一致
- **压测机监控:**别忘了监控压测机本身,确认瓶颈不在压测端
测试脚本关键概念
思考时间(Think Time)
📖 思考时间 = 模拟真实用户在两次操作之间的停顿
真实用户浏览页面需要时间、阅读内容需要时间、填写表单需要时间。如果不加思考时间,压测结果会严重偏悲观。
| 场景 | 推荐思考时间 |
|---|---|
| 页面浏览 | 3~10 秒(用户在看内容) |
| 表单填写 | 10~30 秒(输入信息) |
| 搜索后浏览结果 | 2~5 秒 |
| API 自动化调用(无 UI) | 0~1 秒(模拟系统间调用) |
// k6 中加思考时间
import { sleep } from 'k6';
export default function () {
http.get('https://example.com/page');
sleep(Math.random() * 7 + 3); // 3~10 秒随机
http.get('https://example.com/next');
}
# Locust 中加思考时间
class MyUser(HttpUser):
wait_time = between(3, 10) # 每次 task 之间等 3~10 秒Ramp-up(加压策略)
📖 Ramp-up = 并发用户数如何从 0 增长到目标值
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 阶梯式 | 10 → 保持5分钟 → 50 → 保持5分钟 → ... | 找拐点(推荐) |
| 斜坡式 | 从 0 线性增长到 500(10分钟内) | 观察 RT 变化趋势 |
| 脉冲式 | 低 → 瞬间拉高 → 降低 → 再拉高 | 模拟秒杀/推送 |
| 恒定式 | 直接固定在目标并发 | 稳定性测试 |
// k6 阶梯式加压
export const options = {
stages: [
{ duration: '2m', target: 50 }, // 阶段1:加到50
{ duration: '5m', target: 50 }, // 保持50
{ duration: '2m', target: 200 }, // 阶段2:加到200
{ duration: '5m', target: 200 }, // 保持200
{ duration: '2m', target: 500 }, // 阶段3:加到500
{ duration: '5m', target: 500 }, // 保持500
{ duration: '3m', target: 0 }, // 冷却
],
};参数化(Parameterization)
💡 为什么需要参数化?
所有用户查同一个商品 ID → 缓存命中率 100% → RT 50ms。真实场景每个人查不同的商品 → 缓存命中率 30% → RT 可能 500ms。参数化让测试更接近真实。
// k6 参数化示例
import { SharedArray } from 'k6/data';
const products = new SharedArray('products', function () {
return JSON.parse(open('./product-ids.json'));
// [1001, 1002, 1003, ..., 9999]
});
export default function () {
const productId = products[Math.floor(Math.random() * products.length)];
http.get(`https://api.example.com/product/${productId}`);
}关联(Correlation)
从上一个响应中提取数据,传给下一个请求。典型场景:登录返回 Token → 后续请求带 Token。
// k6 关联示例
export default function () {
// Step 1: 登录,获取 token
const loginRes = http.post('https://api.example.com/login', JSON.stringify({
username: 'testuser', password: 'testpass',
}), { headers: { 'Content-Type': 'application/json' } });
const token = loginRes.json('data.token'); // 提取 token
// Step 2: 用 token 访问
http.get('https://api.example.com/profile', {
headers: { Authorization: `Bearer ${token}` },
});
}预热与冷却(Warm-up / Cool-down)
预热(前 1~2 分钟数据不计入结果):
- JIT 编译还没完成(Java/Go 前几秒性能偏低)
- 缓存还没建立(Redis/DB Buffer Pool 是冷的)
- 连接池还在预热 冷却(最后 1 分钟数据不计入结果):
- 正在减少并发,数据不稳定
- 有些请求是"尾巴",不代表稳态性能
测试报告模板
完整报告结构
TIP
一、测试概述
1.1 测试目的
1.2 测试范围
1.3 测试时间
1.4 测试环境配置
- 服务器配置(CPU/内存/磁盘/OS)
- 数据库配置
- 中间件配置
- 网络拓扑
1.5 测试工具及版本
1.6 测试数据说明
二、测试场景与策略
2.1 场景列表(场景描述 + 接口 + 流量占比)
2.2 加压策略(并发数 + 持续时间)
2.3 通过标准(QPS/RT/错误率的目标值)
三、测试结果
3.1 各场景汇总表
场景 | QPS | P50 | P95 | P99 | 错误率 | 是否达标
3.2 核心接口详细数据
3.3 阶梯式加压数据(并发 vs QPS vs RT 曲线图)
3.4 服务端资源使用(CPU/内存/磁盘IO/网络 曲线图)
3.5 数据库指标(连接数/慢查询/缓冲命中率)
四、性能拐点分析
4.1 拐点并发数
4.2 系统天花板
4.3 拐点时的资源瓶颈
五、稳定性测试结果
5.1 测试时长
5.2 内存变化曲线
5.3 GC 情况
5.4 异常事件
六、瓶颈分析
6.1 已发现的瓶颈
6.2 瓶颈证据(截图/日志/监控数据)
6.3 已执行的优化及效果
七、结论与建议
7.1 是否满足上线标准(逐条对照通过标准)
7.2 优化建议(优先级排序)
7.3 扩容建议
7.4 风险提示
八、附录
8.1 测试脚本
8.2 原始数据
8.3 监控截图如何写好"结论与建议"
💡 结论不是列数据,而是回答业务问题
示例
差的结论: "QPS 达到 2000,P95 为 450ms,错误率 0.1%。以上。" 好的结论:
结论:系统满足上线要求,建议按计划发布。
详细说明:
✅ QPS 达到 2,000(目标 1,500),有 33% 余量
✅ P95 = 450ms(目标 < 800ms),达标
✅ 错误率 0.1%(目标 < 0.5%),达标
✅ 8 小时稳定性测试无异常
但存在以下风险:
⚠️ 当并发超过 2,500 时,订单创建接口 P99 飙升到 5s
→ 原因:order 表 user_id+created_at 联合索引缺失
→ 建议:上线前加索引(预计提升 3 倍)
→ 优先级:高
⚠️ Redis 在高并发下内存增长较快(8 小时增长了 500MB)
→ 原因:部分缓存没设过期时间
→ 建议:所有缓存加 TTL
→ 优先级:中
扩容建议:
→ 如果日活超过 50 万,建议应用节点从 3 台扩到 5 台
→ 如果日活超过 100 万,建议数据库做读写分离课堂练习
练习:写一份完整的性能测试报告
- 按照上面的模板结构,为你最近做的一次压测撰写报告
- 重点练习"结论与建议"部分:不只列数据,要有分析
- 每条建议要包含:问题是什么 → 原因是什么 → 建议做什么 → 优先级
- 让一位不了解技术细节的人(如产品经理)看看,他能看懂结论吗?
补充参考答案要点
- 通用篇练习要能把性能问题分成客户端、服务端、数据库、中间件和网络几层来分析。
- 压测方案答案应包含流量模型、数据准备、监控指标和瓶颈定位步骤,而不是只写线程数。
- 如果要给出发布门禁,至少要说明核心 SLA、回归阈值和失败时的回滚标准。