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

猿天地 發佈 2020-03-03T07:16:27+00:00

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

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有沒有問題,進一步縮小範圍。


關鍵字: