解决了CPU飙升的问题,我终于可以安心睡觉了

10,787 阅读3分钟

问题发现

事情是这样的,最近小码仔负责的项目预定今天凌晨2点上进行版本更新。前几天测试小姐姐对网站进行压力测试,观察服务的CPU、内存、load、RT、QPS等各种指标。

在压测的过程中,测试小姐姐发现我们其中一个接口,在QPS上升到400以后,CPU利用率急剧升高。

在这里我不再对CPU内存loadRTQPS等做过多赘述,毕竟这几个点中的任何一个拿出来探讨,一篇文章都不一定写的完。有兴趣深究的小可爱就自己动手查一下吧。

这里我仅对QPSCPU利用率做简单的概述。

QPS每秒查询率,QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。QPS越高说明,在特定时间内服务器所处理的流量越大。

CPU利用率是来描述CPU的使用情况,它表明了一段时间内CPU被占用的情况。使用率越高,说明机器在这个时间上运行了很多程序,反之较少。

了解了这两个基本的点,接下来就来一起看看我具体是怎么定位的吧。

问题定位

测试小姐姐反馈这个问题后,我登录到服务器,做了以下操作:

1.定位进程

登录服务器,执行top命令,查看CPU占用情况:

$top
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
44529 root 20 0 4289m 874m 13312 S 123.0 10.9 10:39.73 java

top命令查看所有进程占系统CPU的排序,它是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。

我们来看一下这些指示符代表什么:

    PID — 进程id
USER — 进程所有者
PR — 进程优先级
NI — nice值。负值表示高优先级,正值表示低优先级
VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb
S — 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比
TIME+ — 进程使用的CPU时间总计,单位1/100秒
COMMAND — 进程名称(命令名/命令行)

通过执行top命令,我看到,进程ID为44529的Java进程的CPU占用率达到了123%,基本可以定位到是我们的Java应用导致整个服务器的CPU占用率飙升。

2.定位线程

当然,Java是单进程多线程的,那么,接下来我就看了这个PID=44529的这个Java进程中的各个线程的CPU使用情况,同样是用top命令:

$top -Hp 44529
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
44580 root 20 0 4289m 874m 13312 S 16.2 10.9 3:02.96 java

通过执行top -Hp 44529命令,我发现,在44529这个进程中,ID为44580的线程占用CPU最高。

3.定位代码

通过top命令,我定位到了导致CPU使用率较高的具体线程, 接下来就要看一下具体是哪一行代码存在问题。

首先,我把44580这个线程转成16进制:

$printf %x 44580
ae24

接下来,根据该16进制值去打印的堆栈日志内查询,查看该线程所驻留的方法位置。并通过jstack命令,查看栈信息:

$jstack 44529 |grep -A 200 ae24
"System Clock" #28 daemon prio=5 os_prio=0 tid=0x00007efc19e8e800 nid=0xae24 waiting on condition [0x00007efbe0d91000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrentC.TimeUnit.sleep(TimeUnit.java:386)
at com.*.order.Controller.OrderController.detail(OrderController.java:37) //业务代码阻塞点

通过以上代码,我们可以清楚的看到,OrderController.java的第37行是有可能存在问题的。

然后我找到项目中OrderController.java的第37行,仔细分析发现由于自己的粗心,代码存在逻辑上的漏洞。修改重构之后,问题解决。

总结

以上就是本次问题排查的整个过程。主要用到的有:topprintfjstack等指令。

  1. 使用top指令查看当前占用CPU较高的进程PID;

  2. 查看当前进程消耗资源的线程PID:    top -Hp PID

  3. 将线程PID转为16进制,根据该16进制值去打印的堆栈日志内查询,查看该线程所驻留的方法位置。

  4. 通过jstack命令,查看栈信息,这步基本上已经能够定位大多数问题了。

碎碎念

项目今早凌晨两点上线,昨晚通宵加班,早上回到家刚睡两个小时,隔壁邻居开始装修,钻墙、砸墙,劈里啪啦、咕咕咚咚,被吵得实在无法入睡(感觉已在猝死的边缘试探)。于是起床写下这篇文章,也算是记录2020年第一次加班吧。

最后,感谢各位的阅读。文章的目的是记录和分享,若文中出现明显纰漏也欢迎指出,我们一起在探讨中学习。不胜感激 !

如果你觉得本文对你有用,那就给点个赞吧,你的鼓励和支持是我前进路上的动力~

欢迎关注我的微信公众号【小码仔】,我们一起探讨代码与人生。