一次線上問題排查所引起的思考

前言

以前或多或少分享過一些內存模型對象建立之類的內容,其實大部分人看完都是懵懵懂懂,也不知道這些的實際意義。java

直到有一天你會碰到線上奇奇怪怪的問題,如:git

  • 線程執行一個任務遲遲沒有返回,應用假死。
  • 接口響應緩慢,甚至請求超時。
  • CPU 高負載運行。

這類問題並不像一個空指針、數組越界這樣明顯好查,這時就須要剛纔提到的內存模型、對象建立、線程等相關知識結合在一塊兒來排查問題了。github

正好此次藉助以前的一次生產問題來聊聊如何排查和解決問題。shell

生產現象

首先看看問題的背景吧:數據庫

我這實際上是一個定時任務,在固定的時間會開啓 N 個線程併發的從 Redis 中獲取數據進行運算。數組

業務邏輯很是簡單,但應用通常涉及到多線程以後再簡單的事情都要當心對待。網絡

果不其然此次就出問題了。多線程

現象:本來只須要執行幾分鐘的任務執行了幾個小時都沒退出。翻遍了全部的日誌都沒找到異常。併發

因而便開始定位問題之路。運維

定位問題

既然沒辦法直接從日誌中發現異常,那就只能看看應用到底在幹嗎了。

最多見的工具就是 JDK 自帶的那一套。

此次我使用了 jstack 來查看線程的執行狀況,它的做用其實就是 dump 當前的線程堆棧。

固然在 dump 以前是須要知道我應用的 pid 的,可使用 jps -v 這樣的方式列出全部的 Java 進程。

固然若是知道關鍵字的話直接使用 ps aux|grep java 也是能夠的。

拿到 pid=1523 了以後就能夠利用 jstack 1523 > 1523.log 這樣的方式將 dump 文件輸出到日誌文件中。

若是應用簡單不復雜,線程這些也比較少其實能夠直接打開查看。

但複雜的應用導出來的日誌文件也比較大仍是建議用專業的分析工具。

我這裏的日誌比較少直接打開就能夠了。

由於我清楚知道應用中開啓的線程名稱,因此直接根據線程名就能夠在日誌中找到相關的堆棧:

因此一般建議你們線程名字給的有意義,在排查問題時頗有必要。

其實其餘幾個線程都和這裏的堆棧相似,很明顯的看出都是在作 Redis 鏈接。

因而我登陸 Redis 查看了當前的鏈接數,發現已經很是高了。

這樣 Redis 的響應天然也就變慢了。

接着利用 jps -v 列出了當前因此在跑的 Java 進程,果不其然有好幾個應用都在查詢 Redis,並且都是併發鏈接,問題天然就找到了。

解決辦法

因此問題的主要緣由是:大量的應用併發查詢 Redis,致使 Redis 的性能下降。

既然找到了問題,那如何解決呢?

  • 減小同時查詢 Redis 的應用,分開時段下降 Redis 的壓力。
  • 將 Redis 複製幾個集羣,各個應用分開查詢。可是這樣會涉及到數據的同步等運維操做,或者由程序了進行同步也會增長複雜度。

目前咱們選擇的是第一個方案,效果很明顯。

本地模擬

上文介紹的是線程相關問題,如今來分析下內存的問題。

以這個類爲例:

https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/oom/heap/HeapOOM.java

public class HeapOOM {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(10) ;
        while (true){
            list.add("1") ;
        }
    }
}

啓動參數以下:

-Xms20m
-Xmx20m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/xx/Documents

爲了更快的突出內存問題將堆的最大內存固定在 20M,同時在 JVM 出現 OOM 的時候自動 dump 內存到 /Users/xx/Documents(不配路徑則會生成在當前目錄)。

執行以後果不其然出現了異常:

同時對應的內存 dump 文件也生成了。

內存分析

這時就須要相應的工具進行分析了,最經常使用的天然就是 MAT 了。

我試了一個在線工具也不錯(文件大了就不適合了):

http://heaphero.io/index.jsp

上傳剛纔生成的內存文件以後:

由於是內存溢出,因此主要觀察下大對象:

也有相應提示,這個頗有可能就是內存溢出的對象,點進去以後:

看到這個堆棧其實就很明顯了:

在向 ArrayList 中不停的寫入數據時,會致使頻繁的擴容也就是數組複製這些過程,最終達到 20M 的上限致使內存溢出了。

更多建議

上文說過,一旦使用了多線程,那就要格外當心。

如下是一些平常建議:

  • 儘可能不要在線程中作大量耗時的網絡操做,如查詢數據庫(能夠的話在一開始就將數據從從 DB 中查出準備好)。
  • 儘量的減小多線程競爭鎖。能夠將數據分段,各個線程分別讀取。
  • 多利用 CAS+自旋 的方式更新數據,減小鎖的使用。
  • 應用中加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp 參數,在內存溢出時至少能夠拿到內存日誌。
  • 線程池監控。如線程池大小、隊列大小、最大線程數等數據,可提早作好預估。
  • JVM 監控,能夠看到堆內存的漲幅趨勢,GC 曲線等數據,也能夠提早作好準備。

總結

線上問題定位須要綜合技能,因此是須要一些基礎技能。如線程、內存模型、Linux 等。

固然這些問題沒有實操過都是紙上談兵;若是第一次碰到線上問題,不要慌張,反而應該慶幸解決以後你又會習得一項技能。

號外

最近在總結一些 Java 相關的知識點,感興趣的朋友能夠一塊兒維護。

地址: https://github.com/crossoverJie/Java-Interview

相關文章
相關標籤/搜索