性能问题排查顺序:
客户端并发量是否足够大->tomcat线程池是否占满->cpu利用率是否急剧增高->GC频率是否急剧增高->是否存在慢SQL->数据库连接池是否占满
工具:
- Jmeter
- VisualVM
- IntelliJ Profiler
- Driud Monitor
1 客户端并发量是否足够大
如果 Number of Threads 设置的太小或者 Ramp-up period 设置的太大都会导致每秒只有很少的线程真正开始发送,表现为tomcat线程池无法充分利用且吞吐率低
2 Tomcat 线程池是否占满
java线程池配置
场景1:
处理请求时业务线程(exec-n结尾的)始终保持10个没有增长,说明线程池未充分利用
visualvm默认每隔200ms的采样中未采样到业务线程的running状态,说明每次请求执行时间都很短未被采样到
场景2:
处理请求时业务线程(exec-n结尾的)由10个增长到200个,说明线程池被充分利用了
visualvm默认每隔200ms的采样中采样到了业务线程的running状态,说明有任务执行时间大约在 200ms 甚至更长
场景3:
- JMeter 配置:2s 内起 5000 并发(等于瞬间洪峰)。
- 线程池:exec 从 10 涨到 200。
-
- 160 个线程同时 Running(业务逻辑正在跑)。
- 30 个线程同时 Monitor(说明在等锁/竞争共享资源)。
- 5 秒后 exec 又涨到 205(说明线程池仍在扩容、任务没消化完)。
- 错误全是
java.net.SocketException: Socket closed。
Tomcat有连接池和最大并发保护,达到上限后,新的连接请求要么进入 acceptCount 队列,要么直接被拒绝,此时需调大server.tomcat.threads.max 。
出现 Monitor 状态可能是:容器队列 server.tomcat.accept-count(默认 100)也满了。
出现Monitor也可能是因为业务逻辑里有全局锁(比如 synchronized、数据库连接池锁、缓存锁),在高并发下大量线程卡在锁上。
场景4:
- JMeter 配置: 5s内启动 10000 个线程。
- 后端线程池:从 10 涨到 200(说明后端在扩容处理请求)。
- 线程状态:出现 Running(执行)。
- 错误率:16%
服务端直接拒绝某些请求,说明服务端进程可用的文件描述符不够(ulimit -n)或服务端 backlog(半连接队列)太小
也可能是客户端单机 JMeter 发 1w 并发本身就到极限了
3 CPU 利用率是否急剧增高,GC 频率是否急剧增高
cpu急剧增高和GC频繁的原因是每次登录成功都将yaml文件加载到内存一次(导致GC频繁),并进行解析(导致CPU增高),将初始化代码放到全局参数中(private final Parser uaParser = new Parser();)后效果如下:
4 是否存在慢 SQL
配置参数
# 打印执行超过 10ms 的 SQL(可根据情况调整,比如 50ms)
spring.datasource.dynamic.druid.stat.slow-sql-millis=10
# 是否将慢 SQL 打印到日志中
spring.datasource.dynamic.druid.stat.log-slow-sql=true
如果出现慢SQl则进行相应调优即可,如分区、增加索引命中率等
5 数据库连接池是否占满
上图未充分利用数据库连接池
上图充分利用了线程池
当数据库是瓶颈时,可适当调大数据最大连接数
show max_connections;
ALTER SYSTEM SET max_connections = 500;