性能基准测试:如何科学测量吞吐量
做优化,第一步不是动手改代码,而是搞清楚:你现在到底跑多快?
说白了,你得先有个基准。没有基准,你所谓的“优化”就是闭着眼睛开车。我个人习惯,任何性能优化项目开始前,至少跑三轮基准测试,取稳定值。这不是走形式,是为了后面你能理直气壮地说:“改完之后,吞吐量提升了XX%”。
什么是真正的“科学测量”?
很多人拿个压测工具,往服务器上怼一下,看到每秒请求数(QPS)出来了,就觉得完事了。嗯,这里要注意,你测出来的那个数字,可能非常不靠谱。
我在项目中遇到过一件事。有次一个同事说SDK吞吐量是 5000 TPS。我看了下他的测试环境——客户端和服务端部署在同一台机器上,连网络开销都省了。这不叫测试,这叫自欺欺人。
科学的测量,必须满足三个条件:隔离性、可重复性、统计有效性。
基准测试三要素
- 隔离性:被测SDK独占环境,不受其他进程干扰。
- 可重复性:同一条件下,多次测试结果波动不超过5%。
- 统计有效性:报告P50、P99、P999延迟,而不是只报平均值。
关键指标:你到底该看什么?
吞吐量这个词,其实是被用滥了的。它至少有三种不同含义:
| 指标 | 定义 | 适用场景 | 常见误区 |
|---|---|---|---|
| QPS(每秒查询数) | 单位时间完成的请求数 | 简单请求、无状态服务 | 忽略了请求复杂度的差异 |
| TPS(每秒事务数) | 单位时间完成的事务数 | 包含多个操作的事务场景 | 把单操作当成事务来算 |
| RPS(每秒请求数) | 客户端视角的请求吞吐量 | 负载均衡、网关层 | 不区分成功与失败请求 |
我个人更关注 RPS + P99 延迟的组合。 你想想看,光看 QPS 高没用,如果 P99 延迟从 20ms 飙到 500ms,用户体验直接崩了。我习惯把这两个指标画在同一个图表里,一眼就能看出系统在高压下的健康状况。
经验之谈:我见过有人把 QPS 从 1000 优化到 8000,结果 P99 延迟从 30ms 涨到了 2000ms。这叫优化吗?这叫把用户逼疯。
测试方法论:预热、稳态与斜坡
很多初级工程师直接开测,上来就上最大并发,结果数据出来一塌糊涂。为什么?因为 JIT、缓存池、连接池都还没准备好。
我得强调一下:预热是基准测试的前提条件。
我曾经在一个金融项目中犯过这个错。SDK 启动后直接压测,前 30 秒数据极差,但后面越来越好。如果我没做预热,就会误以为这个 SDK 只能扛 300 TPS。实际上,预热完成后它能扛 3000 TPS。差了一个数量级。
警告:不要在预热阶段就记录数据。预热数据必须丢弃。
判断预热完成的方式:连续 30 秒内,吞吐量波动幅度小于 3%。
再说说 斜坡测试(Ramp-up)。別一下子把并发扔上去。你可以像温水煮青蛙一样,慢慢增加压力。我一般设置 10 秒到 30 秒的斜坡时间,看吞吐量如何随并发变化。这能帮你找到系统的“拐点”——一旦超过这个点,延迟暴涨,吞吐量反而下降。
工具选型与实战配置
工欲善其事,必先利其器。我这里不吹哪个最好,只说说我怎么挑的。
| 工具 | 优势 | 劣势 | 我的推荐场景 |
|---|---|---|---|
| wrk | 轻量、高性能、支持 Lua 脚本 | 只能压 HTTP,功能单一 | 快速验证 SDK 的纯网络吞吐 |
| JMeter | 功能全面,支持各种协议 | 资源消耗大,配置繁琐 | 复杂业务流程压测 |
| k6 | 脚本化、CI 友好、开源 | 学习曲线中等,插件生态一般 | 自动化回归基准测试 |
我个人习惯在本地先用 wrk 跑一组快速基准,确定大致的吞吐上限。然后再用 k6 集成到 CI 流程里,让每次提交代码后自动跑基准测试,防止性能回退。
一个典型的 wrk 命令
wrk -t12 -c400 -d30s --latency http://localhost:8080/api
参数说明:12 线程,400 连接,持续 30 秒,输出延迟分布。
数据记录与报告规范
跑完测试不是结束,是开始。你得把数据记录下来。我见过有人测完就关终端,下次想对比,什么都找不到。
我建议每个测试结果都保存为结构化的 JSON 文件,包含以下字段:
- 测试时间戳和唯一 ID
- SDK 版本号或 Git Commit ID
- 环境配置(CPU、内存、JVM 参数等)
- 所有延迟分位值(P50、P90、P99、P99.9)
- 总请求数、失败请求数、错误率
这样,如果有人问你“为什么这次比上次慢了”,你能迅速定位出是代码改了还是环境变了。
一个完整的流程模板
下面是我在项目中总结出来的标准操作流程。你照着做,基本不会出差错:
- 准备环境:独享机器,关闭无关服务,固定 CPU 频率。
- 预热:跑 60 秒预压,丢弃结果。
- 斜坡加载:从 100 并发开始,每 10 秒增加 100,直到吞吐量不再增长。
- 稳态测试:在拐点附近,保持并发 3 分钟,记录数据。
- 重复验证:至少跑 3 轮,取中位数作为基准值。
- 保存报告:将 JSON 结果归档,标注当前分支和测试目的。
避坑指南:我曾经在 JDK 版本不同时跑出巨大差异。同样的代码,JDK 8 和 JDK 17 吞吐量差了 20%。记得在报告中写明 JDK 版本和 GC 策略。
本章知识体系
下面这张图,把上面讲的内容做了个串联。你对着它,就能理清整个基准测试的核心逻辑。