压力测试TPS/QPS不达标是系统容量在某个步骤遇到了短板。要解决这个问题不能靠盲猜调参,需要系统化地逐层排查。
一、快速自查压测本身是不是合理
在怀疑系统前,先排除压测工具的干扰,避免测不准。
压测机资源是不是打满:单台施压机受限于带宽、CPU和端口数,如果CPU使用率超过90%或网络流量接近上限,增加并发产生的不是真实吞吐。这时需增加压测机数量或将工具改为分布式运行。
连接和超时配置:检查压测客户端的HTTP连接池大小、连接超时、响应超时是不是过短,导致请求堆积在客户端而不是服务端。
模拟是不是真实:如果压测脚本请求参数过于单一,所有请求都命中一条缓存数据,测出的TPS看似很高但无法代表真实场景,也可能导致缓存命中率高假象,业务数据读盘就会垮掉。必须使用数据参数化模拟真实分布。
二、定位问题从资源到代码
确定压测本身无问题就沿着网络-主机资源-应用服务-中间件-数据库这条链路排查。
1. 网络和带宽问题
现象:TPS达到一定值后完全平直,增加并发也不上升,且服务端CPU、内存使用率都很低。
排查:检查压测机和服务器之间的带宽,或后端服务间的内网带宽是不是占满。云服务器一般有带宽上限(如5Mbps),计算:1000个100KB响应体请求,理论QPS上限仅(带宽×1024/8)/ 100 ≈ 6.4,远远拖垮目标。
优化:升级带宽,开启Gzip压缩缩小响应体,使用CDN分流静态资源,减少不必要的大报文传输。
2. 服务端硬件资源问题
通过 top、vmstat、dstat、云监控等观察四个标准:
CPU:
CPU使用率高(>85%):一般是代码思路复杂、序列化开销大、频繁GC、正则一致过多等。需用Profiler工具定位热点方法。
CPU使用率低但TPS上不去:问题多数在阻塞等待(等待锁、IO、数据库响应),线程都处于BLOCKED或WAITING状态,CPU自然空闲。
内存:
内存不足导致频繁GC,尤其是Full GC耗时数秒,会暂停所有用户线程,TPS瞬间跌零,出现毛刺。需检查JVM堆配置和内存泄漏。
磁盘IO:
磁盘IO高(util接近100%)会导致写日志、读配置、数据库落盘都变慢。如果应用日志级别为DEBUG或数据库随机IO过多,TPS必受影响。
网络连接数和端口:
服务端单机端口范围有限(约28000+),高并发短连接下可能导致TIME_WAIT堆积,新连接无法建立。需优化为长连接或调整内核参数。
3. 应用服务层问题
线程池/连接池配置过小:
Web容器(Tomcat/Jetty)最大工作线程数、数据库连接池、Redis连接池、HTTP客户端连接池等,任何一个配置小于并发量都会让请求排队等待。
测试:看线程dump,如果大量线程在 getConnection() 等待,即数据库连接池不够。
锁竞争严重:
同步块过大、全局锁、分布式锁等待超时,导致大量线程串行执行。
现象:TPS无法随并发数线性增长,大量线程处于BLOCKED状态。
阻塞式IO或长耗时外部调用:
一个请求内同步调用了多个外部HTTP接口、发送邮件等,耗尽容器线程。必须用异步化、MQ解耦。
不合理的超时和重试:
超时设得过长,线程被慢请求长时间占用;重试次数过多,下游一慢导致上游雪崩。
4. 数据库层问题
数据库一般是最后一道关,也是最大的问题。
慢SQL和缺失索引:
explain 查看执行计划,是不是全表扫描、索引未命中、临时表排序过大。
高并发下,一条慢SQL每秒被执行上百次就会拖垮整个库。
连接数不足:
数据库最大连接数有限(如MySQL默认151),连接池总连接数超过上限或配置过低都会阻塞。
锁冲突:
行锁等待:大量更新同一条记录(如扣减库存),或间隙锁导致插入阻塞。要分析SHOW ENGINE INNODB STATUS,优化事务模型,将热点行分散或使用队列串行化。
事务范围过大:
把RPC调用、文件操作置于事务内,导致长事务锁定资源,阻塞后续事务。
缓存无法扛量:
没有缓存或缓存透过,全部请求直接打到数据库,数据库很快CPU或磁盘IO打满。
5. 中间件和架构问题
负载不均衡:各服务器压力差别大,某个实例先达到短板。
消息队列积压:消费者处理能力不足,消息堆积,TPS体现在异步链路上减弱。
缓存击穿/雪崩:热点Key失效瞬间,大量请求涌向数据库。
序列化/反序列化:使用了低效序列化协议(如Java原生序列化),或JSON反序列化大对象耗时高。
三、系统优化方法
1. 应用层优化
扩容线程池/连接池:根据实测并发数调整Tomcat最大线程数(如从200扩至500)、数据库连接池(如HikariCP的maximumPoolSize)。但要结合硬件,避免过高导致上下文切换剧增。
异步化改造:对于非即时响应必须完成的操作(发短信、记录日志、推送),使用MQ异步处理,缩短主链路响应时间,释放线程。
减少锁粒度:用ConcurrentHashMap替代Hashtable,用分段锁,或在数据库方面用乐观锁替代悲观锁。
本地/分布式缓存:热点数据使用Caffeine、Redis缓存,设置合理的淘汰时间和预热方法。
代码级微优化:避免大循环中字符串+拼接,降低序列化开销(使用Protobuf),使用连接池化复用。
2. 数据库优化
SQL优化和索引:这是性价比最高的,一个缺少索引的SQL优化后QPS可能提升百倍。
读写分离:主库写,从库读,分摊压力。
分库分表:单表数据量过大或写入热点时,横向拆分。
批量操作:将单条插入改为批量插入,减少和数据库的交互次数。
调整事务隔离级别:在保证业务正确情况下,读多写少场景可以用读提交,降低锁持有。
3. 系统和JVM层调优
JVM参数:选择合适的垃圾回收器(如G1),调大新生代,减少Full GC。合理设置-Xmx和-Xms。
内核参数:调整tcp_tw_reuse、tcp_fin_timeout快速回收TIME_WAIT连接;增大file-max和用户进程文件句柄数限制。
启用压缩:Nginx/网关开启Gzip,缩小网络传输量。
4. 架构扩展
水平扩容:增加服务节点,通过负载均衡分摊流量,这是提升容量上限的方法。
服务隔离:重要服务独立部署,避免被非重要服务拖累。
限流和熔断:防止突发流量打垮系统,保护下游。
四、定位工具
全局监控(Grafana+Prometheus/云监控):先看四大资源(CPU、内存、网络、磁盘),确定资源层是不是已到顶。
应用层剖析(Arthas/JProfiler/strace):CPU高则用thread -n 3看线程栈,抓热点方法;CPU低则dump线程,分析线程状态(WAITING还是BLOCKED),看他们在等什么锁或IO。
数据库审计(慢查询日志、performance_schema):抓Top慢SQL,分析执行计划和锁等待。
链路追踪(SkyWalking/Pinpoint):看一次请求耗时分布,定位是哪个服务或SQL慢。
性能优化是一个循环:设定目标-压测-监控定位-优化调整-回归压测。每次只改一个配置或代码,测试效果,不断逼近目标值。