面试官:CPU百分百!给你一分钟,怎么排查?有几种方法?

Part0 遇到了故障怎么办?

在生产上,我们会遇到各种各样的故障,遇到了故障怎么办?

不要慌,只有冷静才是解决故障的利器。

下面以一个例子为例,在生产中碰到了CPU 100%的问题怎么办?

在生产中真的碰到了CPU 100%的问题,再来看这篇文章已经迟了,还是先来模拟演练下吧。
怎么模拟演练?

(1)查找资料,选型排查CPU高负载问题的工具。

(2)安装一个高负载程序或手写个高负载应用部署。

(3)安装、执行分析工具,实战分析,找出故障原因。

(4)思考与总结。

Part1 工具选型

因为现在大部分的企业应用都是java编写的,所以我们本次排查的高负载应用也是针对java的,但是思路其实是相同的,如果也有php、python、go等语言写的程序,无非就是换个工具而已,排查的步骤都是类似的。

而top这个命令一定是Linux上不可动摇的资源监控工具。

以下三类工具从原生的top、jstack到功能强大的Arthas和一键式查找的show-busy-java-threads,它们都各有长处。在合适的环境选择合适的工具才是考验一个IT人员能力的时候。

运用之道,存乎一心。

1.1 原生方法

此方法无需额外安装工具,在没法连接互联网的情况下使用此方法排查效果较好。

top、printf都是Linux原生命令,jstack、jstat是jdk自带命令工具。

很多功能强大的Linux和java诊断工具也是以top、jstack、jstat为基础命令做的封装。

注意:jstack、jstat等命令需要jdk完整安装,linux自带的openJdk一般无此工具,可以在java的bin目录下查看是否有这些命令。

oracle jdk 1.8下载地址:
https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html。

1.2 阿里开源:Arthas(阿尔萨斯)

Arthas(阿尔萨斯)是 阿里巴巴开源出来的一个针对 java 的线上诊断工具,功能非常强大。

Arthas的githup官网https://github.com/alibaba/arthas。

Arthas 支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

1.3 淘宝开源:show-busy-java-threads

show-busy-java-threads.sh,其作者是淘宝同学【李鼎(哲良) oldratlee】,这个工具是useful-scripts工具集的其中一个工具。

useful-scripts的github网址:https://github.com/oldratlee/useful-scripts。

show-busy-java-threads用于快速排查Java的CPU性能问题(top us值过高),自动查出运行的Java进程中消耗CPU多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用。

注意:此工具的核心还是使用jdk的jstack方法,只是在其上做了封装展示。

Part2 高负载代码创建

查看CPU负载的工具选好了,现在我们需要弄个程序来让CPU达到高负载运行。

以java代码为示例,写一个死循环程序,基本就会导致CPU使用率百分百。

2.1 新建springboot项目

开始动手,新建springboot的maven项目,创建web服务,引入SpringBoot内置web容器,pom.xml关键引用jar包如下:

<!-- 引入容器类 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2 创建service:TestWhile

创建service类TestWhile,编写死循环代码。

package com.yao.service;

import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author 姚毛毛
* @version V1.0
* @Package com.yao.yaojiaxiaoyuan
* @Description: 死循环demo
* @date 2019/11/19--16:55
*/
@Service
public class TestWhile {

    /* 操作内存对象 */
    ConcurrentHashMap map = new ConcurrentHashMap();

    /**
    * 死循环,生产中千万不要这么写,while(true)时一定要有退出条件
    * @param threadName 指定线程名
    */
    private void whileTrue(String threadName) {
    // 不设置退出条件,死循环
    while (true) {

    // 在死循环中不断的对map执行put操作,导致内存gc
    for( int i = 0; i <= 100000; i ++) {
    map.put(Thread.currentThread().getName() + i, i);
    } // end for

    }// end while
}

    /**
    * 循环size,新建线程,调用whileTrue
    * @param size 线程数
    */
    public void testWhile(int size) {

        // 循环size,创建多线程,并发执行死循环
        for (int i = 0; i < size; i++) {

            int finalI = i;

            // 新建并启动线程,调用whileTrue方法
            new Thread(() -> {
                whileTrue("姚毛毛-" + finalI);
             }).start();

        }// end for
    }//  end testWhile

}

2.3 创建Controller:TestWhile

