得虧了它,我才把潛藏那么深的Bug挖出來

欧洲青年联赛 www.enqgt.com 2020年寫了很多事故解決的文章,并不是我絞盡腦汁想出來的,而是真的遇到了這些問題。通過文章的方式記錄下來,分享出去,才有意義。

事故背景

首先看下面的圖吧,這是我從cat上截的圖。

可以看到是一個Rpc調用的錯誤,從錯誤中我們只能分析出這個Rpc的請求成功了,并且返回了,因為都走到了反序列化這步。

最后是在創建DTO對象的時候報錯了,Could not initalize class xxxxx.DTO說明了這一點。

作為一個調用方,雖然看到了明確的錯誤,但還是要本著嚴謹的態度去排查問題,還是先確認服務提供者到底有沒有問題,跟同事確認了,服務提供方沒問題,通過telnet可以正常invoke。

好了,到這為止就把背景交代清楚了,能不能將這個潛藏的Bug找出來就各顯身手吧。

arthas大顯身手

要想效率高,那必須得有好用的工具呀!arthas挺身而出,都毛遂自薦了,不用白不用。

首先使用sc命令查看JVM已加載的類信息,就看這個不能實列化的類到底有沒有被成功加載。

sc -d 類全路徑 (打印類的詳細信息

類的信息都被打印出來了,足以證明這個類被加載了。

然后打印下類里面的字段,看看有沒有丟失什么的

sc -d -f 類全路徑 (打印類的Field信息

居然報錯了,錯誤還跟我們之前在cat中看到的一模一樣,這邊也是要是創建對象,然后反射獲取所有字段信息,由于不能創建對象,直接報錯了。

就這么結束了嗎?怎么可能,還沒下班呢,接著走下去。。。。

現在我開始懷疑這個class是不是有問題,然后就開始用arthas的另一個命令jad來反編譯。

通過jad 命令將 JVM 中實際運行的 class 的 byte code 反編譯成 java 代碼,便于我們理解業務邏輯,也能讓我們知道代碼跟本地的到底是不是一致。

**jad --source-only **類全路徑

執行完后,什么也沒輸出,我一度懷疑這個命令是不是我用錯了,然后我試了下jad --source-only java.lang.String 發現命令沒問題,就是那個class有問題。

這時我想起還有一個redefine命令可以用于加載外部的.class文件,看看能不能加載進來。于是我將lib目錄里面依賴的jar包解壓了,然后用redefine去加載那個不能反編譯的class。

居然告訴我是一個無效的class,嘗試多次都無法讓這個class現出廬山真面目。

最后沒辦法,只能將這個class弄到本地,拖入IDEA中反編譯,對比了下代碼,跟git倉庫里面的一模一樣,也就不存在jar包損壞的問題。

即將揭開真相

到目前為止,有效的線索如下:

  • class已加載,但是無法實例化
  • 通過本地反編譯,代碼是完整的

越在這種沒有思路的情況下越要靜下心來思考,于是再次看了一遍源碼,發現這個類中有引用一個外部的自定義異常類。

然后我用sc -d去查看這個類的信息,告訴我不存在,終于明白了。

看上面這張圖,項目A依賴了API,API中依賴了Common,Common中又依賴了很多其他的三方Jar包。

由于項目A和Common中依賴的三方Jar包沖突了,所以項目A中之前就簡單粗暴的把Common給排除了,沖突是解決了。

在進行RPC調用的時候,請求的數據響應回來后需要反序列化成對象,這個時候去創建對象失敗了,因為類中依賴了某個外部的類,但在當前項目中沒有加載進來,所以就報錯了。

總結

這次的問題歸根到底還是沒有想到一個API會依賴其他的???,本身API作為RPC調用客戶端就應該簡潔。

其實在做exclusion的時候應該只exclusion有沖突的三方Jar,不應該將整個Common都exclusion掉。

最后就是合理的利用方便快速的工具幫助我們快速的排查問題,arthas就是這個好幫手,通過arthas我們可以進一步排除程序啟動后加載的class有沒有問題,進一步縮小范圍。

posted @ 2020-03-03 10:02  猿天地  閱讀(...)  評論(...編輯  收藏