在生產上,咱們會遇到各類各樣的故障,遇到了故障怎麼辦?php
不要慌,只有冷靜纔是解決故障的利器。html
下面以一個例子爲例,在生產中碰到了CPU 100%的問題怎麼辦?java
在生產中真的碰到了CPU 100%的問題,再來看這篇文章已經遲了,仍是先來模擬演練下吧。
怎麼模擬演練?python
(1)查找資料,選型排查CPU高負載問題的工具。mysql
(2)安裝一個高負載程序或手寫個高負載應用部署。linux
(3)安裝、執行分析工具,實戰分析,找出故障緣由。git
(4)思考與總結。github
由於如今大部分的企業應用都是java編寫的,因此咱們本次排查的高負載應用也是針對java的,可是思路實際上是相同的,若是也有php、python、go等語言寫的程序,無非就是換個工具而已,排查的步驟都是相似的。web
而top這個命令必定是Linux上不可動搖的資源監控工具。spring
如下三類工具從原生的top、jstack到功能強大的Arthas和一鍵式查找的show-busy-java-threads,它們都各有長處。在合適的環境選擇合適的工具纔是考驗一個IT人員能力的時候。
運用之道,存乎一心。
此方法無需額外安裝工具,在無法鏈接互聯網的狀況下使用此方法排查效果較好。
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。
Arthas(阿爾薩斯)是 阿里巴巴開源出來的一個針對 java 的線上診斷工具,功能很是強大。
Arthas的githup官網https://github.com/alibaba/arthas。
Arthas 支持JDK 6+,支持Linux/Mac/Windows,採用命令行交互模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。
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方法,只是在其上作了封裝展現。
查看CPU負載的工具選好了,如今咱們須要弄個程序來讓CPU達到高負載運行。
以java代碼爲示例,寫一個死循環程序,基本就會致使CPU使用率百分百。
開始動手,新建springboot的maven項目,建立web服務,引入SpringBoot內置web容器,pom.xml關鍵引用jar包以下:
<!-- 引入容器類 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
建立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 }
建立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 姚毛毛!"; } }
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。
# 訪問上傳路徑 cd /usr/local/games # 後臺運行jar包 java -jar spring-boot-hello-1.0.jar &
注意:請在自用的測試服務器或虛擬機上使用,千萬不要在生產機器上運行此項目。
打開瀏覽器,地址欄輸入
http://【IP】:9999/api/testWhile?size=20
返回「Hello I'm 姚毛毛!」,說明調用成功。
實際上,不少排查工具的本質都是在原生工具上作的擴展和封裝。理解了原生工具的用法,對於更多強大的工具爲何能作到那樣的效果便也會心中有數了。
也有不少場景中,咱們所運維的服務器是在內網環境,須要通過層層堡壘機、跳板機,此時安裝額外的排查工具較爲困難與耗時,使用原生的工具與方法則是較爲合適的選擇。
命令: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」。
命令:top -H -p 【PID】,顯示一個進程的線程運行信息列表。
實例:top -Hp 17376 ,以下圖所示,能夠看到多個高耗CPU使用率的線程。
命令:printf 「%x\n」 【線程pid】,轉換多個線程數字爲十六進制,第4步使用時前面加0x。
實例:printf '%x\n' 17378 17379 17412 17426,獲得結果43e二、43e三、440四、4412;以下圖所示:
命令:jstack 【進程PID】| grep 【線程轉換後十六進制】-A10 , 使用jstack獲取進程PID堆棧,利用grep定位線程id,打印後續10行信息。
實例:jstack 17376 | grep '0x43e2' -A10 ,以下圖所示:
看上圖中的「GC task thread#0 (ParallelGC)」,表明垃圾回收線程,該線程會負責進行垃圾回收。
爲何會有兩個線程一直在進行垃圾回收,而且佔用那麼高的CPU使用率呢?
第4步也能夠換個方法查看,能夠先將jstack堆棧信息存儲起來。
命令:jstack 【進程PID】> 【文件】
實例:jstack 17376 > yao.dump,存儲17376進程的堆棧信息。
再使用cat + grep查找看看後面幾個高CPU線程的堆棧信息。
實例:cat -n yao.dump | grep -A10 '0x4404',以下圖所示:
能夠看到線程0x4404【線程17426】產生堆棧信息,直指方法whileTrue。
在第3步時咱們看到CPU佔用率最高的並非0x4404,而是0x43e二、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信息的堆棧,查看具體的代碼。
使用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安裝完成後從新啓動。
(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,則開始監控進程1545八、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
已經進入Arthas操做界面,輸入dashboard,回車後將看到線程及堆棧信息,如圖所示,arthas已經將cpu高使用率的線程給安排上了。
固然,Arthas的dashboard顯示了很是豐富的資源監控信息,不僅是線程運行信息,還有堆棧使用、GC等信息。
ctrl + c 退出dashboard界面,輸入thread 32查看線程信息,以下圖所示:
能夠看到是TestWhile類中的whileTrue方法中的put方法致使cpu使用率升高。
使用Arthas自帶的反編譯方法jad,輸入命令:
jad com.yao.service.TestWhile
能夠反編譯java的class查看問題函數的具體代碼,以下圖所示:
最後,既然問題已經找到,那就退出Arthas吧。輸入命令:quit,以下圖所示:
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)。
# 快速安裝 source <(curl -fsSL https://raw.githubusercontent.com/oldratlee/useful-scripts/master/test-cases/self-installer.sh)
# 下載到當前目錄下 wget --no-check-certificate https://raw.github.com/oldratlee/useful-scripts/release/show-busy-java-threads # 賦權 chmod +x show-busy-java-threads
以上兩種方法均可如下載安裝,安裝完成後,就能夠直接執行了。
show-busy-java-threads
以下圖所示,找到了CPU使用率前5高的線程,查找很是迅速。
從前兩個線程能夠看出,與使用原生工具(jstack)看到的同樣,都是頻繁gc致使的高cpu使用率。
而這gc線程出現的主要緣由,則是後面幾個高CPU線程中的方法致使的。
與上面兩類工具同樣,既然已經定位到問題方法,那就修改下程序吧。
與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秒。
若是報沒有權限(多是無權限執行jstack),須要加sudo來執行:
# root權限執行 show-busy-java-threads sudo show-busy-java-threads.sh
學習完了三類工具的排查實戰後,咱們如今來總結下,怎麼去排查問題的?
(1)查看CPU負載太高進程。
(2)查看進程中負載高的線程。
(3)獲取進程中的堆棧信息。
(4)獲取堆棧中對應的線程信息,找到裏面的問題方法。
在排查過程當中咱們不僅使用了原生工具,還使用了加強工具Arthas與show-busy-java-threads,大大簡化了咱們排查的步驟。
再仔細想想,加強工具其實無非就是在原生工具的基礎上,將這些方法與步驟作了一些自動化處理是否是?
若是咱們本身用shell腳本去寫一個自動監控程序,是否是也能夠去借鑑借鑑呢?
祝你們在遇到類似問題時,能夠作到手中有刀、心中有譜,穩如老狗、不忙不慌。
最後,若沒法按照Part2的代碼變異出死循環jar包,可關注公衆號,留言hello,獲得名爲srping-boot-hello的jar包連接地址。
歡迎關注個人公衆號:姚毛毛的博客
這裏有個人編程生涯感悟與總結,有Java、Linux、Oracle、mysql的相關技術,有工做中進行的架構設計實踐和讀書理論,有JVM、Linux、數據庫的性能調優,有……
有技術,有情懷,有溫度
歡迎關注我:姚毛毛& 妖生