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