上善若水,不進則退

姚毛毛的博客

左手代碼,右手寫詩

面試官: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打包項目,如圖所示:
file

(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。

file

可以看到紅框標處,測試機器的雙核CPU使用率都已經快達到100%。

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

3.2 找到最耗CPU的線程

? 命令:top -H -p 【PID】,顯示一個進程的線程運行信息列表。

實例:top -Hp 17376 ,如下圖所示,可以看到多個高耗CPU使用率的線程。

file

3.3 轉換線程PID為16進制

? 命令:printf “%x\n” 【線程pid】,轉換多個線程數字為十六進制,第4步使用時前面加0x。

實例:printf '%x\n' 17378 17379 17412 17426,得到結果43e2、43e3、4404、4412;如下圖所示:
file

3.4 查看堆棧,定位線程

? 命令:jstack 【進程PID】| grep 【線程轉換后十六進制】-A10 , 使用jstack獲取進程PID堆棧,利用grep定位線程id,打印后續10行信息。

實例:jstack 17376 | grep '0x43e2' -A10 ,如下圖所示:
file

看上圖中的“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',如下圖所示:

file

可以看到線程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次,如下圖所示:
file
可以看到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所示:
file

#啟動
./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啟動
file

如上圖,在啟動后,可以看到報錯信息:“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。

file

關閉此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,進入監控頁面了。

file

(2)啟動方法二
首先top -c查看哪個進程有問題,輸出結果如下圖:

file

再使用./as.sh 【PID】命令監控線程,實例命令如下:

# 打開Arthas,監控17376進程
./as.sh 17376

4.3 監控查看

已經進入Arthas操作界面,輸入dashboard,回車后將看到線程及堆棧信息,如圖所示,arthas已經將cpu高使用率的線程給安排上了。

file

當然,Arthas的dashboard顯示了非常豐富的資源監控信息,不只是線程運行信息,還有堆棧使用、GC等信息。

4.4 thread【ID】查看線程

ctrl + c 退出dashboard界面,輸入thread 32查看線程信息,如下圖所示:

圖14 Arthas的thread【ID】命令結果

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

4.5 jad反編譯

使用Arthas自帶的反編譯方法jad,輸入命令:

jad com.yao.service.TestWhile

可以反編譯java的class查看問題函數的具體代碼,如下圖所示:

file

4.6 退出arthas

最后,既然問題已經找到,那就退出Arthas吧。輸入命令:quit,如下圖所示:

file

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高的線程,查找非常迅速。

file

從前兩個線程可以看出,與使用原生工具(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、數據庫的性能調優,有……

有技術,有情懷,有溫度

歡迎關注我:姚毛毛& 妖生

公眾號

posted @ 2019-11-29 11:38  姚毛毛  閱讀(...)  評論(...編輯  收藏
七乐彩2011年走势图南方双彩