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
}
}
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 姚毛毛!”,說明調用成功。
Part3 實戰分析:原生工具
實際上,很多排查工具的本質都是在原生工具上做的擴展和封裝。理解了原生工具的用法,對於更多強大的工具爲什麼能做到那樣的效果便也會心中有數了。
也有很多場景中,我們所運維的服務器是在內網環境,需要經過層層堡壘機、跳板機,此時安裝額外的排查工具較爲困難與耗時,使用原生的工具與方法則是較爲合適的選擇。
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”。
2、找到最耗CPU的線程
命令:top -H -p 【PID】,顯示一個進程的線程運行信息列表。實例:top -Hp 17376 ,如下圖所示,可以看到多個高耗CPU使用率的線程。
3、轉換線程PID爲16進制
命令:printf “%x\n” 【線程pid】,轉換多個線程數字爲十六進制,第4步使用時前面加0x。實例:printf '%x\n' 17378 17379 17412 17426,得到結果43e2、43e3、4404、4412;如下圖所示:
4、查看堆棧,定位線程
命令:jstack 【進程PID】| grep 【線程轉換後十六進制】-A10 , 使用jstack獲取進程PID堆棧,利用grep定位線程id,打印後續10行信息。實例:jstack 17376 | grep '0x43e2' -A10 ,如下圖所示:
看上圖中的“GC task thread#0 (ParallelGC)”,代表垃圾回收線程,該線程會負責進行垃圾回收。爲什麼會有兩個線程一直在進行垃圾回收,並且佔用那麼高的CPU使用率呢?
5、存儲堆棧,批量查看
第4步也可以換個方法查看,可以先將jstack堆棧信息存儲起來。 命令:jstack 【進程PID】> 【文件】 實例:jstack 17376 > yao.dump,存儲17376進程的堆棧信息。再使用cat + grep查找看看後面幾個高CPU線程的堆棧信息。實例:cat -n yao.dump | grep -A10 '0x4404',如下圖所示:
可以看到線程0x4404【線程17426】產生堆棧信息,直指方法whileTrue。
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(阿爾薩斯)
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安裝完成後重新啓動。
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
3、監控查看
已經進入Arthas操作界面,輸入dashboard,回車後將看到線程及堆棧信息,如圖11.13所示,arthas已經將cpu高使用率的線程給安排上了。
當然,Arthas的dashboard顯示了非常豐富的資源監控信息,不只是線程運行信息,還有堆棧使用、GC等信息。
4、thread【ID】查看線程
ctrl + c 退出dashboard界面,輸入thread 32查看線程信息,如下圖所示:
可以看到是TestWhile類中的whileTrue方法中的put方法導致cpu使用率升高。
5、jad反編譯
使用Arthas自帶的反編譯方法jad,輸入命令:jad com.yao.service.TestWhile 可以反編譯java的class查看問題函數的具體代碼,如下圖所示:
6、退出arthas
最後,既然問題已經找到,那就退出Arthas吧。輸入命令:quit,如下圖所示:
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繁忙線程查找工具)
1、方法1——快速下載 & 安裝
# 快速安裝
source <(curl -fsSL https://raw.githubusercontent.com/oldratlee/useful-scripts/master/test-cases/self-installer.sh)
2、方法2——下載後賦權
# 下載到當前目錄下
wget --no-check-certificate https://raw.github.com/oldratlee/useful-scripts/release/show-busy-java-threads
# 賦權
chmod +x show-busy-java-threads
3、命令執行定位 以上兩種方法都可以下載安裝,安裝完成後,就可以直接執行了。show-busy-java-threads 如下圖所示,找到了CPU使用率前5高的線程,查找非常迅速。
從前兩個線程可以看出,與使用原生工具(jstack)看到的一樣,都是頻繁gc導致的高cpu使用率。而這gc線程出現的主要原因,則是後面幾個高CPU線程中的方法導致的。與上面兩類工具一樣,既然已經定位到問題方法,那就修改下程序吧。
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、注意事項
如果報沒有權限(可能是無權限執行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包鏈接地址。
推薦閱讀
公衆號@陳樹義,用最簡單的語言,分享我的技術見解。