標籤:

sbt結合IDEA對Spark進行斷點調試開發

原創文章,謝絕轉載

筆者出於工作及學習的目的,經常與Spark源碼打交道,也難免對Spark源碼做修改及測試。本人一向講究藉助工具提升效率,開發Spark過程中也在摸索如何更加順暢的對源碼進行調試。

n

Spark基於Scala,採用IntelliJ IDEA和sbt應對日常開發,自然是最佳選擇了。如何導入及編譯Spark項目,網上資料很多,官網給的教程也比較詳細:

n

  • <Building Spark - Spark 2.1.1 Documentation>
  • n

  • <Useful Developer Tools>
  • n

本文基於Spark2.x的源碼,重點介紹如何使用sbt結合IDEA對Spark進行斷點調試開發,這對於經常修改或學習Spark源碼的讀者較為有益。廢話到此,我們進入正題。

n

Spark源碼編譯

n

首次拿到Spark源碼,直接導入IDEA會有很多錯誤,因為SQL項目的catalyst中的SQL語法解析依賴ANTLR語法定義,需要通過編譯生成代碼,如下是採用sbt打包編譯的流程:

n

git clone https://github.com/apache/spark.gitncd sparknbuild/sbt packagen

…經過漫長等待,成功編譯後,導入IDEA就可以正常看源碼了。

n

大家可以採用阿里雲的Maven倉庫,加速下包的過程,可以參考我的這篇文章:<使用阿里雲的Maven倉庫加速Spark編譯過程 - 知乎專欄>

n

n

編寫測試用例

n

我習慣於直接在Spark項目中寫TestCase的方式作為執行Spark的入口,這種方式對於經常修改Spark源碼的開發場景很適用,相比在SparkShell中寫測試代碼有以下好處:

n

  • 代碼保留在文件中,方便修改重新執行
  • n

  • 代碼在同一個項目中,源碼修改後IDEA無需對代碼進行二次索引
  • n

  • 方便進行持續測試(Continuous Test)
  • 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

  1. 在IDEA中,找到要複製的文件,右擊,複製代碼路徑

    n

  2. 在IDEA的Terminal窗口中執行cp xxx xxx2完成複製

    n
  3. 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進行斷點調試。

n

sbt結合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(五)數據怎麼存

TAG:Spark | 大数据 |