SparkOnYarn 調用System.exit(0)狀態異常 與 scala獲取當前活躍線程

一.引言:

在yarn-cluster模式下運行spark程序時,出現任務結束可是顯示程序沒有退出的狀況,在本地和yarn上嘗試System.exit(0),本地能夠正常退出可是在集羣模式下沒法正常退出並顯示Application狀態爲Failed。redis

 

二.本地運行

=> 不加入System.exit(x)bash

sc.stop()

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

 

=> 加入System.exit(x)app

sc.stop()
sys.exit(0)

加入sc.stop() 與 sys.exit(0),程序能夠正常退出,因而決定將sys.exit(0)加入到集羣代碼中,看集羣代碼可否正常執行完畢。this

 

三.spark on yarn運行

在集羣的主程序加入sys.exit(0)後,程序第一時間退出,可是結束狀態顯示爲FAILED,看log的輸出實際上是正常執行完畢的,可是結束狀態卻不是SUCCESS。spa

查找了yarn相關的介紹找到了緣由:線程

當使用Yarn集羣進行集羣管理並啓動Spark程序並在腳本中選擇 --deploy-mode: cluster 模式時,Spark應用程序代碼不是在JVM中運行的,而是由 ApplicationMaster 即常說的 AM 在執行。當嘗試在應用程序中調用System.exit(x),時,應用程序首先在 startUserApplication中啓動,而後在應用程序返回後調用完成方法。當執行System.exit(0)時,執行的是shutdown hook ,他看到代碼還沒有成功完成,因此標記狀態爲Failed,並標記 EXIT_EARLY故障。能夠看到spark日誌中顯示 Shutdown hook ... 。ShutdownHook能夠理解爲一個監聽JVM關閉的底層接口,當檢查到咱們用System.exit(x)結束JVM程序時,就會調用ShutdownHook,啓動鉤子線程結束程序。3d

// 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()程序下面加入以下代碼進行線程分析,經過遍歷線程樹獲取當前全部活躍線程與線程名:
 
netty

......

    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)來關閉程序

相關文章
相關標籤/搜索