中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

利用 Arthas 精準定位 Java 應用 CPU 負載過高問題

發布時間:2020-08-10 23:51:03 來源:ITPUB博客 閱讀:183 作者:阿里巴巴云原生 欄目:云計算

Arthas 官方社區正在舉行征文活動,參加即有獎品拿哦~ 點擊投稿

作者 | 張云翔

最近我們線上有個應用服務器有點上頭,CPU總能跑到99%,我尋思著它流量也不大啊,為啥能把自己整這么累?于是我登上這臺服務器,看看它到底在干啥!

以前碰到類似問題,可能會考慮使用 top -Hpjstack 命令去排查,雖然能大致定位到問題范圍,但有效信息還是太少了,多數時候還是要靠猜。今天向大家推薦一款更高效更精準的工具: Arthas!Arthas 是 Alibaba 開源的 Java 診斷工具,能夠幫助我們快速定位線上問題。基本的安裝使用可以參考官方文檔: https://alibaba.github.io/arthas

這次我們利用它來排查 CPU 負載高的問題。CPU 負載過高一般是某個或某幾個線程有問題,所以我們嘗試使用第一個命令: thread,這個命令會顯示所有線程的信息,并且把 CPU 使用率高的線程排在前面。

[arthas@384]$ thread
Threads Total: 112, NEW: 0, RUNNABLE: 26, BLOCKED: 0, WAITING: 31, TIMED_WAITING: 55, TERMINATED: 0
ID  NAME    STATE    %CPU  TIME
108 h..ec-0 RUNNABLE  51   4011:48     
100 h..ec-2 RUNNABLE  48   4011:51
...

為了方便閱讀,刪掉了一些不重要的信息

可以看到,CPU 資源幾乎被前兩個線程占滿,并且已經執行了 4000 多分鐘,我們服務器也就啟動了兩天,可見這兩天它們是一刻也沒閑著!那它們究竟在干什么呢?我們可以使用命令: thread id,查看線程堆棧。

[arthas@384]$ thread 108
"http-nio-7001-exec-10" Id=108 cpuUsage=51% RUNNABLE
    at c.g.c.c.HashBiMap.seekByKey(HashBiMap.java)
    at c.g.c.c.HashBiMap.put(HashBiMap.java:270)
    at c.g.c.c.HashBiMap.forcePut(HashBiMap.java:263)
    at c.y.r.j.o.OaInfoManager.syncUserCache(OaInfoManager.java:159)

也可以使用 thread -n 3 命令打印出 CPU 占比最高的前三個線程,這差不多是 > top -Hp> & > printf> & > jstack> 三令合一的效果了>

可以看到,這個線程一直在執行 HashBiMap.seekByKey 方法(可以重復執行幾次 thread id 確保該線程執行的方法沒有時刻在變化),造成這個問題一般有兩個原因:

  1. seekByKey 方法被循環調用
  2. seekByKey 內部有死循環

先看一下是不是第一種,我們使用 tt 命令監聽一下這個方法的調用情況:

tt -t com.google.common.collect.HashBiMap seekByKey -n 100

注意:在線上執行這個命令的時候,一定要記得加上 -n 參數,否則線上巨大的流量可能會瞬間撐爆你的 JVM 內存執行結果顯示, seekByKey 方法并沒有被一直調用,那大概率是 seekByKey 方法內部有死循環。看下這個方法內部的邏輯,我們可以使用 jad com.google.common.collect.HashBiMap seekByKey 命令反編譯這個方法,這樣做的好處是顯得比較高端,不過我還是打算直接找到源碼,說不定還有注釋。源碼如下:

private BiEntry<K, V> seekByKey(@Nullable Object key, int keyHash) {
    for (BiEntry<K, V> entry = hashTableKToV[keyHash & mask];
        entry != null;
        entry = entry.nextInKToVBucket) {
      if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) {
        return entry;
      }
    }
    return null;
  }

然后并沒有注釋,還好這個方法邏輯比較簡單,也很容易看懂。

  1. 通過 hash 找到 bucket,每個 bucket 是一個鏈表;
  2. 遍歷鏈表,找到這個 key 對應的 entry。這里要留意下 entry 的下一個節點是 nextInKToVBucket,后文中會用到。

發生了死循環,我們猜想可能是因為這個鏈表有環路。那么有沒有辦法驗證這個猜想呢?答案是有!那么如何驗證呢?首先我們要獲得這個 HashBiMap 對象,以便于查詢對象里的數據。獲得這個對象有很多辦法,比如監聽這個對象的某個方法,然后主動觸發這個方法。這里向大家介紹一種更為通用的方法,這個方法在 SpringMVC 程序里非常好用。因為我們是 SpringMVC 應用,所有請求都會被 RequestMappingHandlerAdapter 攔截,我們通過 tt 命令,監聽 invokeHandlerMethod 的執行,然后在頁面隨便點點,就會得到以下內容:

[arthas@384]$ tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod -n 10
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 622 ms.
 INDEX    COST(ms)      OBJECT        CLASS             METHOD
