背景
最近在进行接口服务优化,前面对处理大量数据的线程进行了优化。当优化后的版本部署到灰度服务器上时,原以为CPU占用率会明显地降低,然而事实却狠狠地打了脸,非但该服务的CPU占用及负载没有降低,反而远远差于上次优化之前版本及线上的服务。是优化方向错了,还是引入了新的问题?
现象
通过运维中心zabbix
面板查看该机房上所有的报价服务负载情况,新部署接口服务的负载情况如下图红线所示:
可以发现在上午11:00~14:00时间段内,该接口服务的负载明显高于其他几台服务。CPU利用率的情况与负载类似。
排查过程
一、比较用户数量
5个服务都部署在SZVPC机房,并且可以确定服务运行环境是相同的,那么首先需要确认是否是该服务用户量过大导致的。
登录CRT,通过netstat -antp|grep 'port'|wc -l
命令查看服务器上的实时用户连接数。对比该机房的5个报价服务发现,所有服务的实时用户连接数大体相等,不存在优化版本服务用户连接远大于其他几台服务的情况。到这里可以得出结论是新部署的服务本身有问题。
二、定位高CPU占用线程
1、在上一篇的CPU高负载排查过程中,我们定位到是组合和账户的数据处理线程CPU占用率高。那首先,可以确定下上次优化有没有解决这两个线程的CPU占用问题。
通过Arthas进入到当前服务进程,执行thread -n 5 -i 1000
命令来查看当前CPU占用最高的几个线程。分析堆栈信息,可以看到改完之后的服务组合账户线程的CPU占用率已经降低了,至少前5个线程里面不见他们的踪影。
2、排除了组合账户线程的影响,下一步就要定位到底是什么线程占用CPU最高。通过$ top
命令,查看CPU实时使用情况(注:图为后面补加的,数据可能有出入):
可以看出pid为26810的进程CPU占用率竟然达到了1000%,而这个进程就是接口服务的进程。
3、经典的命令行工具,如ps或top,都可以用来显示线程级别的信息,下面列举了这两种方法的使用。
方法一:通过$ ps -mp <pid> -o tid,THREAD,time
命令,查看线程列表。到这里确认tid为8592…的这三个线程CPU占用率最高,且占用CPU的时间也很长。
方法二:通过$ top -H -p <pid>
命令,查看线程列表,得出的结论与方法一一致。
4、定位到线程tid之后,就可以用jstack
命令查看线程具体的堆栈信息了,不过jstack
命令中线程id使用16进制,因而首先需要通过 $ echo "obase=16;<tid>"|bc
或 $ printf "%x\n" <tid>
命令将tid转为16进制。
以tid8592为例,转为16进制后为2190,再通过$ jstack <pid> |grep <tid> -C 20
命令查看该线程的堆栈信息。
三、分析
Gang worker是JVM 用于做新生代垃圾回收(monir gc)的一个线程,#号后面是线程编号。从线程的堆栈信息可以看出是JVM的GC线程一直在占用大量CPU.定位到垃圾收集器的问题,可以通过jstat -gcutil <pid> 1000 100
命令统计GC回收的情况(没发现有什么异常)。
对应的参数如下:
- S0:幸存1区当前使用比例
- S1:幸存2区当前使用比例
- E:伊甸园区使用比例
- O:老年代使用比例
- M:元数据区使用比例
- CCS:压缩使用比例
- YGC:年轻代垃圾回收次数
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
通过jinfo -flags <pid>
命令,查看该服务器上的JVM内存参数设置,看到了这一条配置:-Xmx12g -XX:MaxDirectMemorySize=18g
,原来之前的服务版本中已经不使用直接内存,而是全都使用堆内存,而这里JVM配置却没有改过来,由此推测是服务的内存分配不够用导致频繁GC,进而引起CPU负载过高。
四、后续
修改JVM的配置,将最大堆内存设置为-Xmx=24g
,重启服务运行一段时候后发现CPU负载迅速降了下来,并一直低于线上服务的负载。
结论
1 有关JVM调优相关的可以参考这一系列博客,写的很详尽:Java内存泄露分析系列。
2 这次问题解决也是凑巧去看了一眼JVM参数,本身GC回收具体什么地方占用了CPU,迫于时间关系实际上没有准确定位到,后面再研究吧。