最近要截取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查看異常,可以有效的第一時間獲取到了。