创建rest服务,编写get方法testWhile,调用死循环服务testWhile。

package com.yao.controller;

import com.yao.service.TestWhile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

/* 注入服务TestWhile */
@Autowired
TestWhile testWhile;

/**
* testWhile循环size生产线程,调用whileTrue方法
* size有多少,则意味着调用了whileTrue多少次,产生了多少个死循环
* @param size 产生线程任务
* @return 调度成功,返回信息
*/
@RequestMapping("/testWhile")
public String testWhile(@RequestParam int size) {
    testWhile.testWhile(size);
    return "Hello I'm 姚毛毛!";
    }
}

2.4 打包项目,上传测试服务器

application.properties配置如下:
# 设置应用端口
server.port=9999
# 应用访问根目录
server.servlet.context-path=/api

打包我们可以选择idea或maven原生工具。

(1)利用idea开发工具,打开右侧的maven project,使用package打包项目,如图所示:

(2)使用maven命令,打开项目根目录,在windows的cmd命令窗口中中输入命令如下:

maven clean package

打包项目为:spring-boot-hello-1.0.jar。
上传服务器,路径:/usr/local/games。

2.5 登录服务器,运行项目

# 访问上传路径
cd /usr/local/games

# 后台运行jar包
java -jar spring-boot-hello-1.0.jar &

注意:请在自用的测试服务器或虚拟机上使用,千万不要在生产机器上运行此项目。

2.6 打开浏览器,访问死循环方法

打开浏览器,地址栏输入

http://【IP】:9999/api/testWhile?size=20

返回“Hello I‘m 姚毛毛!”,说明调用成功。

Part 3 实战分析:原生工具

实际上,很多排查工具的本质都是在原生工具上做的扩展和封装。理解了原生工具的用法,对于更多强大的工具为什么能做到那样的效果便也会心中有数了。

也有很多场景中,我们所运维的服务器是在内网环境,需要经过层层堡垒机、跳板机,此时安装额外的排查工具较为困难与耗时,使用原生的工具与方法则是较为合适的选择。

3.1 找到最耗CPU的进程

? 命令:top –c,显示进程运行信息列表。

实例:top -c。

交互1:按1,数字1,显示多核CPU信息。

交互2:键入P (大写p),进程按照CPU使用率排序。

如下图所示结果,已经在交互过程中按了数字1及大写P。

可以看到红框标处,测试机器的双核CPU使用率都已经快达到100%。

而第一个进程PID是17376的就是我们要找的罪魁祸首了;可以看到进程最后一列,COMMAND注释的进程名:“java -jar spring-boot-hello-1.0.jar”。

3.2 找到最耗CPU的线程

? 命令:top -H -p 【PID】,显示一个进程的线程运行信息列表。

实例:top -Hp 17376 ,如下图所示,可以看到多个高耗CPU使用率的线程。

3.3 转换线程PID为16进制

? 命令:printf “%x\n” 【线程pid】,转换多个线程数字为十六进制,第4步使用时前面加0x。

实例:printf ‘%x\n‘ 17378 17379 17412 17426,得到结果43e2、43e3、4404、4412;如下图所示:

3.4 查看堆栈,定位线程

? 命令:jstack 【进程PID】| grep 【线程转换后十六进制】-A10 , 使用jstack获取进程PID堆栈,利用grep定位线程id,打印后续10行信息。

实例:jstack 17376 | grep ‘0x43e2‘ -A10 ,如下图所示:

看上图中的“GC task thread#0 (ParallelGC)”,代表垃圾回收线程,该线程会负责进行垃圾回收。

为什么会有两个线程一直在进行垃圾回收,并且占用那么高的CPU使用率呢?

3.5 存储堆栈,批量查看

第4步也可以换个方法查看,可以先将jstack堆栈信息存储起来。

? 命令:jstack 【进程PID】> 【文件】

实例:jstack 17376 > yao.dump,存储17376进程的堆栈信息。

再使用cat + grep查找看看后面几个高CPU线程的堆栈信息。

实例:cat -n yao.dump | grep -A10 ‘0x4404‘,如下图所示:

可以看到线程0x4404【线程17426】产生堆栈信息,直指方法whileTrue。

3.6 GC查看

在第3步时我们看到CPU占用率最高的并不是0x4404,而是0x43e2、0x43e3。但是并没法看到其中是什么类与方法,只有一条GC信息。

