“不朽的”Spark Streaming Job?

时间:2017-04-07 08:52:37

标签: scala apache-spark spark-streaming

好吧,所以我问过一个与Spark如何在内部处理异常有关的类似question,但我当时的例子并不是很清楚或完整。那里的答案指向了某个方向,但我无法解释一些事情。

我已经设置了一个虚拟火花流媒体应用程序,在转换阶段,我有一个俄语轮盘表达式,可能会或不会抛出异常。如果抛出异常,我会停止Spark流式上下文。就是这样,没有其他逻辑,没有RDD转换。

object ImmortalStreamingJob extends App {
  val conf = new SparkConf().setAppName("fun-spark").setMaster("local[*]")
  val ssc  = new StreamingContext(conf, Seconds(1))

  val elems = (1 to 1000).grouped(10)
    .map(seq => ssc.sparkContext.parallelize(seq))
    .toSeq
  val stream = ssc.queueStream(mutable.Queue[RDD[Int]](elems: _*))

  val transformed = stream.transform { rdd =>
    try {
      if (Random.nextInt(6) == 5) throw new RuntimeException("boom")
      else println("lucky bastard")
      rdd
    } catch {
      case e: Throwable =>
        println("stopping streaming context", e)
        ssc.stop(stopSparkContext = true, stopGracefully = false)
        throw e
    }
  }

  transformed.foreachRDD { rdd =>
    println(rdd.collect().mkString(","))
  }

  ssc.start()
  ssc.awaitTermination()
}

在IntelliJ中运行它会在某个时刻抛出异常。有趣的部分:

  • 如果在第一个转换中抛出异常(当处理第一个RDD时),则停止火花上下文并且应用程序死亡,这就是我想要的
  • 如果在处理了至少一个RDD后抛出异常,应用程序会在打印错误消息后挂起并且永不停止,这不是我想要的

为什么应用程序会挂起而不是在第二种情况下死亡?

我在Scala 2.11.8上运行Spark 2.1.0。获取try-catch解决了问题(Spark自行停止)。此外,移出foreachRDD内的try-catch解决了这个问题。

然而,我正在寻找一个可以帮助我理解这个特定例子中发生了什么的答案。

3 个答案:

答案 0 :(得分:4)

您只会在操作中看到异常(例如本例中为foreachRDD)而不是转换(在本例中为transform),因为操作会延迟执行转换。这意味着您的转换甚至会在行动之前发生。这是必要的原因要求改变分布式处理如何工作的心理模型。

考虑传统的单线程程序。代码逐行进行,如果抛出异常而不处理,后续的代码行就不会执行。在分布式系统中,相同的Spark转换在多台计算机上并行运行(以不同的速度运行),抛出异常时会发生什么?它不是那么简单,因为一台机器上的异常与其他机器上的代码无关,这就是你想要的。想要在整个集群中传播的所有独立任务只是关闭异常只是单机思维,而不是转换为分布式范例。司机应该怎么处理?

根据Matei Zaharia的说法,现在是Databricks和Spark在Berkeley的创建者之一," 异常应该被发送回驱动程序并记录在那里(抛出SparkException如果任务失败超过4次)。" (顺便提一下,可以使用spark.task.maxFailures更改此默认重试次数。)。因此,如果在执行程序上正确配置了Log4J,那么将在那里记录异常;然后它将被序列化并发送回驱动程序,默认情况下将再次尝试3 更多次。

在你的特殊情况下,我猜你会发生一些事情。首先,您在一台计算机上运行,​​这将给出异常处理在分布式模型中如何工作的误导性图片。其次,你过早地停止了上下文。停止上下文is an extremely destructive operation,其中包括停止所有听众和DAGScheduler。坦率地说,当你基本上把灯关掉时,我不知道你怎么能指望Spark把所有东西整齐地包起来。

最后,我要提到更优雅的异常处理模型可能正在Try内执行转换。由于您的转换将返回RDD[Try[T]]DStream[Try[T]],这意味着您将需要为每个元素处理SuccessFailure个案例,因此最终可能会出现更麻烦的代码。但是,您将能够在下游传播成功和错误信息以及monad提供的所有好处,包括映射RDD[Try[A]] => RDD[Try[B]]甚至使用for理解(凭借flatMap)。

答案 1 :(得分:2)

首先,将异常处理移动到foreachRDD时它起作用的原因是因为foreachRDD主体中的代码是异常显示的位置。当调用动作(例如,收集)时,懒惰地评估变换。因此,捕获错误后,正确调用ssc.stop会中止作业,ssc在范围内,一切都很好且确定性。

现在,当您在转换中调用ssc.stop时,事情会变得更加混乱。上下文实际上并不在转换主体的范围内,因为转换是由工作者而不是驱动程序执行的。它恰好恰好适合你,因为你在本地跑步都很好。移动到分布式环境后,您将发现您的作业无法运行,因为您无法将上下文分发给执行程序。

当从转换中抛出异常时,堆栈跟踪将被发送到驱动程序,驱动程序将重试该任务,但在您的情况下,您已停止上下文,那么谁知道将要发生什么?我想如果在第一个RDD上发生异常,那么这个工作就会被杀死,因为它很快就会被杀死,无论是通过侥幸还是设计。而且我猜想如果异常不在第一个RDD转换中就不会全部被杀死,因为你停止上下文并以某种方式创建一个驱动程序和/或任务无限期等待的条件。

答案 2 :(得分:0)

过去几周我在这个问题上进行了挖掘,所以我发现让你们了解过去几天借助StackOverflow社区和Apache Spark社区学到的东西会很高兴:

  • 永远不要像我在这个例子中那样停止火花变换/动作中的$(document).ready(function () { $.ajax({ url: '/api/UserApi/', type: 'GET', contentType: "application/json;charset=utf-8", dataType: "json", success: function (data) { alert('User Added Successfudly'); for (var i = 0; i < data.length; i++) { $("#absa").append("<tr><td>" + data[i].user_Name + "</td><td>" + data[i].Address + "</td><td>" + data[i].Email + "</td><td>" + data[i].MobileNo + "</td><td><button onclick="update()"/></td></tr>"); } }, error: function () { alert('User not Added'); } }); });。如果您想这样做,只需在单独的thread中进行。否则你可能偶然发现死锁或一些未定义的行为。谢谢你@Shinzong Xhu
  • 如果您想确保您的流式传输作业失败(您的任务失败),您可以明确地抛出一个StreamingContext,其中可以包含您自己的异常。你的流媒体应用程序不会死,但至少流媒体任务会失败。感谢@Vidya对此的启发。

更新[不要忘记关闭外部资源]

请务必查看是否需要停止与Spark无关的任何其他资源。例如,如果您停止流式传输作业但驱动程序使用来自akka的SparkException来执行某些HTTP呼叫,请不要忘记终止系统或您的应用将挂起。这似乎与Spark无关,但不要忘记它。

再次感谢其他答案,我希望您会发现此信息有用。