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

进入阿尔萨斯完成,如下图,可以看到登录路径已经变成了[arthas@17376]$,可以输入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、数据库的性能调优,有……

有技术,有情怀,有温度

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

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

  1. 面试官:JavaScript如何实现数组拍平(扁平化)方法?

    面试官:JavaScript如何实现数组拍平(扁平化)方法? 1 什么叫数组拍平? 概念很简单,意思是将一个"多维"数组降维,比如: // 原数组是一个"三维" ...

  2. 【MySQL】面试官问我:MySQL如何实现无数据插入,有数据更新?我是这样回答的!

    写在前面 马上就是金九银十的跳槽黄金期了,很多读者都开始出去面试了.这不,又一名读者出去面试被面试官问了一个MySQL的问题:向MySQL中插入数据,如何实现MySQL中没有当前id标识的数据时插入数 ...

  3. 8年经验面试官详解 Java 面试秘诀

      作者 | 胡书敏 责编 | 刘静 出品 | CSDN(ID:CSDNnews) 本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三 ...

  4. 攻略前端面试官(一):JS的数据类型和内存机制浅析

    原文地址:http://rainykane.cn/2019/09/29/与K_K君一起攻略前端面试官(一):JS的数据类型和内存机制浅析/ 背就完事了 介绍:一些知识点相关的面试题和答案 使用姿势:看 ...

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

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 本文作者:程序员内点事 原文链接:https://mp.weix ...

  6. 美女面试官问我Python如何优雅的创建临时文件,我的回答....

    [摘要] 本故事纯属虚构,如有巧合,他们故事里的美女面试官也肯定没有我的美,请自行脑补... 小P像多数Python自学者一样,苦心钻研小半年,一朝出师投简历. 这不,一家招聘初级Python开发工程 ...

  7. Android面试官:说说你对 Binder 驱动的了解?

    面试官提了一个问题:说说你对 binder 驱动的了解.这个问题虽有些 "面试造火箭" 的无奈,可难点就是亮点.价值所在,是筛选面试者的有效手段.如果让你回答,你能说出多少呢?我们 ...

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

    你知道的越多,你不知道的越多 点赞再看,养成习惯 前言 Redis在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行360°的刁难.作为一个在互联 ...

  9. 如何准备Java面试?如何把面试官的提问引导到自己准备好的范围内?

    Java能力和面试能力,这是两个方面的技能,可以这样说,如果不准备,一些大神或许也能通过面试,但能力和工资有可能被低估.再仔细分析下原因,面试中问的问题,虽然在职位介绍里已经给出了范围,但针对每个点, ...

随机推荐

  1. 一文读懂Java类加载机制

    Java 类加载机制 Java 类加载机制详解. @pdai Java 类加载机制 类的生命周期 类的加载:查找并加载类的二进制数据 连接 验证:确保被加载的类的正确性 准备:为类的静态变量分配内存, ...

  2. any_value()函数

    转载自:https://blog.csdn.net/Peacock__/article/details/90608246 MySQL5.7之后,sql_mode中ONLY_FULL_GROUP_BY模 ...

  3. MarkDown时序图

    时序图 语法 ```sequence ``` 标题 title: 我是标题 对象 participant A participant B as b-alias 交互 sequence A->B: ...

  4. ArangoDB图数据库--总参

    参考文章: ArangoDB原生多模型数据库(百科) ArangoDB官网 ArangoDB数据库入门 arangodb-vs-cassandra arangodb-vs-mongodb2 Arang ...

  5. 跑酷天堂C++小黑框版

    上:跳跃 左右:行走 #include <bits/stdc++.h> #include <windows.h> #include <conio.h> using ...

  6. python中函数名后面带()和不带()的区别。

    今天天气不冷,微热.9.18警钟长鸣,国人当自强不息. python中有时候会遇到一个函数名称后面没有带()被调用,这是为什么呢?看下面这个例子. def target(): #定义一个函数 prin ...

  7. 通俗易懂了解Vue组件的通信方式

    1.前言 Vue框架倡导组件化开发,力求将一个大的项目拆分成若干个小的组件,就如同我们小时玩堆积木一样,一个大房子是由若干个小积木组成.组件化开发最大问题就是组件之间数据能够流通,即组件之间能够通信. ...

  8. JSP——底层原理

    都知道jsp就是在HTML文件中写java代码,以实现动态页面的效果,但是这种动态是如何实现的呢?今天就在研究一下. 首先,我写了一个简单的jsp文件: <%@page import=" ...

  9. Abp vNext 自定义 Ef Core 仓储引发异常

    问题 在使用自定义 Ef Core 仓储和 ABP vNext 注入的默认仓储时,通过两个 Repository 进行 Join 操作,提示 Cannot use multiple DbContext ...

  10. Scss的使用场景

    一.Scss 1.CSS有几个缺点 语法不够强大,没有变量和合理的样式复用机制 使得逻辑上相关的属性值必须以字面的形式重复输出,难以维护 动态的样式语言为css富裕了动态语言的特性 极大的提高了样式语 ...