是不是死循环导致了GC太频繁,导致CPU使用率居高不下呢?

我们使用jstat看下jvm的GC信息看看。

? 命令:jstat -gcutil 【进程PID】【毫秒】【打印次数】

实例:jstat -gcutil 17376 2000 5,查看17376进程的GC信息,每2秒打印一次,共打印5次,如下图所示:

可以看到Full GC的次数高达506次,Full GC的持续时间很长,平均每次Full GC耗时达到9秒(4766/506,即GCT/FGC)。

确实验证了我们之前的想法,再返回第4或第5步查看其他几个高CPU占用率线程,找到非GC信息的堆栈,查看具体的代码。

Part4 实战分析:Arthas(阿尔萨斯)

4.1 安装

使用curl下载安装

curl -L https://alibaba.github.io/arthas/install.sh | sh

如图11.8所示:

#启动
./as.sh 

注意:如果报错“Error: telnet is not installed. Try to use java -jar arthas-boot.jar”,说明telnet没有安装。

# 安装telnet
yum install telnet -y

telnet安装完成后重新启动。

4.2 启动

(1)启动方法一

重新使用./as.sh启动

如上图,在启动后,可以看到报错信息:“Error: no available java process to attach”,意思是没有活动的java进程。

启动我们上面写的java示例再重新看下。

输入启动命令:

Java -jar spring-boot-hello-1.0.jar &

Java进程启动完成后,使用./as.sh重启启动Arthas。

如下图所示,显示了当前运行的java进程,按下1,则开始监控进程15458、jar包spring-boot-hello-1.0.jar。

关闭此java进程,我们再来一遍。

# 关闭15458 进程
Kill -9 15458
# 重新启动java示例
Java -jar spring-boot-hello-1.0.jar &
# 启动Arthas
./as.sh
# 按1进入java进程,此时java进程PID已经变成17376
1

进入阿尔萨斯完成,如下图,可以看到登录路径已经变成了[[email protected]]$,可以输入dashboard,进入监控页面了。

(2)启动方法二
首先top -c查看哪个进程有问题,输出结果如下图:

再使用./as.sh 【PID】命令监控线程,实例命令如下:

# 打开Arthas,监控17376进程
./as.sh 17376

4.3 监控查看

已经进入Arthas操作界面,输入dashboard,回车后将看到线程及堆栈信息,如图所示,arthas已经将cpu高使用率的线程给安排上了。

当然,Arthas的dashboard显示了非常丰富的资源监控信息,不只是线程运行信息,还有堆栈使用、GC等信息。

4.4 thread【ID】查看线程

ctrl + c 退出dashboard界面,输入thread 32查看线程信息,如下图所示:

可以看到是TestWhile类中的whileTrue方法中的put方法导致cpu使用率升高。

4.5 jad反编译

使用Arthas自带的反编译方法jad,输入命令:

jad com.yao.service.TestWhile

可以反编译java的class查看问题函数的具体代码,如下图所示:

4.6 退出arthas

最后,既然问题已经找到,那就退出Arthas吧。输入命令:quit,如下图所示:

4.7 Arthas其他命令

