使用shell脚本快速定位Linux服务器CPU突然飙高问题

使用shell脚本快速定位Linux服务器CPU突然飙高问题

问题描述

一般情况下,都是由用户发现反馈或者是CPU监控报警或者是运维反馈服务器CPU过高的问题,CPU突然飙高一般都是由于例如某个活动开始,流量突然飙升或者是程序bug导致,但是这种常见问题,我们总不能每次遇到都走『重复的路』来排查吧?我们要提高效率,站在这个角度来考虑,所以今天发篇文章总结一下。

原因分析

  1. 内存消耗过大,导致Full GC次数过多,比如大对象;
  2. 代码中有大量消耗CPU的操作,导致CPU过高,系统运行缓慢,比如某些复杂算法,甚至算法BUG,无限循环递归等等。;
  3. 由于锁使用不当,导致死锁。
  4. 随机出现大量线程访问接口缓慢。
  5. 某个线程由于某种原因而进入WAITING状态,此时该功能整体不可用,但是无法复现;

大体解决思路

在这里插入图片描述
如图,是常见的普遍情况以及排查问题的大体思路。
总结就是:先解决问题再去定位问题,这是线上事故处理的第一原则,在没有脚本之前,我们先将服务进行重启,保证服务的可用,然后增加一台服务器,便于留下当时的『犯罪』记录;有脚本之后,如果遇到线上cpu飙高问题,我们先多次执行该脚本以便留下当时的『犯罪』记录,然后再重启或回滚服务,后面再去分析该脚本生成的文件来定位问题。
我们一般会通过如下步骤来定位该问题:

  1. 找到cpu占比高的Java进程ID,通过这一步就知道是哪个Java应用出了问题。

执行“top”命令:查看所有进程占系统CPU的排序。极大可能排第一个的就是咱们的java进程(COMMAND列)。PID那一列就是进程号。

  1. 找到该Java进程中哪些线程占用cpu时间比较高,有时候步骤2和步骤1可以合为一个步骤。

执行“top -Hp 进程号”命令:查看java进程下的所有线程占CPU的情况(Mac上需要先输入o,然后输入CPU三个字母就会按照CPU百分比排序)。也可以使用ps H -eo user,pid,tid,%cpu --sort=-%cpu | grep $pid 直接看线程快照结果,或者ps -mp $pid -o THREAD,tid,time 直接看线程快照结果

  1. 利用jvm提供的工具,比如jstack。

直接jstack -l 该Java进程到某个文件(比如/tmp/jstack.dump)。

  1. 将步骤2得到的线程ID由10进制转换成16进制,去jstack.dump文件中看这些线程到底是执行了哪些Java代码,从而可以找出问题。

这里稍微“恶心”的是jstack文件中的线程ID用的是16进制,需要使用命令printf "%x\n 10进行转换,命令用的不熟的话,容易造成定位问题时间过长。

shell脚本

有了思路之后,我们便于后面排查问题,也能提高排查问题的效率,所以这里将这些步骤编写到一个shell脚本中,目标就是拿到服务器执行就可以定位到问题。(你想想,大家都知道思路的情况下,你有现成的工具,是不是显得更有水平?!更厉害?!)
执行该脚本的前提:

执行该脚本的前提是你得拥有使用jps、jstack等命令的权限。

#!/bin/bash

# 入参只有一个,即pid,如果没有,则默认找cpu最高的java进程
if [ -z "$1" ]; then
        ### 1.先找到消耗cpu最高的Java进程 ###
        pid=`ps -eo pid,%cpu,cmd --sort=-%cpu | grep java | grep -v grep | head -1 | awk 'END{print $1}' `
        if [ "$pid" =  ""  ]; then
                echo "无Java进程,退出。"
                exit
        fi
else
        pid=$1
fi

### 2.生成dump后的文件名 ###
curTime=$(date +%Y%m%dT%H:%M:%S)
# jstack后的文件会加上时间,便于对同一个进程dump多次
dumpFilePath="/tmp/pid-$pid-$curTime.jstack"
echo -e "cpu最高的java进程: "`jps | grep $pid`"\n" > $dumpFilePath

### 3.获取该进程的所有线程及其cpu(只显示cpu大于0.0的线程) ###
echo -e "进程内线程cpu占比如下(不显示cpu占比为0的线程):\n" >> $dumpFilePath
ps H -eo pid,tid,%cpu --sort=-%cpu | grep $pid | awk '$3 > 0.0 {totalCpu+=$3; printf("nid=0x%x, cpu=%s\n", $2, $3) >> "'$dumpFilePath'"} 
END{printf("cpu总占比:%s\n\n", totalCpu) >> "'$dumpFilePath'"}'

### 4.dump该进程 ###
echo -e "如下是原生jstack后的结果:\n" >> $dumpFilePath
jstack -l $pid >> $dumpFilePath

echo "dump成功,请前往查看(文件名包含时间,为了采集更准确,可以多执行几次该命令):" $dumpFilePath

exit

该脚本生成的文件名为pid-$pid-时间.jstack,默认放在/tmp目录下,比如pid是123的Java进程的cpu占比最高,那么通过该脚本生成的文件就是/tmp/pid-123-20211208T16:32:03.jstack。

使用方法(假设将上述脚本保存为cpuhigh.sh文件):

  • 直接sh ./cpuhigh.sh让脚本来查找cpu占比最高的Java进程并在/tmp目录下生成dump文件
  • sh ./cpuhigh.sh 123,使用该脚本在/tmp目录下形成某个Java进程(比如ID为123)的dump文件

演示案例

JAVA代码示例

写一段代码,模拟CPU飙高的

package com.dxm.demo.cpu;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @description:
 * @author: dxm
 * @date: Created in 2021/12/1 12:45 下午
 * @version:
 * @modified By:
 */
@RestController
public class TestOomAndCpuController {

    @GetMapping("/cpu/{count}")
    public long cpuCount(@PathVariable("count") long count){
        long num = 0;
        for (long i = 0; i < count; i++) {
            num++;
        }
        return num;
    }
}

将这个打包编译,然后上传到自己的测试服务器。
在这里插入图片描述
如图,我这里上传到自己的测试服务器的/tmp目录下demo-0.0.1-SNAPSHOT.jar,然后使用nohup java -jar demo-0.0.1-SNAPSHOT.jar &运行jar包在这里插入图片描述
可以看到我们的Pid是21092,在模拟之前,我们先使用top命令看一下现在服务器的负载情况:
在这里插入图片描述
从图上可以看到当前服务器负载很小,接下来我们需要访问接口,使得CPU飙高。
我们这里使用curl命令curl http://127.0.0.1:8089/cpu/10000000000000,然后使用top命令查看服务器的负载:在这里插入图片描述
当前CPU已经106.7%,与此同时,我们在打开另一个标签页去执行脚本,记录当前的线程快照。在这里插入图片描述
如图,我们执行脚本,生成jstack文件,我们是简单的模拟,所以是清楚问题是出在哪行代码的,如果是服务器的话,我们多采集几次,便于准确的分析定位问题。下面我们查看一下脚本的结果文件:
在这里插入图片描述
查看一下jstack文件:
在这里插入图片描述
再来看看源代码:
在这里插入图片描述
定位到第20行这里的有问题,具体为啥给count赋值为10000000000000就会导致CPU飙高这种简单问题就不需要我再说了吧!
好了,结束!over

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值