使用Java-Perf进行性能调优提高tps
发表于 2025/12/05
0
背景
XXXX项目在线填报用例 tps 为 3.6/sec,不符合预期,希望进行调优。
在线填报用例分为预览 和 上报两个接口,其中预览接口的tps远大于 3.6,
瓶颈是上报接口。
50并发是上报接口tps最优的并发数, tps 为 5.6/sec,均耗时为 8.9s

单并发上报接口 tps 为 1/sec,平均耗时为 1s

并发高的情况下,上报接口耗时增多了将近8倍,但是 tps 提升的不多。各项系统资源使用率都不高,无瓶颈。
接下来的调优重点是分析接口耗时大幅度增多的原因,降低耗时,从而提升tps
1、采集热点火焰图 &锁火焰图
热点火焰图

热点函数 OnlineFillController.largeAmountReport 是报送接口的入口函数,
Jvm内部代码编译操作以及GC相关操作也属于热点操作
接下来可通过java调优工具分析下对应程序的线程状态、GC执行情况,对 largeAmountReport 进行插桩,具体分析其调用链的耗时。
锁火焰图

大部分锁是日志打印相关的锁,可通过java调优工具分析线程的锁情况。
2、分析GC活动

50并发

1并发

分析java工具采集的GC 活动数据,GC频率比较均匀,垃圾回收耗时也不高,耗时也没有随着并发的增高也明显变大。
3、分析线程状态&线程锁竞争情况
50并发

通过java工具在线分析-概览发现,在50并发压测下,程序80% 的线程会处于等待状态。
锁分析



通过锁图和统计分析,60个业务线程中有42个线程会因为竞争同一把锁而处于等待状态。
根据调用栈发现这把锁处于从druid数据库连接池中获取数据库连接的操作中。
可通过 java工具抓取应用的SQL执行情况;
分析 org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection 获取数据库连接的函数在低并发和高并发情况下的耗时。
4、分析SQL语句执行情况
1并发压测

50并发压测

通过java工具采集SQL执行情况,经分析耗时最长的一个SQL查询语句,50并发和单并发对比,耗时增加了0.4s 左右,其他SQL语句耗时基本没变。
对于1s 到9s的报送接口耗时倍增情况,执行SQL语句增加的耗时只能占小部分。
5、关键函数调用链耗时分析
DataSourceUtils.fetchConnection 函数
1并发

50并发

获取数据库连接操作的耗时,随着并发的提高耗时会明显增大,0.001 ms -> 589ms
该操作内部涉及锁竞争,说明随着并发的提高,锁竞争也会越激烈。
通过第四步的采集数据得知一次报送操纵会涉及9 条SQL语句的执行。
我们假定一次报送每条SQL执行一次,那么就会有从数据库连接池中拿9次数据库连接的动作,我们以1 并发和 50并发为例,(589-0.001)* 9 / 1000 = 5.3s,
50并发情况下,一次报送操作,光在获取数据库连接的耗时上就多了5.3s,
5.3s 对于整体的耗时占比比重也较大,约为 58%。
降低获取数据库连接的耗时,是个重要的调优方向。
分析数据库连接池参数

通过采集的数据得知,该应用druid数据库连接池的最大连接为8,它代表这个连接池最多创建8个数据库连接供应用使用。
那么在并发50的情况下,最多只有8个线程能拿到数据库连接去跟数据库做交互,其他的线程都得排队等待。这一点能跟第三步的分析对得上。
适当调高这个数据库连接数的上限,能同时拿到数据库连接的线程就会变多,排队等待的线程就会减少。理论上获取数据库连接的操作耗时也会响应减少。
接下来做验证。
6、调高数据库连接池的最大连接数进行验证
启动参数调整数据库连接数
通过启动参数-Dspring.datasource.druid.max-active 调整druid数据库连接池的最大连接数

压测
经过不断调整并发数和数据库连接池数作压测验证,60 并发 和 60 数据库连接数的组合下tps最佳,tps 从5.6 / sec 提高到 18 / sec,耗时也从 8.9s降低到3.2s
这个效果是很明显的。

线程状态
等待状态的线程比例从80% 降低到 44%

锁分析




有31个业务线程因等待获取锁而处于阻塞或等待状态,占比为50%,
但是等待锁变为了两把,而且都是跟lockback插件打印日志有关,不再是之前的获取数据库连接相关的锁
关键函数耗时
DataSourceUtils-fetchConnection:

获取数据库连接耗时从589ms 降低到 0.003ms,接近单并发的耗时。
SQL语句耗时
60并发 60 数据库连接:

60并发 8 数据库连接:

SQL语句的耗时比50并发的耗时明显增多
这不难理解,数据库连接变多,那么跟数据库的操作就会变得更频繁,数据的压力就会变大,
相关SQL执行时间变长也是比较正常的。
关键进程资源变化
报送业务进程
50并发 8 数据库连接:

60并发 60 数据库连接:

报送进程 cpu使用率从 35% 升高到94%
Mysql进程
50并发 8 数据库连接:

60并发 60 数据库连接:

mysql进程 cpu使用率 从 763% 升高到 2000%,压力明显增加
总结
通过第六步的验证结果及相关指标的变化,可以得出调高druid数据库连接池的最大连接数,
在并发高的情况下减轻业务线程获取数据库连接的压力,规避排队等待获取数据库连接的现象。
调优方案&优化结果
调高druid数据库连接池的最大连接数到60
报送接口tps从5.6 / sec 提高到 18 / sec,耗时也从 8.9s降低到3.2s
在线填报用例 tps从3.6 / sec 提高到 18 / sec
案例总结
1、 针对吞吐量调优(tps、qps等),如果随着并发的提高,发现吞吐量明线不是线性升高的趋势,可以怀疑是不是业务中有同步锁的操作。
2、 性能调优一定要从问题最大的点出发,这样调优后收益往往是最大的。
本案例来讲,报送接口单并发下耗时为1s,tps为 1 / sec,假设报送逻辑是并行的,互不影响,在理想(资源足够并且无其他干扰因素)情况下,5并发tps应该可以到5/ sec,10并发tps应该可以到10/ sec。但是经过压测50 并发 tps 最高只有5.6 / sec,缩水有点严重,可以怀疑是不是存在同步阻塞的操作,让并行变成了串行或者 其他的资源瓶颈导致的。还有一点 耗时 从 1s 增加到 8.9s,增大比例有点多。
从上述现象看,调优重点是 分析下锁看是否存在同步阻塞操作;分析下耗时增大明显的原因;分析是否出现资源瓶颈。


