背景
最近收到這樣一個問題:
Storm處理消息時會根據Topology生成一棵消息樹,Storm如何跟蹤每個消息、如何保證消息不丟失以及如何實現重發消息機制?
雖已回復,但心想還是看下storm這塊的源碼吧.那看靜態多不爽啊,那總得調試吧,好吧,造個本地環境來調吧。
先看看maven的build過不過:
mvn -f pom.xml clean install
搞定storm的編譯打包,接着是storm-starter的編譯打包,一切很順利啊,跑一下看看:
${STORM_HOME}/bin/storm jar ${STORM_JAR} ${STORM_STARTER_JAR} storm.starter.WordCountTopology
順利出結果了,不就是個hello world嘛!
接着造本地環境吧,將storm-starter的源碼按maven方式導入Intellij IDEA,注意,從這時候悲催就開始了。
hello,world 打臉了
導入IDE后,興致勃勃的點了F5
,然后:
這尼瑪,說好不打臉的!
看了又看依賴“都合適”啊,ClassPath“都合適”啊,否則編譯不通過啊,為毛跑!不!起!來!
這同樣的操作,在eclipse里妥妥的啊,各種能跑啊,為毛在Intellij IDEA里出錯了呢?
異常是如何產生的
好吧,既然打臉了,又是知名IDE的粉絲,堅決要知恥而后勇的。
那么,看下異常如何產生的吧。
上面的圖,基本概括了異常NoClassDefFound
產生的路徑。
更細節的異常產生情況如下:
注意看調用棧:
JVM_GetClassDelaredMethods
是JVM內方法,在找入口函數main
的過程中,此方法被調用。- 接着,此方法會調用驗證字節碼的過程:
verify_code
。- 發現有用到
backtype.storm.topology.IRichBolt
接口,那么找找這個接口所在的.class
文件吧:
如果我們運行java -cp . xxx
,通過-cp
或者-classpath
參數指定了classpath
,那么這個.class
就會被找到。然后進入parse
的過程。
- 悲催的是,IDEA的運行或者調試命令
f5->run
,沒有將backtype/storm/topology/IRichBolt
所在的jar包storm-core.jar
加入classpath,這都是后話了...
那么,既然有個找*.class
的過程,這個過程如下:
對上圖做一點簡要說明:
- 執行java -cp . $mainClass .
java
程序(這里指java這個程序本身)的入口函數main
,會創建虛擬機JVM
實例,過程中會初始化JVM
本地ClassLoader
.- 在
JVM
尋找.class
文件時,調用ClassLoader::load_classfile
方法,從jar包、zip包、目錄中尋找指定的.class
文件 .- 本文中,木有找到
backtype/storm/topology/IRichBolt.class
,所以會置一個延時異常__pending_exception
,這個異常關聯了這個類(接口)、文件名、異常的類型(NoClassDefFound
),那這個異常什么時候處理呢?后文再說.
看下這個異常的內容:
異常是如何處理的
好了,異常的產生清楚了,還有個問題,那個__pending_exception
是何時被處理呢?
看下圖:
對上圖做下簡要說明:
- 執行
java -cp . $mainClass
.- java程序的入口函數
main
,在層層初始化的過程中,會調用到LoadMainClass
函數, 結合本文的第一幅圖就可以知道,這個函數最終會制造那個NoClassDefFound
的異常__pending_exception
,然后返回的是一個空的mainClass
.- 緊跟着
LoadMainClass
函數,是一個CHECK_EXCEPTION_NULL_LEAVE
的宏,這個宏展開后,會處理上面制造的那個異常,然后,打印異常信息。這里就是那個被打臉的異常了。
為什么被打了臉
這里,異常產生的本質和異常處理,清楚了。
簡單的概括下就是:
/* 偽代碼 */
main /* java這個程序的main */
-> createJVM() /* 創建JVM */
-> loadMainClass() /* 加載我們指定的$mainClass文件,這是個class文件 */
-> findMethod("main") /* 在$mainClass中找main方法,java寫的程序的main */
-> getMethodFromJVM() /* 沒緩存,問JVM要 */
-> classLoader.loadFromFile() /* 在classpath中找.class文件 */
-> 沒找到,置異常NoClassDefFound.
但是,Intellij IDEA為何在運行時不將storm-core.jar
包含進classpath
呢?
換句話說:為啥被打臉??
打臉的理由很簡單:
F5->run
,先make/compile/build,再運行.- 依賴的scope設為了
provided
,此設置僅在編譯階段將依賴的jar包加入classpath,在運行階段,不會將jar包加入classpath.
解決的方法也非常簡單:
如果不是通過mvn來運行,而是在IDE下調試/運行,趕緊將依賴的jar包的scope選為
compile
吧,妥妥的不會被打臉!
看下是不是妥妥的呢?
后記
.