自定义 Spark application 监听器进行task异常处理 JAVA版

本文介绍如何在Spark中实现任务失败的实时监控,通过自定义监听器SparkListenerTaskEnd捕获任务失败信息,包括失败原因,从而及时发现并解决程序问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

### 在 Linux 环境下运行 Spark 代码的方法 要在 Linux 系统中成功运行 Spark 代码,通常需要完成以下几个方面的操作: #### 1. 准备开发环境 为了编写并编译 Spark 应用程序,开发者可以选择 Maven 或 SBT 来管理项目依赖项。例如,在构建文件 `build.sbt` 中添加如下依赖关系来引入 Apache Spark 的核心库[^3]。 ```scala libraryDependencies += "org.apache.spark" %% "spark-core" % "3.3.0" ``` 注意本号应与实际使用的 Spark 本一致。 #### 2. 构建可执行 JAR 文件 通过 IDE(如 IntelliJ IDEA)或者命令行工具生成包含应用程序逻辑的 jar 包。如果使用的是 Maven 工程,则可以通过以下步骤打包应用[^2]: - 执行 `mvn clean package` 命令以创建不含外部依赖的 jar 包。 - 如果目标环境中缺少某些必要的类库,则需选择带全部依赖的 fat-jar 方式打包。 #### 3. 部署至 Linux 并设置参数 将上述生成好的 jar 文件传输到目标服务器上的指定目录,比如 `/opt/module/spark-apps/your-application.jar` 。接着定义好输入输出路径以及其他必要选项后提交作业给集群处理。 对于简单的 Word Count 实例而言,其 shell 脚本可能类似于下面这样: ```bash $SPARK_HOME/bin/spark-submit \ --class com.example.WordCount \ --master local[*] \ /opt/module/spark-apps/wordcount-example-assembly-0.1.jar \ hdfs://namenode:8020/user/data/input.txt hdfs://namenode:8020/user/data/output/ ``` 这里指定了主入口函数所在的全限定名以及采用本地模式作为计算资源调度器;同时还提供了 HDFS 上的数据源位置和结果存储地址等信息。 #### 4. 远程调试 (可选) 当遇到复杂问题难以定位时,可以从 Windows 主机连接至部署有 Spark 的 Linux VM 开启远程会话以便更深入地探查内部状态变化情况。具体做法是在 JetBrains Product Family 下打开对应工程配置窗口新增一项 Remote Debugging Task ,填写主机 IP 地址及监听端口号等相关细节即可建立链接通道[^4]。 以上就是关于如何在基于 Unix-like OS 的机器上面启动由 Scala / Java 编写的分布式大数据框架实例的大致流程概述。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值