自定義 Spark application 監聽器進行task異常處理 JAVA版

最近要截取sparkHistory裏面application的運行日誌,發現task級別的某些日誌拿不到,後來想了個辦法搞監聽器,然後一點點學習,將經驗記錄下來。
在spark程序中,task有失敗重試機制(根據 spark.task.maxFailures 配置,默認是4次),當task執行失敗時,並不會直接導致整個應用程序down掉,只有在重試了 spark.task.maxFailures 次後任然失敗的情況下才會使程序down掉。另外,spark on yarn模式還會受yarn的重試機制去重啓這個spark程序,根據 yarn.resourcemanager.am.max-attempts 配置(默認是2次)。

即使spark程序task失敗4次後,受yarn控制重啓後在第4次執行成功了,一切都好像沒有發生,我們只有通過spark的監控UI去看是否有失敗的task,若有還得去查找看是哪個task由於什麼原因失敗了。基於以上原因,我們需要做個task失敗的監控,只要失敗就帶上錯誤原因通知我們,及時發現問題,促使我們的程序更加健壯。
 

在executor中task執行完不管成功與否都會向execBackend報告task的狀態;
 execBackend.statusUpdate(taskId, TaskState.FINISHED, serializedResult)
在CoarseGrainedExecutorBackend中會向driver發送StatusUpdate狀態變更信息;

override def statusUpdate(taskId: Long, state: TaskState, data: ByteBuffer) {
    val msg = StatusUpdate(executorId, taskId, state, data)
    driver match {
      case Some(driverRef) => driverRef.send(msg)
      case None => logWarning(s"Drop $msg because has not yet connected to driver")
    }
  }


CoarseGrainedSchedulerBackend收到消息後有調用了scheduler的方法;

override def receive: PartialFunction[Any, Unit] = {
      case StatusUpdate(executorId, taskId, state, data) =>
        scheduler.statusUpdate(taskId, state, data.value)


        ......
由於代碼繁瑣,列出了關鍵的幾行代碼,嵌套調用關係,這裏最後向eventProcessLoop發送了CompletionEvent事件;

taskResultGetter.enqueueFailedTask(taskSet, tid, state, serializedData)
scheduler.handleFailedTask(taskSetManager, tid, taskState, reason)
taskSetManager.handleFailedTask(tid, taskState, reason)
sched.dagScheduler.taskEnded(tasks(index), reason, null, accumUpdates, info)
eventProcessLoop.post(CompletionEvent(task, reason, result, accumUpdates, taskInfo)) 


在DAGSchedulerEventProcessLoop處理方法中 handleTaskCompletion(event: CompletionEvent)有着最爲關鍵的一行代碼,這裏listenerBus把task的狀態發了出去,凡是監聽了SparkListenerTaskEnd的listener都可以獲取到對應的消息,而且這個是帶了失敗的原因(event.reason)。其實第一遍走源碼並沒有注意到前面提到的sched.dagScheduler.taskEnded(tasks(index), reason, null, accumUpdates, info)方法,後面根據SparkUI的page頁面往回追溯才發現。

 listenerBus.post(SparkListenerTaskEnd(
       stageId, task.stageAttemptId, taskType, event.reason, event.taskInfo, taskMetrics))

自定義監聽器

需要獲取到SparkListenerTaskEnd事件,得繼承SparkListener類並重寫onTaskEnd等方法,
在方法中獲取task失敗的reason就知道哪個task是以什麼原因失敗了。

public class SparkJobListener extends SparkListener {
    

    @Override
    public void onStageCompleted(SparkListenerStageCompleted stageCompleted) {
        System.out.println("stageCompleted");
    }

    @Override
    public void onStageSubmitted(SparkListenerStageSubmitted stageSubmitted) {

    }

    @Override
    public void onTaskStart(SparkListenerTaskStart taskStart) {
        System.out.println("taskStart");
    }

    @Override
    public void onTaskGettingResult(SparkListenerTaskGettingResult taskGettingResult) {

    }

    @Override
    public void onTaskEnd(SparkListenerTaskEnd taskEnd) {
        LogUtil.info("taskEnd");
        taskEnd.reason();
        taskEnd.taskInfo();
        taskEnd.logEvent();
    }

    @Override
    public void onJobStart(SparkListenerJobStart jobStart) {
        LogUtil.info("jobStart");
    }

    @Override
    public void onJobEnd(SparkListenerJobEnd jobEnd) {
        LogUtil.info("jobEnd");
        jobEnd.jobResult();
        jobEnd.logEvent();
    }

    @Override
    public void onEnvironmentUpdate(SparkListenerEnvironmentUpdate environmentUpdate) {

    }

    @Override
    public void onBlockManagerAdded(SparkListenerBlockManagerAdded blockManagerAdded) {

    }

    @Override
    public void onBlockManagerRemoved(SparkListenerBlockManagerRemoved blockManagerRemoved) {

    }

    @Override
    public void onUnpersistRDD(SparkListenerUnpersistRDD unpersistRDD) {

    }

    @Override
    public void onApplicationStart(SparkListenerApplicationStart applicationStart) {
        LogUtil.info("start");
    }

    @Override
    public void onApplicationEnd(SparkListenerApplicationEnd applicationEnd) {
        LogUtil.info("applicationEnd");
    }

    @Override
    public void onExecutorMetricsUpdate(SparkListenerExecutorMetricsUpdate executorMetricsUpdate) {

    }

    @Override
    public void onExecutorAdded(SparkListenerExecutorAdded executorAdded) {

    }

    @Override
    public void onExecutorRemoved(SparkListenerExecutorRemoved executorRemoved) {

    }

    @Override
    public void onExecutorBlacklisted(SparkListenerExecutorBlacklisted executorBlacklisted) {

    }

    @Override
    public void onExecutorUnblacklisted(SparkListenerExecutorUnblacklisted executorUnblacklisted) {

    }

    @Override
    public void onNodeBlacklisted(SparkListenerNodeBlacklisted nodeBlacklisted) {

    }

    @Override
    public void onNodeUnblacklisted(SparkListenerNodeUnblacklisted nodeUnblacklisted) {

    }

    @Override
    public void onBlockUpdated(SparkListenerBlockUpdated blockUpdated) {

    }

    @Override
    public void onSpeculativeTaskSubmitted(SparkListenerSpeculativeTaskSubmitted speculativeTask) {

    }

    @Override
    public void onOtherEvent(SparkListenerEvent event) {

    }
}

最後需要進行註冊,好像spark2.3之後的版本有所改動,因爲我查詢別人的資料都是設值注入,

sparkSession.sparkContext().conf().set("spark.extraListeners","com.dhcc.avatar.gics.driver.SparkAppListener");

但是我試了幾次沒有效果,查了以下API,發現另外一種方法

sparkSession.sparkContext().addSparkListener(new SparkAppListener());

然後就搞定了

如此以來就不用依賴sparkUI查看異常,可以有效的第一時間獲取到了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章