參考:http://dataknocker.github.io/2014/11/12/idea%E4%B8%8Adebug-spark-standalone/html
轉載請註明來自:http://www.cnblogs.com/yuananyun/p/4265706.html java
研究Spark源碼也有一段時間了,一直都是直接看代碼,沒有調試。雖然帶着思路去看源代碼已經可以幫助咱們去了解Spark了;可是不少細節從字面上是看不出來的,若是我可以經過運行時調試驗證個人想法,或者可以查看某個類中變量和結構在運行時是什麼豈不是更好?好,咱們今天就來實現這個想法。git
動手以前,我已經在網上找了關於spark調試的方法,要麼就是local模式的,要麼就是寫的很模糊。spark local模式和其餘分佈式模式有很大不一樣,雖然能夠在local模式下進行debug,但有不少東西只有在分佈式模式下才有用,本文主要是介紹在Spark Standalone模式下如何調試Driver、Master、Worker和Executor(yarn模式比較複雜,還須要結合yarn的debug模式才能搞定,但研究standalone已經能夠搞清楚spark的大部分原理了)。github
一、spark-class: 像Master、Worker、Driver都是經過spark-class腳本進行啓動各自的jvm(Driver實際上是間接由spark-class啓動,提交程序時以spark-class啓動SparkSubmit,而後SparkSubmit以反射的形式調用Driver的main方法從而實現driver的運行)。
spark-class中設置了各jvm的參數,因此能夠在這些參數中加入debug的相關參數。
二、Executor即CoarseGrainedExecutorBackend的參數在spark-class腳本中修改沒用,由於其不是經過spark-class啓動的,其是被ExecutorRunner啓動,在buildCommandSeq->buildJavaOpts對相應參數進行設置,好比固定MaxPermSize=128m等。能夠在SparkConf中設置spark.executor.extraJavaOptions參數。apache
本文假定你已經掌握或完成了如下內容:dom
一、已經完成了一個Spark Standalone的集羣(大小不重要,能用就行,不須要hdfs的支持),而且可以順利啓動和運行jvm
二、IntelliJ IDEA、Scala插件、Java JDK、Scala SDK都已經安裝和配置完成socket
三、擁有java開發基礎 分佈式
啓動IntelliJ IDEA,選擇New Project,而後選擇Scala,點擊下一步ide
輸入項目名稱和參數繼續下一步:
在src目錄下新建一個scala類對象:RemoteDebug,咱們將用這個類來作測試。
輸入如下代碼(相信你看出來了,就是官網的計算π的例子):
object RemoteDebug { def main(args: Array[String]) { val conf = new SparkConf().setAppName("Spark Pi").setMaster("spark://Master:7077") .setJars(List("F:\\Spark\\SparkRemoteDebug\\out\\artifacts\\SparkRemoteDebug_jar\\SparkRemoteDebug.jar"))
val spark = new SparkContext(conf) val slices = if (args.length > 0) args(0).toInt else 2 val n = 100000 * slices val count = spark.parallelize(1 to n, slices).map { i => val x = random * 2 - 1 val y = random * 2 - 1 if (x * x + y * y < 1) 1 else 0 }.reduce(_ + _) println("Pi is roughly " + 4.0 * count / n) spark.stop() } }
注意如下兩點:
一、」setMaster("spark://Master:7077") 」不能忘,由於咱們是要在Standalone集羣上運行的,「Master「就是master所在的主機名,若是你沒有在本機配置「Master」指向集羣中的master機器IP的話,請直接使用IP,如:spark://192.168.1.103:7070。
二、」setJars(List("F:\\Spark\\SparkRemoteDebug\\out\\artifacts\\SparkRemoteDebug_jar\\SparkRemoteDebug.jar"))「,告訴Spark 集羣咱們要提交的做業的代碼在哪裏,也就是咱們包含咱們程序的Jar包的路徑,記住路徑中千萬別包含中文,否則會出錯(血的教訓)。
首先給項目添加Spark的依賴jar以及源碼zip,選擇項目,按下F4,就會彈出下面的配置窗體:
接下來配置咱們的程序打包:
好,讓咱們來看看咱們的測試結果。啓動Spark集羣成功後,你應該能夠看到SparkUI界面(如下截圖是個人環境):
爲咱們的程序添加一個啓動項:
好,點擊旁邊的綠色小三角啓動按鈕,啓動咱們的程序,查看運行結果:
至此,咱們的程序已經可以正常在集羣上運行,並返回結果了,下一步咱們就來看看怎麼調試Driver、Master和Worker以及Executor。
一、修改Master配置。
首先,咱們中止咱們的spark Cluster,由於咱們須要修改一一些參數,打開Master所在機器的spark-class文件進行編輯,記得先備份哦
找到並修改成如下內容:
找到如下內容:
# Master, Worker, and HistoryServer use SPARK_DAEMON_JAVA_OPTS (and specific opts) + SPARK_DAEMON_MEMORY.
'org.apache.spark.deploy.master.Master')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_MASTER_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.worker.Worker')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_WORKER_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.history.HistoryServer')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_HISTORY_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
修改成:
# Master, Worker, and HistoryServer use SPARK_DAEMON_JAVA_OPTS (and specific opts) + SPARK_DAEMON_MEMORY.
'org.apache.spark.deploy.master.Master')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_MASTER_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=8002,server=y,suspend=n"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.worker.Worker')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_WORKER_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=8003,server=y,suspend=n"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.history.HistoryServer')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_HISTORY_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
二、修改Worker配置
同理(文件位置參考Master機器),找到如下內容並修改:
找到如下內容:
# Master, Worker, and HistoryServer use SPARK_DAEMON_JAVA_OPTS (and specific opts) + SPARK_DAEMON_MEMORY.
'org.apache.spark.deploy.master.Master')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_MASTER_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.worker.Worker')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_WORKER_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.history.HistoryServer')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_HISTORY_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
修改成:
PARK_DAEMON_MEMORY.
'org.apache.spark.deploy.master.Master')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_MASTER_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.worker.Worker')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_WORKER_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=8009,server=y,suspend=n"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
'org.apache.spark.deploy.history.HistoryServer')
OUR_JAVA_OPTS="$SPARK_DAEMON_JAVA_OPTS $SPARK_HISTORY_OPTS"
OUR_JAVA_MEM=${SPARK_DAEMON_MEMORY:-$DEFAULT_MEM}
;;
三、從新啓動Spark Cluster
查看Master和Worker的日誌
好,能夠看到,咱們的Master和Worker都已經啓動成功,而且按照咱們的配置監聽各自的端口,下面咱們就經過程序來調試它們。
四、開啓調試Master和Worker
回到咱們的idea中,添加兩個Remote啓動項
重要的時刻來了,咱們先啓動調試Master,並加上屬於Master代碼的斷點:
能夠看到,idea已經鏈接到了咱們Cluster中的Master機器的8002端口,而這正是咱們在集羣中配置的端口。同理啓動Slave1(Worker)
爲了可以調試Executor,咱們得修改一下咱們前面寫的代碼,修改後的代碼以下:
def main(args: Array[String]) { val conf = new SparkConf().setAppName("Spark Pi").setMaster("spark://Master:7077") .setJars(List("F:\\Spark\\SparkRemoteDebug\\out\\artifacts\\SparkRemoteDebug_jar\\SparkRemoteDebug.jar")) .set("spark.executor.extraJavaOptions", "-Xdebug -Xrunjdwp:transport=dt_socket,address=8005,server=y,suspend=n") println("sleep begin.") Thread.sleep(10000) //等待10s,讓有足夠時間啓動driver的remote debug println("sleep end.") val spark = new SparkContext(conf) val slices = if (args.length > 0) args(0).toInt else 2 val n = 100000 * slices val count = spark.parallelize(1 to n, slices).map { i => val x = random * 2 - 1 val y = random * 2 - 1 if (x * x + y * y < 1) 1 else 0 }.reduce(_ + _) println("Pi is roughly " + 4.0 * count / n) spark.stop() }
最後,咱們來測試一下咱們的成果:
Driver:
Master:
Worker:
Executor(CoarseGrainedExecutorBackend)是運行在Worker上的另外一個JVM進程,貌似我此次實驗並無進入斷點,等哪天找到方法,再補上。