最近要截取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查看异常,可以有效的第一时间获取到了。