實操案例
排查函數調用異常
通過curl 請求接口隻能看到傳回異常,但是看不到具體的請求參數和堆棧資訊。
shell@Alicloud:~$ curl http://localhost:61000/user/0
{"timestamp":1655435063042,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
檢視UserController的 參數/異常
在Arthas裡執行:
watch com.example.demo.arthas.user.UserController * '{params, throwExp}'
- 第一個參數是類名,支援通配
- 第二個參數是函數名,支援通配 通路 curl http://localhost:61000/user/0 ,watch指令會列印調用的參數和異常
再次通過curl 調用可以在arthas裡面檢視到具體的異常資訊。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yM2IDM2E2M5UGMwEjNmFmZyYzXwQjM0kDM2EzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
把擷取到的結果展開,可以用-x參數:
watch com.example.demo.arthas.user.UserController * '{params, throwExp}' -x 2
傳回值表達式
在上面的例子裡,第三個參數是傳回值表達式,它實際上是一個ognl表達式,它支援一些内置對象:
- loader
- clazz
- method
- target
- params
- returnObj
- throwExp
- isBefore
- isThrow
- isReturn
比如傳回一個數組:
watch com.example.demo.arthas.user.UserController * '{params[0], target, returnObj}'
條件表達式
watch指令支援在第4個參數裡寫條件表達式,比如:
當通路 user/1 時,watch指令沒有輸出
當通路 user/101 時,watch會列印出結果。
當異常時捕獲
watch指令支援-e選項,表示隻捕獲抛出異常時的請求:
watch com.example.demo.arthas.user.UserController * "{params[0],throwExp}" -e
按照耗時進行過濾
watch指令支援按請求耗時進行過濾,比如:
watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'
熱更新代碼
這個也是真的秀。
通路 http://localhost:61000/user/0 ,會傳回500異常:
shell@Alicloud:~$ curl http://localhost:61000/user/0
{"timestamp":1655436218020,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
通過熱更新代碼,修改這個邏輯。
jad反編譯UserController
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
jad反編譯的結果儲存在 /tmp/UserController.java檔案裡了。
再打開一個Terminal 視窗,然後用vim來編輯/tmp/UserController.java:
vim /tmp/UserController.java
比如當 user id 小于1時,也正常傳回,不抛出異常:
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}
sc查找加載UserController的ClassLoader
[arthas@1266]$ sc -d *UserController | grep classLoaderHash
classLoaderHash 19469ea2
classLoaderHash 是19469ea2,後面需要使用它。
mc
儲存好/tmp/UserController.java之後,使用mc(Memory Compiler)指令來編譯,并且通過-c或者--classLoaderClass參數指定ClassLoader:
mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
[arthas@1266]$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 2879 ms.
也可以通過mc -c <classLoaderHash> /tmp/UserController.java -d /tmp,使用-c參數指定ClassLoaderHash:
mc -c 19469ea2 /tmp/UserController.java -d /tmp
redefine
再使用redefine指令重新加載新編譯好的UserController.class:
[arthas@1266]$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1, classes:
com.example.demo.arthas.user.UserController
熱修改代碼結果
redefine成功之後,再次通路 user/0 ,結果正常
shell@Alicloud:~$ curl http://localhost:61000/user/0
{"id":0,"name":"name0"}
動态更新應用Logger Level
查找UserController的ClassLoader
[arthas@1266]$ sc -d *UserController | grep classLoaderHash
classLoaderHash 19469ea2
用ognl擷取logger
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'
[arthas@1266]$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'
@Logger[
serialVersionUID=@Long[5454405123156820674],
FQCN=@String[ch.qos.logback.classic.Logger],
name=@String[com.example.demo.arthas.user.UserController],
level=null,
effectiveLevelInt=@Integer[20000],
parent=@Logger[Logger[com.example.demo.arthas.user]],
childrenList=null,
aai=null,
additive=@Boolean[true],
loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]
可以知道UserController@logger實際使用的是logback。可以看到
level=null
,則說明實際最終的level是從r
oot logger
裡來的。
單獨設定UserController的logger level
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@[email protected](@ch.qos.logback.classic.Level@DEBUG)'
再次擷取UserController@logger,可以發現已經是DEBUG了。
修改logback的全局logger level
通過擷取root logger,可以修改全局的logger level:
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
擷取Spring Context,在擷取 bean,再調用函數
使用tt指令擷取到spring context
tt即 TimeTunnel,它可以記錄下指定方法每次調用的入參和傳回資訊,并能對這些不同的時間下調用進行觀測。官方tt說明:https://arthas.aliyun.com/doc/tt.html
tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
通路user/1,
curl http://localhost:61000/user/1
可以看到tt指令捕獲到了一個請求:
輸入
q
或者
Ctrl + C
退出上面的
tt -t
指令。
使用tt指令從調用記錄裡擷取到spring context
tt -i 1000 -w 'target.getApplicationContext()'
擷取spring bean,并調用函數
tt -i 1000 -w 'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'
結果如下:
[arthas@1266]$ tt -i 1000 -w 'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'
@String[Hello World]
Affect(row-cnt:1) cost in 1 ms.
排查HTTP請求傳回401
請求接口沒有權限的時候一般就傳回
401 Unauthorized
。
401通常是被權限管理的Filter攔截了,那麼到底是哪個Filter處理了這個請求,傳回了401?
跟蹤所有的Filter函數
開始trace:
trace javax.servlet.Filter *
可以在調用樹的最深層,找到AdminFilterConfig$AdminFilter傳回了401
+---[3.806273ms] javax.servlet.FilterChain:doFilter()
| `---[3.447472ms] com.example.demo.arthas.AdminFilterConfig$AdminFilter:doFilter()
| `---[0.17259ms] javax.servlet.http.HttpServletResponse:sendError()
通過stack擷取調用棧
上面是通過trace指令來擷取資訊,從結果裡,我們可以知道通過stack跟蹤HttpServletResponse:sendError(),同樣可以知道是哪個Filter傳回了401
執行:
stack javax.servlet.http.HttpServletResponse sendError 'params[0]==401'
通路可以看到如下堆棧資訊:
查找Top N線程
檢視所有線程資訊
thread
檢視具體線程的棧
檢視線程ID 2的棧:
thread 2
檢視CPU使用率top n線程的棧
thread -n 3
檢視5秒内的CPU使用率top n線程棧
thread -n 3 -i 5000
查找線程是否有阻塞
thread -b
更多使用檢視:
Github位址: https://github.com/alibaba/arthas
文檔位址: https://arthas.aliyun.com/doc/