Apache Spark:如何在代码中取消作业并终止正在运行的任务?

时间:2016-09-25 10:18:00

标签: scala hadoop apache-spark yarn

我在客户端模式下使用Yarn(版本2.6.0)在Hadoop集群上运行Spark应用程序(版本1.6.0)。我有一段代码运行一个很长的计算,如果它花了太长时间我想杀死它(然后再运行一些其他函数)。
这是一个例子:

val conf = new SparkConf().setAppName("TIMEOUT_TEST")
val sc = new SparkContext(conf)
val lst = List(1,2,3)
// setting up an infite action
val future = sc.parallelize(lst).map(while (true) _).collectAsync()

try {
    Await.result(future, Duration(30, TimeUnit.SECONDS))
    println("success!")
} catch {
    case _:Throwable =>
        future.cancel()
        println("timeout")
}

// sleep for 1 hour to allow inspecting the application in yarn
Thread.sleep(60*60*1000)
sc.stop()

超时设置为30秒,但当然计算是无限的,因此等待将来的结果将抛出异常,将被捕获,然后将取消未来并且备份函数将执行。
这一切都运行得很好,除了取消的作业没有完全终止:当查看应用程序的Web UI时,作业被标记为失败,但我可以看到内部仍有正在运行的任务。

当我使用SparkContext.cancelAllJobs或SparkContext.cancelJobGroup时,会发生同样的事情。问题是,即使我设法继续我的程序,取消的工作的运行任务仍然占用宝贵的资源(这最终将使我减速到接近停止)。

总结一下:如何以一种终止该作业的所有正在运行的任务的方式杀死Spark作业? (与现在发生的情况相反,这会阻止作业运行新任务,但让当前正在运行的任务完成)

更新:
经过很长一段时间忽略了这个问题,我们发现了一个混乱但有效的小解决方法。我们只是在发生超时时记录所有活动阶段的阶段ID,而不是尝试从Spark应用程序中删除相应的Spark Job / Stage,并向用于查杀的Spark Web UI提供的URL发出HTTP GET请求所说的阶段。

3 个答案:

答案 0 :(得分:1)

为了将来的访问者,Spark从2.0.3版本开始引入Spark task reaper,它确实(或多或少)解决了这种情况,并且是内置的解决方案。 请注意,如果任务没有响应,则最终会杀死执行程序。

此外,some built-in Spark sources of data已重构为对火花更敏感:

对于1.6.0版本,Zohar的解决方案是“杂乱而有效”的解决方案。

答案 1 :(得分:0)

根据setJobGroup:

"如果作业组的interruptOnCancel设置为true,则作业取消将导致在作业的执行程序线程上调用Thread.interrupt()。"

因此地图中的anno功能必须是可以中断的:

val future = sc.parallelize(lst).map(while (!Thread.interrupted) _).collectAsync()

答案 2 :(得分:0)

我不知道这能回答您的问题。 我的需要是杀死挂起太多时间的作业(我的作业从Oracle表中提取数据,但由于某些不明确的原因,很少将连接永久挂起)。

经过研究,我得出了这个解决方案:

val MAX_JOB_SECONDS = 100
val statusTracker = sc.statusTracker;
val sparkListener = new SparkListener()  
{ 

    override def onJobStart(jobStart : SparkListenerJobStart)     
    {
        val jobId = jobStart.jobId
        val f = Future 
        {
            var c = MAX_JOB_SECONDS;
            var mustCancel = false;
            var running = true;
            while(!mustCancel && running)
            {
                Thread.sleep(1000);
                c = c - 1;
                mustCancel = c <= 0;
                val jobInfo = statusTracker.getJobInfo(jobId);
                if(jobInfo!=null)
                {
                    val v = jobInfo.get.status()
                    running = v == JobExecutionStatus.RUNNING
                }
                else
                    running = false;
            }
            if(mustCancel)
            {
              sc.cancelJob(jobId)
            }
        }
    }
}
sc.addSparkListener(sparkListener)
try
{
    val df = spark.sql("SELECT * FROM VERY_BIG_TABLE") //just an example of long-running-job
    println(df.count)
}
catch
{
    case exc: org.apache.spark.SparkException =>
    {
        if(exc.getMessage.contains("cancelled"))
            throw new Exception("Job forcibly cancelled")
        else
            throw exc
    }
    case ex : Throwable => 
    {
        println(s"Another exception: $ex")
    }
}
finally
{
    sc.removeSparkListener(sparkListener)
}