性能测试除了QPS,还有哪些被忽视的致命指标?
先说个真实项目。去年帮一个电商团队做压测,他们之前的报告清一色写着“峰值TPS 5200,平均响应时间220ms,通过”。但上线后双11大促,凌晨库存接口直接卡死,监控一看TP99从300ms飙到4.8s,而平均响应时间才1.2s。后来查原因是数据库连接池在低并发下没事,一到500并发就开始排队,JVM老年代GC从每秒2次变成每秒18次。他们之前只测了平均RT,没看分位数和GC日志,等于白测。
性能测试最容易被带偏的一点就是拿QPS当唯一KPI。QPS高不代表系统健康,完全可以用大量线程把CPU烧到90%、队列堆满请求,硬撑出一个好看的吞吐率。真正该做的是把吞吐率、响应时间、资源利用率三个维度叠起来看。比如同一个接口,压测机从50并发涨到200并发,TPS从3000涨到8000,但CPU使用率从45%直接蹦到97%,内存占用从1.2G涨到3.7G,这时候继续加压TPS可能到8500就上不去了,说明瓶颈已经出现。正确做法是在CPU达到80%时记下此时的TPS和延迟,那才是真实容量边界。
再一个容易被忽略的是延迟分位数,特别是P99和P99.9。平均响应时间是个烟雾弹,线上用户感知的是最慢的那几次请求。我习惯用三个数对比:P50、P95、P99。如果P50是80ms,P95是350ms,P99已经1.2s,说明系统里有严重的尾延迟,可能是某个依赖服务超时重试,或者线程池排队。压测时不仅记录分位数,还要画一张时间序列图,看P99是不是在某一刻突然跳变——如果是,多半是触发了缓存穿透、锁竞争或者full GC。拿Redis举例,从缓存的key过期到回源数据库那几秒,P99能瞬间翻5倍,但这个现象在聚合报告里完全看不出来。
还有个更反直觉的维度:TCP重传率和连接队列溢出。大多数性能测试只关注应用层,忽略网络栈。我用Wireshark抓包做个对比,正常压测1000并发下TCP重传率低于0.2%,同时ss -lnt看Send-Q和Recv-Q,如果Recv-Q长期大于0,说明应用accept不够快,连接在堆积。另外要观察Linux系统的time_wait状态数量,短连接压测一旦time_wait超过几万,即使系统有很高吞吐率,新连接也会因为端口耗尽而失败。这些指标不像TPS那么直观,但生产故障十有八九是先坏在传输层。
压测工具本身也是坑。JMeter聚合报告的“错误率”和“吞吐量”实际上是按采样线程算的,如果你用了异步请求,JMeter可能误判超时。我建议至少同时用两个工具交叉验证:JMeter跑业务场景,Grafana + Prometheus看系统指标,再配合Arthas实时看方法级调用耗时。之前有个案例,压测显示平均响应时间没变,但Arthas看到某个SQL执行了3.2s——原来是连接池里的空闲连接被MySQL8小时踢掉后,重连逻辑没做快速失败,全靠超时兜底。这种问题只能靠链路追踪的span耗时分布来定位。
最后说说容量评估的对比方法。不要只做单接口压测,一定要做混合场景。比如用户登录占了30%流量,商品查询占50%,下单占20%,那么压测时也要按这个比例跑。用阶梯加压法:从100并发起步,每5分钟加50,观察所有指标的变化曲线。重点看两个拐点:第一个拐点是TPS增长开始放缓,第二个拐点是延迟开始急剧上升。记录这两个点之间的资源利用率、线程数、待处理队列长度,这才是系统真正的弹性区间。我自己的经验是,如果第二个拐点的P99延迟超过第一个拐点的3倍,那就别想着靠自动扩容解决了,得先优化代码。
说到底,性能测试不是跑出一个“通过”的报告,而是找到系统在什么条件下会突然变慢、变失败。指标之间要交叉对比,比如吞吐率和CPU缩放曲线、延迟分位数和GC频率、连接数和time_wait数量。下次压测别只盯着QPS那个数字了,多敲几行命令看看ss -s、vmstat 1、jstat -gcutil,说不定能救回下一个凌晨三点的线上事故。