線上CPU100%?看看這篇是怎麼排查的!

前言

做爲後端開發工程師,當收到線上服務器CPU負載太高告警時,你會這麼作?重啓服務,忽略告警?不過在我看來一個合格的工程師是必定要定位到具體問題所在的,從而 fix 它。下面記錄一下線上服務器 CPU 負載太高排查過程,把排查流程理清楚,之後遇到問題將會迅速定位到問題所在,快速解決。java

什麼樣的場景會致使線上CPU負載太高?

代碼層面常見的場景有:linux

  1. 程序陷入死循環,不停地消耗CPU
  2. 線程死鎖,線程相互等待,致使假死狀態,不停地消耗CPU

程序死循環場景

這裏使用 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 指令後,就開始實時監控當前系統的負載信息,監控到的負載信息以下圖所示:測試

從圖中的監控信息能夠快速大體的瞭解到,PID17499的進程CPU負載高達328+%,是一個 JAVA 程序。簡單介紹下監控信息以下:this

  • PID:進程的ID  
  • USER:進程全部者
  • PR:進程的優先級別,越小越優先被執行
  • VIRT:進程佔用的虛擬內存
  • RES:進程佔用的物理內存
  • SHR:進程使用的共享內存
  • S:進程的狀態。S表示休眠,R表示正在運行,Z表示僵死狀態,N表示該進程優先值爲負
  • %CPU:進程佔用CPU的使用率
  • %MEM:進程使用的物理內存和總內存的百分比
  • TIME+:該進程啓動後佔用的總的CPU時間,即佔用CPU使用時間的累加值

在監控頁面下 按鍵盤數字 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 操做。

友情提示:測試代碼最好不要在公司的線上環境作測試哦!

更多原創文章會在公衆號第一時間推送,歡迎掃碼關注 張少林同窗

張少林同窗.jpg

相關文章
相關標籤/搜索