做爲後端開發工程師,當收到線上服務器CPU負載太高告警時,你會這麼作?重啓服務,忽略告警?不過在我看來一個合格的工程師是必定要定位到具體問題所在的,從而 fix 它。下面記錄一下線上服務器 CPU 負載太高排查過程,把排查流程理清楚,之後遇到問題將會迅速定位到問題所在,快速解決。java
代碼層面常見的場景有:linux
這裏使用 JAVA 簡單模擬程序死循環帶來的系統高負載狀況,代碼以下:後端
/** * @program: easywits * @description: 併發下的 HashMap 測試.... * @author: zhangshaolin * @create: 2018-12-19 15:27 **/ public class HashMapMultiThread { static Map<String, String> map = new HashMap<>(); public static class AddThread implements Runnable { int start = 0; public AddThread(int start) { this.start = start; } @Override public void run() { //死循環,模擬CPU佔用太高場景 while (true) { for (int i = start; i < 100000; i += 4) { map.put(Integer.toString(i), Integer.toBinaryString(i)); } } } public static void main(String[] args) throws InterruptedException { //線程併發對 HashMap 進行 put 操做 若是一切正常,則獲得 map.size() 爲100000 //可能的結果: //1. 程序正常,結果爲100000 //2. 程序正常,結果小於100000 Thread thread1 = new Thread(new AddThread(0), "myTask-1"); Thread thread2 = new Thread(new AddThread(1), "myTask-2"); Thread thread3 = new Thread(new AddThread(2), "myTask-3"); Thread thread4 = new Thread(new AddThread(3), "myTask-4"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread1.join(); thread2.join(); thread3.join(); thread4.join(); System.out.println(map.size()); } } }
一樣使用 JAVA 程序簡單模擬線程死鎖場景,代碼以下:服務器
/** * @program: easywits * @description: 死鎖 demo .... * 1.兩個線程裏面分別持有兩個Object對象:lock1和lock2。這兩個lock做爲同步代碼塊的鎖; * 2.線程1的run()方法中同步代碼塊先獲取lock1的對象鎖,Thread.sleep(xxx),時間不須要太多,50毫秒差很少了,而後接着獲取lock2的對象鎖。 * 這麼作主要是爲了防止線程1啓動一會兒就連續得到了lock1和lock2兩個對象的對象鎖 * 3.線程2的run)(方法中同步代碼塊先獲取lock2的對象鎖,接着獲取lock1的對象鎖,固然這時lock1的對象鎖已經被線程1鎖持有,線程2確定是要等待線程1釋放lock1的對象鎖的 * <p> * 線程1″睡覺」睡完,線程2已經獲取了lock2的對象鎖了,線程1此時嘗試獲取lock2的對象鎖,便被阻塞,此時一個死鎖就造成了。 * @author: zhangshaolin * @create: 2018-12-20 11:33 **/ public class DeadLock { static Object lock1 = new Object(); static Object lock2 = new Object(); public static class Task1 implements Runnable { @Override public void run() { synchronized (lock1) { System.out.println(Thread.currentThread().getName() + " 得到了第一把鎖!!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println(Thread.currentThread().getName() + " 得到了第二把鎖!!"); } } } } public static class Task2 implements Runnable { @Override public void run() { synchronized (lock2) { System.out.println(Thread.currentThread().getName() + " 得到了第二把鎖!!"); synchronized (lock1) { System.out.println(Thread.currentThread().getName() + " 得到了第一把鎖!!"); } } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Task1(), "task-1"); Thread thread2 = new Thread(new Task2(), "task-2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(Thread.currentThread().getName() + " 執行結束!"); } }
以上兩種場景代碼執行後,不出意外,系統CPU負載將會飆升,個人機器,4核CPU已經明顯感受到卡頓了,因此線上應該杜絕出現死循環代碼。。併發
top
命令監控當前系統負載狀況執行第一種場景測試代碼。ide
在 linux
命令行鍵入 top
指令後,就開始實時監控當前系統的負載信息,監控到的負載信息以下圖所示:測試
從圖中的監控信息能夠快速大體的瞭解到,PID
爲17499
的進程CPU負載高達328+%
,是一個 JAVA
程序。簡單介紹下監控信息以下:this
在監控頁面下 按鍵盤數字 1
能夠看到每一個CPU的負載狀況,以下圖:spa
能夠看到開了四個線程,無限循環以後,個人機器中四個核心CPU,每顆負載接近百分百。命令行
top
命令監控進程中負載太高的線程top -H -p pid
: 查看指定進程中每一個線程的資源佔用狀況(每條線程佔用CPU時間的百分比),監控結果以下圖:
以上監控指令輸出的指標針對的是某個進程中的線程,從圖中看能夠快速得出結論:四個 JAVA
線程CPU負載極高,線程ID分別爲:17532
,17535
,17533
,17534
,注意這裏打印出來的線程ID爲十進制的哦!
進程pid
&&線程id
查看線程堆棧信息jstack pid
:查看指定進程中線程的堆棧信息,這個命令最終會打印出指定進程的線程堆棧信息,而實際線上狀況發生時,咱們應當把快速把堆棧信息輸出到日誌文本中,保留日誌信息,而後迅速先重啓服務,達到臨時緩解服務器壓力的目的。jstack 17499 > ./threadDump.log
:將線程堆棧信息輸出到當前目錄下的 threadDump.log 文件。注意:jstack 打印出的線程id號爲十六進制,而 top 命令中打印出來的線程號爲十進制,須要進行轉換後,定位指定線程的堆棧信息
這裏分析日誌文件後,過濾出四個線程堆棧信息以下圖:
從這四個線程執行的堆棧信息,很明顯的看出:致使CPU飆升的程序正在執行 HashMap 的 put 操做。
友情提示:測試代碼最好不要在公司的線上環境作測試哦!
更多原創文章會在公衆號第一時間推送,歡迎掃碼關注 張少林同窗