Arthas还有些常用及好用的命令,命令如下:
help——查看命令帮助信息
cls——清空当前屏幕区域
session——查看当前会话的信息
reset——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
version——输出当前目标 Java 进程所加载的 Arthas 版本号
history——打印命令历史
quit——退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
stop——和shutdown命令一致
shutdown——关闭 Arthas 服务端,所有 Arthas 客户端全部退出
keymap——Arthas快捷键列表及自定义快捷键
其他功能和具体使用教程,可以看这里:Arthas的进阶命令(https://alibaba.github.io/arthas/advanced-use.html)。

Part5 实战分析:show-busy-java-threads(java繁忙线程查找工具)

5.1 方法1——快速下载 & 安装

# 快速安装
source <(curl -fsSL https://raw.githubusercontent.com/oldratlee/useful-scripts/master/test-cases/self-installer.sh)

5.2 方法2——下载后赋权

# 下载到当前目录下
wget --no-check-certificate https://raw.github.com/oldratlee/useful-scripts/release/show-busy-java-threads
# 赋权
chmod +x show-busy-java-threads

5.3 命令执行定位

以上两种方法都可以下载安装,安装完成后,就可以直接执行了。

show-busy-java-threads

如下图所示,找到了CPU使用率前5高的线程,查找非常迅速。

从前两个线程可以看出,与使用原生工具(jstack)看到的一样,都是频繁gc导致的高cpu使用率。

而这gc线程出现的主要原因,则是后面几个高CPU线程中的方法导致的。

与上面两类工具一样,既然已经定位到问题方法,那就修改下程序吧。

5.4 其他命令

与Arthas一样,show-busy-java-threads也有一些其他很好用的增强命令:

show-busy-java-threads --help

查看show-busy-java-threads常用命令:

show-busy-java-threads
从所有的 Java进程中找出最消耗CPU的线程(缺省5个),打印出其线程栈。

show-busy-java-threads -c 3
-c 3:3为n,指定显示最耗cpu使用率前3的线程。

show-busy-java-threads -c 3 -p 17376
展示进程17376耗费CPU组多的3个线程;
-p 17376 :17376为进程PID,-p参数指定进程PID。

show-busy-java-threads -s 【指定jstack命令的全路径】
对于sudo方式的运行,JAVA_HOME环境变量不能传递给root,
而root用户往往没有配置JAVA_HOME且不方便配置,
显式指定jstack命令的路径就反而显得更方便了

show-busy-java-threads -a yao.log
将输出结果导入到指定文件yao.log中

show-busy-java-threads 3 5
每5秒执行一次,一共执行3次; 缺省执行一次,缺省间隔是3秒。

5.5 注意事项

如果报没有权限(可能是无权限执行jstack),需要加sudo来执行:

# root权限执行 show-busy-java-threads
sudo show-busy-java-threads.sh

Part6 小结

学习完了三类工具的排查实战后,我们现在来总结下,怎么去排查问题的?

(1)查看CPU负载过高进程。

(2)查看进程中负载高的线程。

(3)获取进程中的堆栈信息。

(4)获取堆栈中对应的线程信息,找到里面的问题方法。

在排查过程中我们不只使用了原生工具,还使用了增强工具Arthas与show-busy-java-threads,大大简化了我们排查的步骤。

再仔细想一想,增强工具其实无非就是在原生工具的基础上,将这些方法与步骤做了一些自动化处理是不是?

如果我们自己用shell脚本去写一个自动监控程序,是不是也可以去借鉴借鉴呢?

祝大家在遇到相似问题时,可以做到手中有刀、心中有谱,稳如老狗、不忙不慌。

最后,若无法按照Part2的代码变异出死循环jar包,可关注公众号,留言hello,得到名为srping-boot-hello的jar包链接地址。



欢迎关注我的公众号:姚毛毛的博客

这里有我的编程生涯感悟与总结,有Java、Linux、Oracle、mysql的相关技术,有工作中进行的架构设计实践和读书理论,有JVM、Linux、数据库的性能调优,有……

有技术,有情怀,有温度

欢迎关注我:姚毛毛& 妖生

原文地址:https://www.cnblogs.com/yaomaomao/p/11956609.html

时间: 2024-10-10 15:55:21

面试官:CPU百分百!给你一分钟,怎么排查?有几种方法?的相关文章

一口气说出 9种 分布式ID生成方式,面试官有点懵了

摘自:https://www.cnblogs.com/chengxy-nds/p/12315917.html 整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 本文作者:程序员内点事原文链接:https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg 更多精选 3万字总结,Mysql优化之精髓 为了不复制粘贴,我被逼着学会了JAVA爬虫 技术部突然宣布:JAVA开发人员全部要

面试官绝杀:系统是如何支撑高并发的?

很多人面试的时候被问到一个让人特别手足无措的问题:你的系统如何支撑高并发? 大多数同学被问到这个问题压根儿没什么思路去回答,不知道从什么地方说起,其实本质就是没经历过一些真正有高并发系统的锤炼罢了. 因为没有过相关的项目经历,所以就没法从真实的自身体会和经验中提炼出一套回答,然后系统地阐述出来自己复杂过的系统如何支撑高并发的. 所以,这篇文章就从这个角度切入来简单说说这个问题,教你用一个最简单的思路来如何应对的. 当然这里首先说清楚一个前提:高并发系统各不相同.比如每秒百万并发的中间件系统.每日

《吊打面试官》系列-Redis哨兵、持久化、主从、手撕LRU

你知道的越多,你不知道的越多 点赞再看,养成习惯 前言 Redis在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行360°的刁难.作为一个在互联网公司面一次拿一次offer的面霸(请允许我使用一下夸张的修辞手法),打败了无数竞争对手,每次都只能看到无数落寞的身影失望的离开,略感愧疚,在一个寂寞难耐的夜晚,我痛定思痛,决定开始写<吊打面试官>系列,希望能帮助各位读者以后面试势如破竹,对面试官进行360°的反击,吊打问你的面试官,让一同面试的

【真实面试经历】我和阿里面试官的一次“邂逅”(附问题详解)

本文的内容都是根据读者投稿的真实面试经历改编而来,首次尝试这种风格的文章,花了几天晚上才总算写完,希望对你有帮助. 本文主要涵盖下面的内容: 分布式商城系统:架构图讲解: 消息队列相关:削峰和解耦: Redis 相关:缓存穿透问题的解决: 一些基础问题: 网络相关:1.浏览器输入 URL 发生了什么? 2.TCP 和 UDP 区别? 3.TCP 如何保证传输可靠性? Java 基础:1. 既然有了字节流,为什么还要有字符流? 2.深拷贝 和 浅拷贝有啥区别呢? 下面是正文! 面试开始,坐在我前面

fuck猎豹移动面试官

 特郁闷了,前几天在qq群遇到以为猎豹移动的,说招人做云后台开发,我还特意问了要求,他说很简单能做事就可以了. 一轮电话面试期望不大,但是出乎意料过了,接着二轮电话面试,感觉还好.后面是到广州的面试. 见到面试管, 先让我自我介绍,好吧说了一些········,然后简单介绍了一下以前的工作,接着让我说说自己的优点.这是重点了,我说我有创新能力,有钻研精神,还跟他列举了很多事例,我觉得这些事例都是很宝贵的.然后问了一个技术问题,有一个10G的文件里面是整型让我排序,我考虑了不到半分钟的时间后,

[转] 写个软件来防止服务器网站 CPU 百分百

问题: 大概每隔两个星期左右,秋色园上服务器就会来一次 CPU 百分百,由于问题发生的概率极低,要它重现也难,所以只能意淫是内存太少的原故.以前出现,远程上去结束掉进程,就正常了, 悲剧的是最近秋色园 VPS 不知啥原因,经常远程不上去, 最后转转折折只能进 VPS 管理后台重启.要遇上 CPU 百分百,又是需要机缘,所以一旦发生和遇到解决的时间差度大,就会造成服务器长时间打不开,后果大伙都懂的... 解决: 方法一:设置应用池CPU策略,达到N的时候自动回收进程(不实用,排除) 因为更新网站

【长文】Google面试官分步解析自己泄漏前的面试题,超多干货和建议

本文翻译自Google工程师/面试官Alex Golec的文章:Google Interview Questions Deconstructed: The Knight's Dialer:翻译:实验楼扫地阿姨:原文链接 作为一名Google的工程师和面试官,今天是我第二次发文分享科技公司面试建议了.这里先声明:本文仅代表我个人的观察.意见和建议.请勿当作来自Google或Alphabet的官方建议或声明. 下面这个问题,是我面试生涯中第一个问题:也是第一个被泄漏出来,以及第一个被禁掉的问题.我喜

去百度面试,想知道面试官问哪些问题,看这篇文章

https://mp.weixin.qq.com/s?__biz=MzI0NjM3NjI1NQ==&mid=2247487861&idx=1&sn=34316976cdb0aa2e4df3aa6f1f5cc31d&chksm=e9416325de36ea33200d9944fff37381de11c8d4dc8aeb0cc4b83c6201e56b8ee2bc8c2798cc&mpshare=1&scene=1&srcid=1102ElP5k7MBT

面试官:你分析过线程池源码吗?

线程池源码也是面试经常被提问到的点,我会将全局源码做一分析,然后告诉你面试考啥,怎么答. 为什么要用线程池? 简洁的答两点就行. 降低系统资源消耗. 提高线程可控性. 如何创建使用线程池? JDK8提供了五种创建线程池的方法: 1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待. 1 public static ExecutorService newFixedThreadPool(int nThreads) { 2 return new ThreadPoolExecutor(