------------------------------------------------------------------------------------
 1000     481.203383    0x481eb705    RequestMappingHandlerAdapter    invokeHandlerMethod
 1001     3.432024      0x481eb705    RequestMappingHandlerAdapter    invokeHandlerMethod
...

tt 命令會記錄方法調用時的所有入參和返回值、拋出的異常、對象本身等數據。INDEX 字段代表著一次調用,后續tt還有很多命令都是基于此編號指定記錄操作。

我們可以通過 -i 參數后邊跟著對應的 INDEX 編號查看這條記錄的詳細信息。再通過 -w 參數,指定一個 OGNL 表達式,查找相關對象:

[arthas@384]$ tt -i 1000 -w 'target.getApplicationContext()'
@AnnotationConfigServletWebServerApplicationContext[
    reader=@AnnotatedBeanDefinitionReader[org.springframework.context.annotation.AnnotatedBeanDefinitionReader@50294e97],
    scanner=@ClassPathBeanDefinitionScanner[org.springframework.context.annotation.ClassPathBeanDefinitionScanner@5eeeaae2],
    annotatedClasses=@LinkedHashSet[isEmpty=true;size=0],
    basePackages=null,

OGNL 使用文檔: https://commons.apache.org/proper/commons-ognl/language-guide.html

Arthas 會把當前執行的對象放到 target 變量中,通過 target.getApplicationContext() 就得到了 SpringContext 對象,然后,我們就可以為所欲為了!

接下來我們需要用 OGNL 寫一個函數,來實現鏈表的環路檢測,在 OGNL 里寫一段環路檢測代碼里是不太容易的,這里我用了一個取巧的偽實現。

#loopCnt=0,
#foundCycle=:[ #this == null ? false :
    #loopCnt > 50 ? true :
        (
            #loopCnt = #loopCnt + 1,
            #foundCycle(#this.nextInKToVBucket)
        )]

因為我知道一個 bucket 不太可能有 50 個以上的節點,所以就通過遍歷次數是否大于 50 來判斷是否有環路。

完整的命令:

tt -i 1000 -w ‘target.getApplicationContext().getBean(“oaInfoManager”).userCache.entrySet().{delegate}.{^ #loopCnt = 0,#foundCycle = :[ #this == null ? false : #loopCnt > 50 ? true : (#loopCnt = #loopCnt + 1, #foundCycle(#this.nextInKToVBucket))], #foundCycle(#this)}.get(0)’ -x 2

命令解析:

  1. 獲取 HashBiMap 對象: target.getApplicationContext().getBean("oaInfoManager").userCache
  2. 遍歷所有 entry,取出第一個有環路的 entry
  3. -x 參數指定展開層級,我們需要將這個參數設置的比環要大一些,才能確保可以發現環路。這里我們的環路非常小,所以設置成了 2

執行結果如下:

@BiEntry[
    key=@String[張三],
    value=@Long[1111],
    nextInKToVBucket=@BiEntry[
        key=@String[李四],
        value=@Long[2222],
        nextInKToVBucket=@BiEntry[張三=1111]
    ]
]

可以看到是有 張三->李四->張三 這樣一個環路。至此,造成死循環的原因確定了下來。結合兩個線程幾乎同時啟動,又同時在執行 HashBiMap.forcePut 方法,容易想到是因為并發導致了數據的不一致,這一點也可以驗證,不過由于篇幅有限,這里就不再贅述。找到了問題,就成功了 99%,解決這個問題的方法非常簡單,就是對 syncUserCache 方法加一個 synchronized 關鍵字!

結語

這次遇到的問題并不復雜,用 jstack 命令也可以解決的了。但我們希望通過這樣一個案例,向大家展示 Arthas 一些強大的功能,幫助大家打開思路,未來在遇到更復雜場景時,可以多一些趁手的工具!

Arthas 征文活動火熱進行中

Arthas 官方正在舉行征文活動,如果你有:

  • 使用 Arthas 排查過的問題
  • 對 Arthas 進行源碼解讀
  • 對 Arthas 提出建議
  • 不限,其它與 Arthas 有關的內容

    歡迎參加征文活動,還有獎品拿哦~ 點擊投稿

“ 阿里巴巴云原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦云原生流行技術趨勢、云原生大規模的落地實踐,做最懂云原生開發者的公眾號。”

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

西乌珠穆沁旗| 乃东县| 洛隆县| 乐清市| 闻喜县| 吉木萨尔县| 宝兴县| 石门县| 镶黄旗| 鄂尔多斯市| 依兰县| 武功县| 博罗县| 毕节市| 岗巴县| 赞皇县| 宿迁市| 永昌县| 昆明市| 布拖县| 马公市| 荣成市| 左云县| 武宁县| 辰溪县| 永川市| 叶城县| 铁力市| 万载县| 酒泉市| 平武县| 遵义市| 东阿县| 平顺县| 麟游县| 阜宁县| 故城县| 江北区| 东宁县| 中牟县| 曲沃县|