三歪最近每天都很忙,一直在整合系統,期間遇到了不少奇奇怪怪的問題。今天想分享一個Bug,如果後面你遇到了希望你能想起這篇文章。
Bug不會無緣無故出現,修複一個又一個Bug往往不是憑借着運氣。?
看到我這篇文章的讀者,其中也不乏多年開發經驗的老兵,如果你有一些不錯的修Bug技巧或者心得或者工具,希望可以在評論區不吝分享?
背景
一個系統會有多個工程,一個工程會有多個依賴,工程與工程之間的依賴很容易就沖突。
如果有做過系統整合的同學會發現,每天可能會花很長的時間排除依賴。
舉個很簡單的例子:現在工程上有兩個依賴,但是兩個依賴的版本不一樣。在編譯的時候沒有任何問題,但是啟動的時候就會有各種稀奇古怪的錯誤。
這些稀奇古怪的錯誤往往不能指引你去立馬發現這是依賴沖突導緻的問題,但是有經驗的人會想到:「這絕壁是依賴沖突了,我的代碼不可能有問題!!」?
在某年某月某日,我系統已經啟動起來了,在上線之前肯定要自測一下各個功能有無問題。測試到短信下發管道的時候,發現出錯了。
衆所周知,下發一條短信是需要知道使用者的手機号的。如果業務方傳給我的是一個站内的userId,那我需要将userId轉成手機号。
順便來簡單科普一下這裡的技術設計:
- 如果使用者綁定了手機号,那麼在綁定後會觸發一條
消息。我這邊會監聽到這條消息,然後将這條消息清洗/處理,完了之後寫到引擎中。topic
- 提供一個對外暴露引擎Client用戶端使用(傳入userId,查到userId對應的手機号)
對外暴露api層給業務方使用(封裝了引擎的用戶端)
我們想要對Id進行轉換,那麼就引入
searchClient
的依賴,然後調他封裝好的方法,就OK了。
分享遇到的Bug
于是我把
searchClient
引入到項目裡邊,項目正常啟動起來。我在驗證功能是否正常的時候,重複報了一個錯誤:
java.lang.NoClassDefFoundError: Could not initialize class xxx
複制
一次印象:我見到這個錯誤的時候,會覺得:我的依賴包沒引入進去吧?怎麼沒找到呢
于是我就去确認我的包到底有沒有引入進去,經過一大輪驗證,我的依賴包是的的确确是引入了進去啊。那怎麼會報
NoClassDefFoundError
的呢。
于是我就懷疑,是不是我引入的依賴包跟原有的工程依賴沖突呢?于是我就去對比版本,也發現好像沒什麼問題啊。searchClient依賴的版本都一樣的,Zookeeper的版本也是一樣的,怎麼還是報
NoClassDefFoundError
呢。
檢查maven版本,我一般是先用maven的插件”Maven Helper“在目前的工程下去看看有沒有類似的包沖突了,如果有類似的包沖突了那直接在插件上Exclude就好了。
如果發現Maven Helper 不好使,我就會用
mvn dependency:tree
去看看項目裡有沒有版本不一樣的依賴,效果如下:
但走到一步,貌似沒找到相關的依賴有啥問題(畢竟也不能瞎排依賴)。于是就隻能去DeBug看源碼,看源碼的時候發現出錯是在建立單例對象的時候。
代碼裡邊用了靜态内部類的方式去建立對象,一直報的錯就是沒找到這個内部類。那也挺符合
NoClassDefFoundError
這個報錯資訊的。
于是我就去Google也搜了一下,搜到了相關的部落格:
仔細一看,發現說得真的很有道理。跟我的錯誤長得一模一樣,我終于找到了......
這個部落客好像也沒怎麼說解決啊?那怎麼把内部類的class檔案加載到對應的目錄呢??折騰了半天,把依賴包的版本也更新了一把,貌似也沒啥用啊。。
于是我陷入了沉思,這就為啥報
NoClassDefFoundError
呢,不對勁啊,我依賴明明是有的,依賴包好像也沒沖突啊。
如果你直接以去搜是很難搜到你想要的資訊,但是以
NoClassDefFoundError
的關鍵字去搜,檢索出來的資訊就不一樣了。
NoClassDefFoundError 靜态内部類
我就繼續Google,又學習到了另一個問題:單例對象以靜态内部類的方式實作,如果在建立對象的時候出了問題,那麼報的就是
NoClassDefFoundError
。
比如以下的代碼(分别在main方法擷取了三次單例對象,但是在建立對象的時候失敗了):
/**
* 作者:Java3y
*/
public class SingleTon {
private SingleTon() {
int i = 1 / 0;
}
private static class Holder {
private static final SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance() {
return Holder.INSTANCE;
}
public static void main(String[] args) {
try {
System.out.println("First");
SingleTon.getInstance();
} catch (Throwable t) {
t.printStackTrace();
}
try {
System.out.println("Second");
SingleTon.getInstance();
} catch (Throwable t) {
t.printStackTrace();
}
try {
System.out.println("Three");
SingleTon.getInstance();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
複制
報錯資訊:
看到這裡,我在懷疑:是不是我沒有好好看第一次的調用失敗資訊,其實在建立單例的時候内部有出錯了呢。然後在心裡邊也有個問号:這别人提供好的二方包依賴,怎麼可能這麼随意就失敗了,不可能吧。
于是我就重新開機了一把,然後順便Debug進去看看相關的邏輯。進去建立單例對象時,走到某條分支的時候,發現有連接配接ZK的 curator包有兩個依賴可供我選擇。那我知道了,絕壁又是依賴沖突了。
學習筆記:
類加載時靜态變量隻會在第一次加載時,進行初始化,此後不管成不成功,都不會進行第二次初始化了。
這個Bug在最開始的時候已經想過是不是依賴沖突的問題,但是我們懷疑版本依賴往往隻會在頂層的jar包上懷疑,至于内部的jar包沖突一般也不好發現,發現了也不敢去亂排(畢竟複現的報錯不是包依賴的問題啊!!)。
排Bug經驗反思:
看錯誤資訊固然很重要,但不要忽略第一次的報錯資訊(像整合工程這樣的操作,絕大多數時候都是依賴沖突的問題。如果報錯的資訊比較詭異,可以重新開機直接Debug相應的位置上看看究竟是什麼原因)
參考資料:
- https://www.jianshu.com/p/f48c90270fae
Tomcat列印大量的debug日志
整合了個系統以後,發現
tomcat_stdout.log
這個檔案瘋狂列印debug日志,都快把三歪打哭了。
于是我就一頓分析:項目裡邊用的是
logback
去列印日志的,那麼我控制一下
logback
的日志級别應該就行了吧。但是我沒找到
tomcat_stdout.log
這個日志檔案的級别設定。
于是我就根據關鍵字搜:
tomcat_stdout.log DEBUG日志太多
。搜尋了一輪,感覺都沒啥卵用。雖然提供了很多解決方案(但這些我都看不懂,我也不懷疑是伺服器上的配置存在問題。)
搜了一輪
logback 設定日志級别
、
logback debug 日志太多
這些關鍵字都沒有用,隐隐約約能發現是因為logback和log4j沖突了。
最後我搜了一下日志打出的debug資訊,以
ClientCnxn debug
關鍵字去搜尋就才搜到相關的解決方案。
發現還是包依賴沖突的問題,把Zookeeper的
log4j
的包排掉,就解決了。
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
複制
排Bug經驗反思:
上面的表面現象的确是Tomcat debug日志量太大了,而僅僅用這個關鍵字(
tomcat_stdout.log DEBUG
)去搜尋引擎搜尋很多時候都不是自己想要的解決方法。用
logback 設定日志級别
、
logback debug日志量
等關鍵字去搜尋也隻能搜出
logback
的相關教程。
透過表面現象,把輸出的Debug日志加上關鍵字去搜,往往就能得出想要的結果。
參考資料:
- https://blog.csdn.net/qq_32465815/article/details/82081300