作者 | 于爽 中原銀行系統研發工程師,目前在技術平台室中間件小組從事分布式緩存、消息隊列等相關工作。
【Arthas 官方社群正在舉行征文活動,參加即有獎品拿哦~
點選投稿】
Arthas 是一款 Java應用開源診斷工具,由于其強大的問題排查及診斷能力,自其開源以來廣受開發者的關注和使用,多次登頂 GitHub Trending,并得到國内多家技術媒體的推薦分享。
一. 定制化功能改造
Arthas 可以通過簡單的指令互動模式,接入運作的 JVM,快速定位和診斷線上程式運作問題。在不重新開機服務的情況下,實時、動态的修改相關 code,并實時生效。具體工作原理如下:
1. 連接配接JVM:通過attach機制,通過attach pid連接配接正在運作的JVM;
2. 檢視及修改JVM位元組碼:通過instrument技術對運作中的JVM附加或修改位元組碼來實作增強的邏輯。
2018 年底,中原銀行開始投入人員對 Arthas 進行調研,在開源社群了解了主要功能,并通過閱讀 Arthas 工程大綱,明晰整體工程結構,整個執行過程如下:Arthas 底層調用 rt.jar 包的 ManagementFactory 擷取整個 jvm 内部資訊,通過指令內建與後端互動,執行,傳回結果,整個工程簡單清晰,容易上手。
2019 年初,中原銀行技術團隊開始使用推廣 Arthas 定位和診斷線上問題。
出于保護客戶敏感資訊的嚴格要求,同時切實保障生産環境業務系統的穩定運作,我們對 Arthas 的部分功能進行了定制化改造,對一些指令進行了隐藏:
1. watch:watch方法可以在沒有列印日志的情況下,看到方法的入參和傳回值,有可能暴露客戶的敏感資訊;
2. mc、redefine:mc組合rdefine可以對代碼進行熱更新,不能滿足我行生産運作管理規範要求。
同時,出于使用需要,定制化開發了 gc 等指令:
1. gc:實時動态展示年輕代,年老代垃圾百分比,回收次數及耗時等情況。
下一步,我行計劃在全部開發測試環境、部分生産環境推廣使用 Arthas 來進行問題排查與定位診斷。同時采用内部技術分享的形式向行内應用開發團隊普及推廣 Arthas 的使用。
二. 重點使用功能
除了日常問題排查使用到的方法外,Arthas 還有一些強大的功能,深受中原銀行技術團隊喜愛。
1.target-ip
# target-ip 為指定綁定的IP,如果不指定IP,Arthas隻listen 127.0.0.1,是以如果想從遠端連接配接,則可以使用 --target-ip參數指定listen的IP
java -jar arthas-boot.jar --target-ip IP
綁定遠端通路IP後,可以在通過telnet或http的方式遠端連接配接 Arthas 進行問題排查。
web端通路位址:ip:8563
/telnet通路:ip:3658
當線上應用出現問題時,可以将問題機器隔離起來,通過Arthas在啟動時指定target-ip,多方技術人員可同時通過遠端連接配接進行問題排查。
2.trace
# 檢視方法内部調用路徑,并輸出方法路徑上的每個節點上耗時
trace ClassName methodName
使用 trace 指令可以一層一層追蹤耗時在哪裡 ,在進行性能調優的時候十分有效。
3.ognl
ognl 是應用于 Java 中的一個開源的表達式語言,作用是對資料進行通路,它擁有類型轉換、通路對象方法、操作集合對象等功能,通過 ognl 可以完成一些列強大的操作。
- 執行靜态方法
# 使用ognl調用靜态方法
ognl “@類名@方法名(參數)”
- 擷取靜态屬性
# 使用ognl擷取靜态屬性
ognl “@類名@屬性名”
- 示例:修改日志等級
# 查找目前類的classloader hashcode
sc -d 類名 | grep classLoaderHash
# 用OGNL擷取logger
ognl -c ***** '@類名@logger'
# 單獨設定該類的logger level
ognl -c ***** '@類名@logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)'
#全局設定logger level
ognl -c ***** '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
4.gc
gc 是我行定制化開發的功能,源自于 jstat -gcutil pid timeinterval 指令,其中 pid 可以從 Arthas 中擷取,timeinterval(機關為毫秒)表示 gc 每次時間間隔,預設為 1s。
# 檢視應用gc情況(timeinterval表示間隔時間,機關毫秒,預設為1S)
gc -i timeinterval -n 5
三. 應用實踐案例
下面記錄一些我行 Arthas 應用實踐案例(由于行内代碼保密性要求,下文所示案例均為場景複現所寫示例代碼)
案例一:系統 CPU 使用率高
問題描述:業務人員回報背景管理系統其中一個頁面響應時間很長,登入伺服器上發現 CPU 使用率較高,達到 80% 左右。
1. 啟動 Arthas,附加到對應的 java 程序
注意:Arthas 啟動時要使用與 Java 程序相同的啟動使用者。
# 啟動Arthas
java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.2.0
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 11360 org.gradle.launcher.daemon.bootstrap.GradleDaemon
[2]: 12196 com.durian.ddp.Application
# 選擇要附加的java程序編号
2
...
2. thread 指令檢視 CPU 使用率高的線程
啟動 Arthas,附加到對應的 java 程序,執行 thread -n 5 檢視 CPU 使用率最高的 5 個線程的堆棧。
# 檢視CPU使用率最高的5個線程
thread -n 5
at ***.TreeUtil.findMenuChildren(TreeUtil.java:94)
at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
at ***.TreeUtil.recursiveTree(TreeUtil.java:74)
at ***.getOwnerDeparmentTree(DepartmentServiceImpl.java:550)
...
at ***.TreeUtil.findMenuChildren(TreeUtil.java:94)
at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
at ***.TreeUtil.recursiveTree(TreeUtil.java:74)
at ***.getOwnerDeparmentTree(DepartmentServiceImpl.java:550)
...
...
3. 通過 monitor 指令檢視方法的調用次數與耗時
通過 thread 指令已經定位到 CPU 主要消耗在
TreeUtil
的
findMenuChildren
方法上,通過 monitor 指令檢視方法的具體調用次數與耗時。
# 5s為一個統計周期,統計TreeUtil中findMenuChildren方法的耗時
monitor -c 5 ***.TreeUtil findMenuChildren
通過 monitor 指令可以明确該方法單次調用平均耗時為 17 ~ 20ms,但是調用次數多,是以整體上頁面響應慢。
4. 通過 jad 指令反編譯 TreeUtil 類,檢視源碼
[arthas@12196]$ jad com.durian.ddp.utils.TreeUtil
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@244038d0
Location:
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* ***.ResourceTreeVo
*/
...
public class TreeUtil {
public static ResourceTreeVo findMenuChildren(ResourceTreeVo resourceTreeVo, List<ResourceTreeVo> treeNodes) {
for (ResourceTreeVo resource : treeNodes) {
if (!resourceTreeVo.getResourceId().equals(resource.getResourceParentId())) continue;
if (resourceTreeVo.getChildResourceVo() == null) {
resourceTreeVo.setChildResourceVo(new ArrayList());
}
resourceTreeVo.getChildResourceVo().add(TreeUtil.findMenuChildren(resource, treeNodes));
}
return resourceTreeVo;
}
public static List<ResourceTreeVo> recursiveTree(List<ResourceTreeVo> list) {
ArrayList<ResourceTreeVo> trees = new ArrayList<ResourceTreeVo>();
for (ResourceTreeVo treeNode : list) {
if (!StringUtils.isEmpty(treeNode.getResourceParentId())) continue;
trees.add(TreeUtil.findMenuChildren(treeNode, list));
}
return trees;
}
}
通過 jad 指令檢視源碼可以發現,此處的業務邏輯大緻是通過
ResourceTreeVo
對象的
resourceParentId
字段把一個清單建構一個樹。在
findMenuChildren
方法中存在遞歸調用,而且每一次調用都需要周遊整個
ResourceTreeVo
清單來查找子節點,時間複雜度為 O(n)。是以在
ResourceTreeVo
清單元素比較多的時候,會很耗時。
5. 解決問題
定位到問題就友善解決了,可以通過提前基于 list 建構一個
parentId->List<ResourceTreeVo>
的 map,每個節點查找子節點清單的時候可以從 map 中擷取。這樣整個建構樹的時間算法為 O(n)。
案例二:應用線程連接配接數異常
問題描述:伺服器句柄數耗盡,檢視發現某個應用占用句柄數較多。
1. thread 指令檢視線程資訊
啟動 Arthas,附加到對應的 java 程序,執行 thread 檢視線程情況。
# 檢視線程情況
thread
看到有大量的
MasterListener-mymaster-*
線程處于連接配接狀态,一直沒有釋放。
# 選擇其中一個線程檢視堆棧資訊
thread id
發現這些線程是由
redis.clients.JedisSentinelPool$MasterListener
産生的,那麼接下來就來檢視一下
JedisSentinelPool$MasterListener
的調用情況。
2. stack 指令檢視堆棧資訊
stack redis.clients.jedis.JedisSentinelPool$MasterListener
觸發一次應用請求,列印出如下堆棧資訊:
通過調用鍊定位到
RedisUtil
類,發現每次請求否會觸發
RedisUtil.getJedis
方法調用
JedisSentinelPool$MasterListener
,那麼下一步我們反編譯一下
REedisUtil
類。
3. jad 指令反編譯檢視代碼
# 反編譯RedisUtil類
jad cn.com.zybank.testredis.starter.RedisUtil
檢視
getJedis
方法,發現
getJedis
每調用一次都會建立一個
JedisSentinelPool
。
通過分析發現,每次使用 redis 時,都會調用
getJedis
方法建立一個新的
JedisSentinelPool
,進而啟動一個
MasterListener-mymaster-*
線程,由于該線程會一直保持監聽,不會自動釋放,故随着應用請求的增加線程數一直增加進而導緻連接配接數占滿。
4. 解決問題
針對該問題,隻需建立一個全局的
JedisSentinelPool
,每次擷取 redis 連接配接時都從該連接配接池擷取即可,這裡不再對代碼進行展示。
四.總結建議
我行在使用 Arthas 以前,線上問題排查往往需要查網絡、jps、jstack、jmap、jhat、jstat、hprof 等一系列操作,費時費力。目前,大多數的常見問題都可以使用 Arthas 輕松定位,迅速解決。
一鍵安裝并啟動 Arthas
- 方式一:通過 Cloud Toolkit 實作 Arthas 一鍵遠端診斷
Cloud Toolkit 是阿裡雲釋出的免費本地 IDE 插件,幫助開發者更高效地開發、測試、診斷并部署應用。通過插件,可以将本地應用一鍵部署到任意伺服器,甚至雲端(ECS、EDAS、ACK、ACR 和 小程式雲等);并且還内置了 Arthas 診斷、Dubbo工具、Terminal 終端、檔案上傳、函數計算 和 MySQL 執行器等工具。不僅僅有 IntelliJ IDEA 主流版本,還有 Eclipse、Pycharm、Maven 等其他版本。
推薦使用 IDEA 插件下載下傳 Cloud Toolkit 來使用 Arthas:
http://t.tb.cn/2A5CbHWveOXzI7sFakaCw8- 方式二:直接下載下傳
位址:
https://github.com/alibaba/arthas随着我行全面深化使用 Arthas,也發現了一些有待改進提升的功能,希望進一步優化完善。
- 在進行 trace 的時候,隻要調用鍊中有異步,堆棧就會斷掉,無法 trace 到子線程内部,隻能手動逐層跟進 trace,效率較低。
- 希望 tt 指令能夠添加異步開關,如果開關開啟, 那麼 COST 即可顯示異步得到結果的耗時。
截至目前,Arthas GitHub Star 已經突破 2.4 萬了,希望 Arthas 能夠獲得更多全球開發者的關注和喜愛,也期待更多像 Arthas 一樣的國内優質項目能夠開源。
Arthas 征文活動火熱進行中
Arthas 官方正在舉行征文活動,如果你有:
- 使用 Arthas 排查過的問題
- 對 Arthas 進行源碼解讀
- 對 Arthas 提出建議
- 不限,其它與 Arthas 有關的内容
歡迎參加征文活動,還有獎品拿哦~
“ 阿裡巴巴雲原生 關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的公衆号。”