部落格地址:joey771.cn/2018/10/26/…
我們在寫Spark程式的時候免不了要對我們的程式碼進行debug,在程式碼當中打上斷點來檢視程式執行過程中各個變數的變化情況。我一般使用Intellij IDEA來寫Spark程式,可以直接在其中以local的方式執行Spark程式,也可以在其中打上斷點進行除錯,但這樣做有一些問題:
- 我們只能對Spark driver端的程式進行打斷點debug;
- Spark很多程式碼都是惰性執行的,很多程式碼都需要有action才能觸發,在這之前打斷點沒有意義,真正的Task執行邏輯位於Executor當中;
- 對於某些運算量比較大或者記憶體消耗比較多的程式來說,本地電腦不能執行;
- 這樣只能觀察程式碼在local模式下執行的是否正確,無法對在叢集中執行的程式碼進行除錯。
之前所說的幾點問題我在之前一般採用比較低效率的方式來進行debug,即在程式碼當中加入一些log資訊,利用log資訊來除錯程式碼,這樣當Spark程式在叢集中執行時,可以在web UI的Executor的stdout和stderr中檢視我們留下的log資訊。這樣做可以解決一些問題,但是十分低效,debug的資訊需要新增改動時都需要重新編譯程式,並且列印log資訊的方式並不能很完整地觀察到所有變數的變化情況。但這樣做確實解決了一些問題,比如我們可以對Executor真正執行Task邏輯的程式碼進行除錯,也無需考慮惰性執行的過程,Spark的所有RDD的transform的行為都會反映到每個Executor執行的stdout和stderr資訊當中;這些程式碼都可以在伺服器叢集當中執行進行除錯,不用擔心本地電腦效能不夠的問題,本地電腦只需要開啟瀏覽器檢視Spark的web UI即可,或者使用終端來檢視一些資訊;可以在叢集當中除錯程式碼,不需要侷限於local模式下。
但我始終認為這樣的debug方式是低效的,並且不是一個正常的程式設計師應該有的debug方式,之前有想過肯定有具體的方法來解決這樣的除錯問題,Intellij IDEA當中功能非常多,肯定有這樣的功能來解決這個問題。近期又要寫一些Spark程式,並且輸入資料非常大,計算量也很大,我在本地電腦上根本沒辦法debug,於是想到了去查詢一些資料來解決遠端除錯Spark程式的問題。其實Intellij IDEA或者Eclipse這樣的IED都會有remote debug這樣的功能,以Intellij IDEA為例,在Run-Edit Configurations選單中我們可以新增一個Remote的Configurations,這就是Intellij IDEA為我們提供的遠端除錯的功能。這裡對Spark程式的除錯主要分兩種——Driver程式除錯和Executor程式除錯。
Driver程式除錯
Driver程式在遠端進行除錯時,需要在spark-submit的引數中增加一個配置:
--conf spark.driver.extraJavaOptions=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
複製程式碼
或
--driver-java-options -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
複製程式碼
我們隊這個引數進行一些說明,首先spark.driver.exetraJavaOptions這個引數允許我們在Spark的driver程式在執行時傳入一些額外的Java引數,agentlib:jdwp
是Java Debug Wire Protocol選項,是一個遠端除錯的協議,後面緊跟的是以逗號進行分隔的子選項:
transport
定義了遠端Debugger及Debuggee之間資料傳輸協議,可以選擇socket和shared memory的方式,一般情況下都是選擇socket的方式,即選項的值選擇dt_socketserver
執行Java程式的程式是否作為服務端與客戶端(Debugger)進行通訊,通常情況下遠端除錯都需要有一個服務端和一個客戶端,若執行Java程式端為服務端,則除錯端為客戶端,在除錯driver程式時,選擇Java程式為服務端,監聽遠端Debugger的連線,因此值為y(yes)suspend
是否暫停執行直至有客戶端(Debugger)成功連線到服務端,除錯driver程式時將這個值設定為y(yes)使得driver程式會在剛啟動時就暫停住,指到有遠端Debugger客戶端連線上來才繼續執行,確保遠端Debugger能在程式開始執行時已經連線完畢address
監聽的埠,即服務端socket監聽的埠,可以設定為任意可用的沒有被佔用的埠號,只要確保客戶端能夠通過這個埠與服務端進行連線即可
進行了如上設定之後,在我們允許spark-submit指令之後,我們一般能夠看到Spark程式暫停住了,並且會顯示如下這樣一句話:
Listening for transport dt_socket at address: 5005
複製程式碼
這時候,我們的Spark程式已經做好準備在等待遠端Debugger客戶端連線了(一般稱之為attach),接下來就在Intellij IDEA中開啟之前所說的那個選單,點選“+”新增一個新的run/debug configuration,這裡選擇的是Remote型別,Debugger mode選擇Attach to remote JVM,接下來填入Host和port並選擇moudle classpath,之後使用這個配置點選debug按鈕便可以進行遠端除錯了。
Executor程式除錯
一般來說,我們進行Spark程式除錯最主要的就是要對Task當中的執行邏輯進行除錯,因為Executor中執行的才是真正的並行任務,也是程式最主要的部分。對Executor進行除錯時我們一般將允許Java程式的伺服器作為客戶端,將Debugger所在的機器作為服務端,也就是在除錯機器上啟動一個socket伺服器對指定埠進行監聽,而Executor上執行的程式作為客戶端與之進行連線。為什麼不採用之前的那種模式呢?因為我們需要保證Executor上的程式一開始執行就與Debugger進行了連線,若採用之前的模式,就需要將Executor進行暫停,而Executor暫停會使得driver程式誤認為其出現了故障,會不斷的重啟Executor,無法正常進行除錯。一般情況下要這樣進行除錯需要將Executor設定為只有一個,因為多個Executor同時連線Debugger的服務端會出現問題,若一定要進行叢集除錯,則在指定Worker啟動的時候加入引數,而不是在spark-submit的時候加入引數,首先說下引數:
--conf spark.executor.extraJavaOptions=-agentlib:jdwp=transport=dt_socket,server=n,address=10.0.0.171:5005,suspend=n
複製程式碼
引數的具體意思不用多加說明,主要是address這裡要寫入具體的Debugger的地址以及埠號,如果要指定一個Worker上啟動一個Executor來進行除錯,可以在使用手動的方式啟動該Worker
spark-class -agentlib:jdwp=transport=dt_socket,server=n,address=10.0.0.171:5005,suspend=n org.apache.spark.deploy.worker.Worker spark://10.0.0.1:7077
複製程式碼
只要給該Worker分配一定的資源,確保在這個Worker上啟動一個Executor即可。Intellij IDEA的設定上和之前的區別就是將Debugger mode改為Listen to remote JVM,然後首先需要啟動Debugger的服務端,即先在Intellij IDEA當中點選debug開啟服務,再執行Spark程式,這樣遠端的Spark Executor上執行的程式會自動進行連線,方便進行debug。這裡需要注意的是,debug過程中不要讓一行程式碼停留太長時間,否則driver會認為該Executor出現了故障,會不斷進行重啟Executor。
參考資料: