1. 程式人生 > 其它 >SparkOnYarn 呼叫System.exit(0)狀態異常 與 scala獲取當前活躍執行緒

SparkOnYarn 呼叫System.exit(0)狀態異常 與 scala獲取當前活躍執行緒

技術標籤:sparkScala異常解決sparkyarnscalajvm

一.引言:

在yarn-cluster模式下執行spark程式時,出現任務結束但是顯示程式沒有退出的情況,在本地和yarn上嘗試System.exit(0),本地可以正常退出但是在叢集模式下無法正常退出並顯示Application狀態為Failed。

二.本地執行

=> 不加入System.exit(x)

sc.stop()

在之加入 sc.stop() 的情況下,程式未直接退出,只能手動關閉任務。

=> 加入System.exit(x)

sc.stop()
sys.exit(0)

加入sc.stop() 與 sys.exit(0),程式可以正常退出,於是決定將sys.exit(0)加入到叢集程式碼中,看叢集程式碼能否正常執行完畢。

三.spark on yarn執行

在叢集的主程式加入sys.exit(0)後,程式第一時間退出,但是結束狀態顯示為FAILED,看log的輸出其實是正常執行完畢的,但是結束狀態卻不是SUCCESS。

查找了yarn相關的介紹找到了原因:

當使用Yarn叢集進行叢集管理並啟動Spark程式並在指令碼中選擇--deploy-mode: cluster 模式時,Spark應用程式程式碼不是在JVM中執行的,而是由 ApplicationMaster 即常說的 AM 在執行。當嘗試在應用程式中呼叫System.exit(x),時,應用程式首先在 startUserApplication中啟動,然後在應用程式返回後呼叫完成方法。當執行System.exit(0)時,執行的是shutdownhook ,他看到程式碼尚未成功完成,所以標記狀態為Failed,並標記 EXIT_EARLY故障。可以看到spark日誌中顯示 Shutdown hook ... 。ShutdownHook可以理解為一個監聽JVM關閉的底層介面,當檢查到我們用System.exit(x)結束JVM程式時,就會呼叫ShutdownHook,啟動鉤子執行緒結束程式。

  // The default state of ApplicationMaster is failed if it is invoked by shut down hook.
  // This behavior is different compared to 1.x version.
  // If user application is exited ahead of time by calling System.exit(N), here mark
  // this application as failed with EXIT_EARLY. For a good shutdown, user shouldn't call
  // System.exit(0) to terminate the application.

這裡說到如果通過ShutdownHoot呼叫來關閉ApplicationMaster,則Application預設狀態為Failed。這裡與1.x版本不同。如果使用者應用程式通過呼叫 System.exit(N) 提前退出使用者應用程式,則在這裡標記應用程式失敗,並顯示未EXIT_EARLY。為了更好的結束應用程式,使用者不應該通過呼叫ShutdownHook來終止應用程式。綜上分析我們的程式整體結束時,還有一些活躍的執行緒沒有結束從而導致我們呼叫 exit(x) 提前結束然後顯示為FAILED,所以接下來需要查一下還有哪些執行緒在程式結束時處於活躍並沒有退出。

四.活躍執行緒分析

在呼叫sc.stop()程式下面加入如下程式碼進行執行緒分析,通過遍歷執行緒樹獲取當前所有活躍執行緒與執行緒名:

    ......

    Main Function ...   
    
    ......

    sc.stop()

    var group = Thread.currentThread.getThreadGroup
    var topGroup = group
    // 遍歷執行緒組樹,獲取根執行緒組
    while ( {
      group != null
    }) {
      topGroup = group
      group = group.getParent
    }
    // 啟用的執行緒數再加一倍,防止列舉時有可能剛好有動態執行緒生成
    val slackSize = topGroup.activeCount * 2
    val slackThreads = new Array[Thread](slackSize)
    // 獲取根執行緒組下的所有執行緒,返回的actualSize便是最終的執行緒數
    val actualSize = topGroup.enumerate(slackThreads)
    val atualThreads = new Array[Thread](actualSize)
    // 複製slackThreads中有效的值到atualThreads
    System.arraycopy(slackThreads, 0, atualThreads, 0, actualSize)
    System.out.println("Threads size is " + atualThreads.length)
    for (thread <- atualThreads) {
      System.out.println("Thread name : " + thread.getName)
    }

sc.stop()之後活躍執行緒共計:

Threads size is 364

通過檢查發現了大量redission-netty的執行緒從而定位到程式沒有正常結束的原因是redission client啟動後未呼叫close方法,從而一直線上程中活躍導致程式無法退出,呼叫client的close方法後,程式正常退出,問題解決:

Thread name : redisson-netty-2-1
Thread name : redisson-netty-2-2
Thread name : redisson-netty-2-3
Thread name : redisson-netty-2-4

五.總結

1.spark程式在local和yarn-cluster下執行狀態控制模式,一個基於JVM,一個基於AppicationMaster,所以呼叫 System.exit(x) 結果不同

2.啟動一些基於網路的客戶端在不使用的情況下及時關閉客戶端防止有執行緒一直活躍,也可以用類似try-with-resources使用完畢後關閉客戶端服務

3.儘量不使用System.exit(x)來關閉程式