目录
前言
工作中时不时会被指派去排查线上问题,线上问题大致分成两种:
一种是业务bug相关的,这种实际上没啥好讲的,无非就是程序运行结果不符合业务,一般都是看下相关日志,获取相关入参,在本地复现bug后修复即可。
另一种则是排查程序是否能稳定运行,即在程序能完成预期的功能前提下,保证程序能够有较快的响应速度,不会出现明显的卡顿以及延迟。一般出现延迟或者卡顿的情况,服务器CPU使用率通常都会异常飙高。
所以问题也就转换成了排查CPU为何飙高,以及应该如何解决。
CPU飙高原因
CPU使用率突然飙高,可以尝试用以下的思路逐步排查
- 流量激增
- 程序代码存在问题
- 针对Java程序,频繁Full GC
流量激增
如果是在短时间内访问流量突然变多,系统的并发量变大,从而导致CPU突然飙高,这种情况导致CPU飙高其实是正常的,很多情况下并不需要去做调整。因为这并不是程序本身有问题,而是突发的流量一时间超过了系统设计的阈值。
针对这种场景,解决的思路一般都是开源节流,削峰填谷
开源:即加机器,水平扩展应用程序
节流:即对接口进行限流,保证并发不会超过设计的阈值,防止程序崩溃
削峰填谷:使用消息队列作为一个缓冲层,将高峰期的请求先保存到消息队列中,然后由消费者按照自己的处理能力从队列中取出消息进行处理。
程序代码
如果是程序代码写的不好导致的CPU飙高,那么这个是必须要排查并且及时修复的。
代码中存在了死循环,反复初始化单例对象,一次性创建很多对象,创建大对象等等耗时的操作,都会导致程序在运行时CPU飙高。
具体情况还需要具体分析,在线上环境中,一般服务都是部署在Linux上,可以通过 top + jstack 来帮助定位问题代码
使用 top 命令来查看CPU使用情况
top
找到CPU占用最高的Java进程ID后,使用 top -Hp 来查看对应 Java 进程的线程详情
-H:在进程信息中显示线程详细信息。
-p <进程ID>:仅显示指定进程ID的信息。
top -Hp 进程ID
找到CPU占用最高的Java线程ID,将线程ID转换成16进制
printf '%x\n' 线程ID
得到线程ID十六进制后,使用 jstack + grep 输出该线程的详细栈信息
grep -A <显示行数>:除了显示符合范本样式的那一列之外,并显示该行之后的内容。
jstack Java进程ID | grep -A 200 Java线程ID十六进制
输出栈信息后,便可以分析是具体哪一行代码导致线程运行时间长了。
Full GC
针对 Java 程序,垃圾回收器频繁地进行Full GC,也会导致CPU飙升。
频繁 Full GC 的原因可能是:
应用程序存在内存泄漏,老年代有效空间变小,每次Full GC 后释放的空间也有限,所以频繁Full GC
方法运行中创建了很多大对象,新生代放不下,直接在老年代进行分配空间,导致老年代一下子就满了需要进行 Full GC,若是方法的并发量大,则 Full GC 会非常频繁。
在Linux服务器中,可以使用 jstate 来查看 Java 应用程序 GC 情况
jstat -gc Java进程ID
可以使用 jmap 来导出堆栈信息
jmap dump:format=b,file=dump.hprof Java进程ID
导出的hprof文件,可以使用JDK自带的JVM分析工具 jvisualvm 进行分析,排查是否发生内存泄漏,即查看堆中是否存在某些对象占用了特别大的空间。