原創文章,謝絕轉載
筆者出於工作及學習的目的,經常與Spark源碼打交道,也難免對Spark源碼做修改及測試。本人一向講究借助工具提升效率,開發Spark過程中也在摸索如何更加順暢的對源碼進行調試。
Spark基於Scala,采用IntelliJ IDEA和sbt應對日常開發,自然是最佳選擇了。如何導入及編譯Spark項目,網上資料很多,官網給的教程也比較詳細:
- http://spark.apache.org/docs/latest/building-spark.html
- http://spark.apache.org/developer-tools.html
本文基於Spark2.x的源碼,重點介紹如何使用sbt結合IDEA對Spark進行斷點調試開發,這對於經常修改或學習Spark源碼的讀者較為有益。廢話到此,我們進入正題。
Spark源碼編譯
首次拿到Spark源碼,直接導入IDEA會有很多錯誤,因為SQL項目的catalyst中的SQL語法解析依賴ANTLR語法定義,需要通過編譯生成代碼,如下是采用sbt打包編譯的流程:
git clone https://github.com/apache/spark.git
cd spark
build/sbt package
...經過漫長等待,成功編譯后,導入IDEA就可以正常看源碼了。
大家可以采用阿里雲的Maven倉庫,加速下包的過程,可以參考我的這篇文章:https://zhuanlan.zhihu.com/p/25279570
編寫測試用例
我習慣於直接在Spark項目中寫TestCase的方式作為執行Spark的入口,這種方式對於經常修改Spark源碼的開發場景很適用,相比在SparkShell中寫測試代碼有以下好處:
- 代碼保留在文件中,方便修改重新執行
- 代碼在同一個項目中,源碼修改后IDEA無需對代碼進行二次索引
- 方便進行持續測試(Continuous Test)
Spark源碼自帶大量的TestCase可供我們學習參考,我們以Spark的SQL項目為例,將spark/sql/core/src/test/scala/org/apache/spark/sql/SQLQuerySuite.scala
復制為SimpleSuite.scala
。
注意,這里不要是使用IDEA自帶的復制功能,因為IDEA在復制的時候會重新組織代碼中import的次序,這有可能會導致編譯出錯。正確的姿勢應該是:
- 在IDEA中,找到要復制的文件,右擊,復制代碼路徑
- 在IDEA的Terminal窗口中執行
cp xxx xxx2
完成復制
我們之所以要基於SQLQuerySuite
復制出一個SimpleSuite
文件是因為:Spark為了確保代碼風格一致規范(比如每個代碼文件頭部需要定義Apache的License注釋;import的順序為java,scala,3rdParty,spark),在項目引入了Scala-style checker,如果代碼不合規范,執行編譯會出錯。直接復制一個文件在上面做修改可以避免踩到代碼風格檢查的坑。我將SimpleSuite的內容修改如下:
打開IDEA的Terminal窗口,執行build/sbt進入sbt的交互式環境,通過以下方式執行我們的SimpleSuite:
> project sql
> testOnly *SimpleSuite
project sql
指的是切換到SQL項目,這樣在執行testOnly時可以快速定位到我們的SimpleSuite類,可以執行projects
查看Spark定義的所有子模塊,當前所在的模塊名稱前會有個*
的標識。首次執行測試的時間比較長,再次執行就會比較快了,如果測試通過的話,會看到如下信息:
在sbt中執行exit
退出交互式環境,接下來介紹如何使用sbt結合IDEA進行斷點調試。
sbt結合IDEA對Spark進行斷點調試
由於sbt是在Terminal中單獨啟動的進程,要對sbt調試,就需要采用IDEA的遠程調試功能了。在IDAE的菜單中選擇Run -> Edit Configrations...
,在接下來的窗口中添加一個Remote配置:
配置名稱大家隨意,我這里為Spark,遠程調試的端口為5005,如果本地的5005端口被占用,改為其他端口即可。
然后回到Terminal重新啟動sbt,啟動時需要添加遠程調試參數:build/sbt -jvm-debug 5005
,啟動過程中會提示Listening for transport dt_socket at address: 5005
,啟動sbt后,我們就可以通過IDEA對sbt進行調試了。
接下來我們給SimpleSuite的test方法內部隨意添加一個斷點,回到sbt執行:
> project sql
> set fork in Test := false
> testOnly *SimpleSuite
一切順利的話,執行testOnly的過程中,我們的斷點會被命中:
如果對Spark源碼或SimpleSuite的代碼做了修改只需要重新執行testOnly *SimpleSuite
即可。
讓IDEA命中斷點有一個關鍵的語句:set fork in Test := false
,這個語句的作用是讓sbt執行Test時避免fork子進程。我們啟動sbt的時候添加的遠程調試端口是加在sbt上的,如果執行Test不在一個進程內,IDEA就無法命中斷點。
如果頻繁修改代碼,反復執行testOnly
難免有些不便,我們可以采用sbt的持續編譯功能簡化流程。執行時加上~
,也就是~testOnly *SimpleSuite
,這樣,我們修改代碼,在保存,sbt會監控文件變化並自動執行測試,超級方便。這種方式同樣適用於compile,test,run等命令。
總結
幾個關鍵點:
# Spark源碼目錄下執行(以SimpleSuite為例):
$ build/sbt -jvm-debug 5005
> project sql
> set fork in Test := false
> testOnly *SimpleSuite
OK,掌握以上技巧,我們就可以愉快的深入Spark源碼內部,了解Spark的運作機制了。