背景
公司新迭代了一个版本, 但是上线没多久,cpu就跑满,导致所有的请求都响应很慢, 大多数请求都超时,甚至有一次OOM。出现这样的情况,以前的负责人就叫运维重启。现在这个项目交给我来负责, 我需要把导致这个现象的原因找出来,并解决掉。
前负责人说怀疑是首页接口的问题,看了代码,还真是。
吐槽
公司使用docker运行spring boot项目, docker运行的系统和jdk是非常干净的
linux: Alpine Linux 3.4
jdk: openjdk version "1.8.0_111-internal" (build 1.8.0_111-internal-alpine-r0-b14)
这个版本的linux有些命令是执行不了的,这个版本的jdk同样也没有jps jstack等命令。
要求运维在测试环境给换套系统
linux: CentOS release 6.7 (Final)
jdk: openjdk version "1.8.0_91" (build 1.8.0_91-b14)
OK, 到此,准备工作做好了。
分析
在方法中新建了一个线程池,并且在方法中没有关闭。这样会导致每个请求都会创建一个线程池,然后创建几个线程,运行完了后,线程就会处于等待状态,GC回收不了。这是一个很严重的BUG!!!
接下来压测,并使用jprofiler监测JVM运行状况, 拿到结果。
等待中心的线程达到了15679个,其它状态的线程都不够其零头。
其中有一个线程一直在耗CPU。另外说下,测试服务器是一个2核的cpu,但是只有一个核是跑满的, 这说cpu的利用率不均匀(有些cpu很忙,有些cpu并不是),这表明只有一条线程在努力的跑。
如果是多核cpu出现利用率不均匀的情况,那说明大多数计算工作都是由一小组线程完成的,应用程序并没有利用其它的处理器。通常有这几种原因:I/O密集、外部限制、锁竟争。
回到主话题,在docker运行的linux中使用命令找到jvm的pid,然后使用top -H -p {pid}找出耗cpu的线程。或者使用ps -mp {pid} -o THREAD,tid,time|sort -k1 -nr|head -15命令也可以。
打到jvm的pid为6
top -H -p 6
或
ps -mp 6 -o THREAD,tid,time|sort -k1 -nr|head -15
发现在耗cpu的线程id为25.
先使用jstack把线程的堆栈信息统计到文件中
jstack 6 >data.txt
运行命令将10进制转换为16进制
printf "0x%x\n" 25
输出 0x19
在data.txt中找到nid=0x19的线程。
也可以使用如下命令
jstack 6 | grep nid=0x19 -A 30
这行命令会找到nid=0x19的行,并将其下30输出打印出来
发现是metrics监控的线程,根据上面的运行栈去找
发现metrics有一个定时任务,是检查线程状态的,一万五千多个线程,六个状态,就是循环将近十万次 隔60秒循环一次,确实是很耗cpu了。
问题定位到,找到原因,将线程池从方法中移出来,放到类变量中, 初步解决。
思考
首页竟然没用缓存,这是** !
后面还要对首页进行优化,使用缓存和管理缓存,不从库里取,不用线程池。