性能测试别只盯着TPS:从抖动指数到稳定吞吐域的五个反直觉洞察

🔑 关键词:性能测试,TPS,抖动指数,稳定吞吐域,压测工具

📖 摘要:基于真实压测经验,剖析TPS指标失效、压测工具干扰、采样粒度等性能测试中常见但被忽视的问题,提出抖动指数与稳定吞吐域的新评估思路。

上个月帮朋友看一个订单服务的压测报告,报告上TPS稳定在3800,平均响应时间38ms,P95也才112ms。他们拿着这个纸面成绩去备战大促,结果线上流量只有压测的40%时就开始毛刺,某次促销直接导致下单接口超时报警。我拉原始日志一算,99.9%响应时间竟然飙到860ms,连接重置每秒有几百次。问题就出在大家都只盯着TPS和平均响应时间,完全忽视了响应时间在短窗口里的分布形态。后来我们用100ms滑动窗口算出一个'抖动指数',发现这个指数在压测进行到第12分钟时突然跳了3倍,那个时间点正好是JIT优化触发了一个冷门分支,之前完全没人注意。

图片

更隐蔽的干扰来自压测工具本身。我用JMeter和Gatling对同一接口做对比,JMeter用300线程跑,TPS曲线像锯齿一样一高一低,而Gatling用异步模拟同样300并发,TPS曲线平滑得多,最终测出来的饱和吞吐量差了2.8倍。很多人说压测工具都差不多,这不是傻就是懒——因为纯线程模型下每个用户线程要阻塞等待响应,线程切换和socket读写会制造出周期性的请求空档,这种空档在目标服务器上表现为GC频率的周期性波动,测出来的结果天然带了工具的指纹。真正该做的是验证请求到达间隔是否符合泊松分布或实际线上流量模型,而不是一上来就铺开1000台压测机。如果被测系统对请求到达模式敏感,比如有缓存过期策略或线程池队列,那么选择异步工具比如Gatling或者k6,再配合自定义的到达间隔脚本,得到的数据才具备参考价值。

图片

所以我越来越怀疑性能测试界主流的'找最大TPS'思路,它把压测搞成了数字竞赛。我自己的做法是先定义一个'稳定吞吐域':在响应时间P99不超过某个约束(比如SLA要求300ms)的前提下,系统能连续稳定运行30分钟的吞吐量范围。这个域的下限用于容量评估,上限用于熔断设计。为了量化抖动,我用滑动窗口内P99与中位数的比值,加上CPU调度延迟的标准差,合成一个'抖动指数'。比如上次压测,吞吐量从1400涨到1900时抖动指数从1.2跳到4.7,虽然平均响应时间只多了20ms,但线上体验早就崩了。这套方法不是我的发明,但真正用的团队特别少,因为大家被各种报表工具惯坏了,只想看一个绿色通过的图标,不愿意自己写两条SQL。

图片

操作层的问题同样致命。第一是预热时间不够,Spring Boot应用在JIT完全编译和数据库连接池填满之前,性能和稳定期差很多。我跑过的新服务,不预热直接满压,TPS长期在1500上下,预热9分钟后再测,稳定在2600,而且GC频率低了一倍。所以建议预热时间至少占正式压测时长的三分之一,最少别低于5分钟。第二是采样粒度,很多工具默认按1秒聚合TPS和响应时间,这个粒度会把持续500ms的雪崩抹平成一条漂亮的曲线,必须改成100ms甚至50ms的直方图。第三是压测环境跟生产配置不一致,常见的是数据库连接池只有生产的五分之一,导致压测瓶颈出现在连接池获取线程上,测出来的数据对容量规划毫无意义。我见过所谓全链路压测,前置机上的中间件版本比生产新两个版本,压了一整天结论就是——系统扩容到三倍就能扛住,结果真正上线时数据库主从延迟把整个链路拖死了。

图片

说了这么多,不是否定性能测试,而是觉得现在的性能测试被'报告好看'绑架了。工具只是探针,探针本身的材质会影响读数。真正有价值的输出不是一个最高TPS数字,而是一张'系统在什么情况下会变脆'的地图。所以我建议团队里每次压测都必须附带三个东西:抖动指数的变化曲线、请求到达间隔的一致性验证结果、以及异常时段的线程栈快照。如果报告里没有这些,那它只是一张Excel涂鸦,连PPT都算不上。

图片