阿里開(kāi)源的那個(gè)牛X的問(wèn)題排查工具,推出IDEA插件了!
Arthas 對(duì)于很多 Java 開(kāi)發(fā)者來(lái)說(shuō),已經(jīng)不可分割了,在我們?nèi)粘i_(kāi)發(fā)、線上問(wèn)題排查中扮演了非常重要的角色。作為小開(kāi)發(fā)的我,日常需要排查線上運(yùn)營(yíng)同學(xué)提的各種 bug、各種線上問(wèn)題診斷、日常運(yùn)維、線上問(wèn)題優(yōu)化等等。
在剛來(lái)公司時(shí),我是比較恐懼運(yùn)維任務(wù)的,代碼不熟悉、各種問(wèn)題比較多...幾乎崩潰的狀態(tài),運(yùn)維的一周基本上沒(méi)有干活,完全是全身心投入到運(yùn)維的任務(wù)中,排查問(wèn)題效率低下。
由于深刻體驗(yàn)到了這種奔潰,我一直想改變這種狀態(tài),直到 Arthas 的開(kāi)源,讓我在這種崩潰的狀態(tài)中減輕了不少負(fù)擔(dān),同時(shí)也讓我成為了同事們咨詢 Arthas 排查問(wèn)題的小幫手~~ 雖然使用 Arthas 特別方便,但在此過(guò)程中也遇到一些問(wèn)題,作為問(wèn)題咨詢小幫手也感到有點(diǎn)不方便,因此才造就了 Arthas idea 插件的誕生。
目前 Arthas 官方的工具還不夠簡(jiǎn)單,需要記住一些命令,特別是一些擴(kuò)展性特別強(qiáng)的高級(jí)語(yǔ)法,比如 ognl 獲取 spring context 為所欲為,watch、trace 不夠簡(jiǎn)單,需要構(gòu)造一些命令工具的信息,因此只需要一個(gè)能夠簡(jiǎn)單處理字符串信息的插件即可使用。
當(dāng)在處理線上問(wèn)題的時(shí)候需要最快速、最便捷的命令,因此 Idea Arthas plugin 插件還是有存在的意義和價(jià)值的。---這個(gè)是最初編寫(xiě)這個(gè)插件的最核心的理由。
Arthas IDEA plugin 實(shí)踐
Arthas 的功能點(diǎn)非常的多(詳見(jiàn)下方大圖),這里就不一一的講解了,可以參考使用文檔 ,不過(guò)最近一直在更新,使用文檔中的命令名稱(chēng)可能有變化。
插件安裝
下載 arthas idea 插件: https://plugins.jetbrains.com/ ... -idea
- Mac:
Preferences
->Plugins
- Windows:
Settings
->Plugins
Install Plugin form Disk... 導(dǎo)入插件
安裝之后重啟 IDEA 就可以愉快的使用啦!
獲取 static 變量
首先要獲取 classloader 的 hash 值,然后獲取命令,這個(gè)是一個(gè)交互流程需要連貫性,后續(xù)只要是 static 的通過(guò) static spring context 的都需要有這個(gè)交互的流程,連貫的,都在同一個(gè)界面進(jìn)行操作.粘貼執(zhí)行,然后獲取結(jié)果即可。
這里的 classloader 的 hash 值緩存起來(lái)的
最后合并的腳本如下。
- yaml
- ognl -x 3 '@com.wangji92.arthas.plugin.demo.controller.StaticTest@INVOKE_STATIC_NAME' -c 316bc132
反射設(shè)置 static field
通過(guò)反射進(jìn)行設(shè)置 static field ,參考: https://github.com/WangJi92/ar ... ues/1
填寫(xiě)你想要修改的值、默認(rèn)根據(jù)類(lèi)型設(shè)置默認(rèn)值 Str->"" Long -> 0L 等等。
- yaml
- ognl -x 3 '#field=@com.wangji92.arthas.plugin.demo.controller.StaticTest@class.getDeclaredField("INVOKE_STATIC_FINAL"),#modifiers=#field.getClass().getDeclaredField("modifiers"),#modifiers.setAccessible(true),#modifiers.setInt(#field,#field.getModifiers() & ~@java.lang.reflect.Modifier@FINAL),#field.setAccessible(true),#field.set(null,"設(shè)置的值")' -c 316bc132
Spring Context Invoke
通過(guò) spring context 進(jìn)行調(diào)用 bean 的方法、字段的內(nèi)容。
Static Spring Context Invoke Method Field
首頁(yè)要設(shè)置一下 static spring context 的路徑。
由于都是通過(guò) ognl 調(diào)用 static 的 spring context 都需要 classloader,這個(gè)就是配置的 spring conetxt 的地址信息:
@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context 參考 demo 就需要配置這個(gè)地址。
- yaml
- ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getBean("commonController").getRandomInteger()' -c 316bc132
Watch Spring Context Invoke Method Field
watch 這個(gè)是支持在 spring mvc 場(chǎng)景,通過(guò) watch 間接的獲取 spring context,需要出發(fā)一次接口的調(diào)用,可以參考 : https://github.com/WangJi92/ar ... ues/5
- yaml
- watch -x 3 -n 1 org.springframework.web.servlet.DispatcherServlet doDispatch '@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(params[0].getServletContext()).getBean("commonController").getRandomInteger()'
TimeTunnel Spring Context Invoke Method Field
這個(gè)是參考了橫云斷嶺的 arthas 通過(guò) tt 獲取 spring context 為所欲為 ,可以參考這個(gè)文檔: https://github.com/WangJi92/ar ... ues/4 這里做了些什么?將整個(gè)連貫了起來(lái),不需要記住參數(shù)信息,然后對(duì)于調(diào)用的參數(shù)進(jìn)行簡(jiǎn)單的默認(rèn)封裝,復(fù)雜的參數(shù)場(chǎng)景不支持,需要手動(dòng)拼接。
記錄獲取 spring context
- yaml
- tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
然后根據(jù)這個(gè) target 獲取 spring context 調(diào)用方法
- yaml
- tt -w 'target.getApplicationContext().getBean("commonController").getRandomInteger()' -x 3 -i 1000
獲取某個(gè) spring 環(huán)境變量
獲取 spring 環(huán)境變量這里依托,static spring context ,當(dāng)然這個(gè) watch 、和 tt 獲取 spring context 的場(chǎng)景也是支持的。在線上環(huán)境、測(cè)試環(huán)境程序多復(fù)雜,你怎么知道環(huán)境中的變量一定是你配置的?在 nacos 等等配置中心的場(chǎng)景,估計(jì)手速慢了一點(diǎn)點(diǎn),可能就沒(méi)有上去,這個(gè)時(shí)候就有這種需求獲取當(dāng)前的環(huán)境變量。選中變量,然后右鍵執(zhí)行命令。由于使用靜態(tài)的 static spring context 依然需要 classloader 的值。這里已經(jīng)基本上是 arthas 的上層應(yīng)用啦。
- yaml
- ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getEnvironment().getProperty("custom.name")' -c 316bc132
獲取全部的 spring 環(huán)境變量
比較優(yōu)先級(jí),最前面的一定優(yōu)先級(jí)最高,你一定被 spring 的各種優(yōu)先級(jí)順序搞暈了,那么怎么辦呢?arthas idea plugin 支持獲取當(dāng)前的全部的環(huán)境變量,依次打印出來(lái), 這樣就可以了解優(yōu)先級(jí) ,特別是接入了 nacos、diamond 等遠(yuǎn)程的配置中心,實(shí)現(xiàn)不一樣肯定更暈了。
參考文檔: https://blog.csdn.net/xunjiush ... 50139
- yaml
- ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#allProperties={},#standardServletEnvironment=#propertySourceIterator=#springContext.getEnvironment(),#propertySourceIterator=#standardServletEnvironment.getPropertySources().iterator(),#propertySourceIterator.{#key=#this.getName(),#allProperties.add(" "),#allProperties.add("------------------------- name:"+#key),#this.getSource() instanceof java.util.Map ?#this.getSource().entrySet().iterator.{#key=#this.key,#allProperties.add(#key+"="+#standardServletEnvironment.getProperty(#key))}:#{}},#allProperties' -c 316bc132
TimeTunnel Tt
方法執(zhí)行數(shù)據(jù)的時(shí)空隧道,記錄下指定方法每次調(diào)用的入?yún)⒑头祷匦畔?,并能?duì)這些不同的時(shí)間下調(diào)用進(jìn)行觀測(cè)(可以重新觸發(fā),周期觸發(fā),唯一缺點(diǎn)對(duì)于 ThreadLocal 信息丟失[隱含參數(shù)]、引用對(duì)象數(shù)據(jù)變更無(wú)效),這個(gè)方便二次觸發(fā),特別是自己調(diào)試不方便的情況下記錄下來(lái),二次觸發(fā)、周期觸發(fā),不過(guò)自從段嶺大神 tt 為所欲為之后都被帶偏了。這里 arthas 插件做了一些什么?增加了二次觸發(fā)的一些常用的命令,不讓使用者愁于記憶,整個(gè)過(guò)程更加的具有連貫性。
stack 堆棧
獲取方法從哪里執(zhí)行的調(diào)用棧(用途:源碼學(xué)習(xí)調(diào)用堆棧,了解調(diào)用流程) 這個(gè)是非常好用的功能,對(duì)于喜歡樂(lè)于排查問(wèn)題的小伙伴真是福音,arthas idea 插件只是修改的命令的集成,之前也處理自己編碼過(guò)程中的問(wèn)題,源碼、問(wèn)題排查技巧-Java Debug and Arthas: https://blog.csdn.net/u0128819 ... 91529
- yaml
- stack com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger -n 5
Decompile Class Jad
反編譯方法、類(lèi)的源碼, 有時(shí)候需要查看提交的代碼是否上線呢?這個(gè)功能就非常的友好。
參考文檔: https://github.com/WangJi92/arth!
as-idea-plugin/issues/2
- yaml
- jad --source-only com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger
watch、trace
增加了默認(rèn)參數(shù)、默認(rèn)展開(kāi)的層級(jí)限制次數(shù),使用者不用知道這些核心的參數(shù),簡(jiǎn)單的使用就好了,要使用更加的高級(jí)的自己help 一下就知道了。
- yaml
- watch com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger '{params,returnObj,throwExp}' -n 5 -x 3
- trace com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger -n 5
trace -E(層級(jí)的打印 trace)
trace -E 自己構(gòu)造起來(lái)非常的麻煩,通過(guò)界面操作簡(jiǎn)化了一下,需要觀察多個(gè)類(lèi)、多個(gè)方法的場(chǎng)景。選擇你需要的場(chǎng)景繼續(xù)添加即可。
- yaml
- trace -E com.wangji92.arthas.plugin.demo.controller.CommonController|com.wangji92.arthas.plugin.demo.service.ArthasTestService traceE|doTraceE -n 5
Heap Dump
打印堆棧,有點(diǎn)類(lèi)似 jmap -dump:format=b,file=/temp/dump.hprof pid 下載下來(lái)使用 MAT 分析即可。
- yaml
- heapdump /tmp/dump.hprof 打印堆棧信息
特殊用法鏈接
這個(gè)必須要說(shuō)一下,這個(gè)特殊用法的鏈接在線上自己束手無(wú)措的時(shí)候可以查看一下,非常有用。
對(duì)于通過(guò) spring context 調(diào)用方法說(shuō)明
通過(guò) spring context 調(diào)用復(fù)雜的方法其實(shí)是不支持的,由于這個(gè)操作起來(lái)不方便,還是必須手工處理一下。
比如這里的 Map names 的處理方式可以借鑒一下子。
更多可以參考 demo: https://github.com/WangJi92/arthas-plugin-demo
- yaml
- /**
- * 復(fù)雜參數(shù)調(diào)用 場(chǎng)景
- * static spring context
- * ognl -x 3 '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getBean("commonController").complexParameterCall(#{"wangji":#user})' -c e374b99
- *
- * watch get spring context 備注 需要調(diào)用一次方法
- * watch -x 3 -n 1 org.springframework.web.servlet.DispatcherServlet doDispatch '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(params[0].getServletContext()).getBean("commonController").complexParameterCall(#{"wangji":#user})'
- *
- * tt get spring context ,only first get time index ok
- * tt -w '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),target.getApplicationContext().getBean("commonController").complexParameterCall(#{"wangji":#user})' -x 3 -i 1000
- * @return
- */
- @RequestMapping("complexParameterCall")
- @ResponseBody
- public String complexParameterCall(@RequestBody Map<String, User> names) {
- if (names == null) {
- return "EMPTY";
- }
- return names.toString();
- }
總結(jié)
本文簡(jiǎn)單介紹了 Arthas IDEA 插件的安裝與使用技巧,該插件解放了大家對(duì)于 Arthas 使用的一些記憶、機(jī)械性的重復(fù)工作,歡迎大家試用!
Arthas 官方舉行了征文活動(dòng),于 3 月 26 日—— 4 月 26 日舉辦,如果你有:
- 使用 Arthas 排查過(guò)的問(wèn)題
- 對(duì) Arthas 進(jìn)行源碼解讀
- 對(duì) Arthas 提出建議
- 不限,其它與 Arthas 有關(guān)的內(nèi)容