长时间运行的 Spark Streaming 作业一旦提交给 YARN 集群,应该一直运行,直到故意停止。 任何中断都会导致严重的处理延迟,并且可能会导致处理数据丢失或重复。 YARN 和 Apache Spark 都不是为执行长时间运行的服务而设计的。 但是他们已经成功地适应了日益增长的近乎实时处理的需求,这些需求是作为长期工作而实施的。 成功并不一定意味着没有技术挑战。

本博文总结了我在安全的 YARN 集群上运行关键任务,长时间运行的 Spark Streaming 作业的经验。 你将学习如何将 Spark Streaming 应用程序提交到 YARN 群集,以避免在任务运行时的不眠之夜。


故障容错

在 YARN 集群模式下,Spark 的 Driver 程序与 Application Master 运行在同一个容器中,该容器是 YARN 为该 Application 分配的第一个容器。Driver 程序是负责分发任务和向 YARN 请求资源(Spark executors) 的。What is important, Application Master eliminates need for any another process that run during application lifecycle. 即使提交 Spark Streaming 作业的边缘 Hadoop 集节点挂掉了,应用程序也不会受到影响。

要在集群模式下运行 Spark Streaming 程序,请确保为 spark-submit 命令提供以下参数:

spark-submit --master yarn --deploy-mode cluster

因为 Spark Driver 和 Application Master 共享一个 JVM,所以 Spark Driver 中的任何错误都会停止我们的长时间运行的作业(注:cluster模式)。幸运的是,我们可以配置将重新运行应用程序的最大尝试次数(注:即尝试了多少次失败后才重新去申请资源并重新运行任务)。 设置比默认值2更高的值是合理的(来自于 YARN cluster 的 yarn.resourcemanager.am.max-attempts)。我觉得设置为 4 是相当不错的一个选择,设置更高的值会导致不必要的重启,即故障的原因可能是永久性的。

spark-submit --master yarn --deploy-mode cluster \
    --conf spark.yarn.maxAppAttempts=4

如果应用程序运行数天或数周而不在高度利用的群集上 重新启动或重新部署的话,则可能会在几个小时内耗尽4次尝试(注:这里不是很理解)。 为了避免这种情况,应该在每个小时重置尝试计数器。为了避免这种情况,attempt 计数器应该在每小时重新设置。

spark-submit --master yarn --deploy-mode cluster \
    --conf spark.yarn.maxAppAttempts=4 \
    --conf spark.yarn.am.attemptFailuresValidityInterval=1h

另一个重要的设置是应用程序失败之前 executor 程序失败的最大次数。 默认情况下它是 max(2 * num executors, 3),非常适合批量作业,但不适用于长时间运行的作业。 该属性与相应的有效期间也应该设置。

spark-submit --master yarn --deploy-mode cluster \
    --conf spark.yarn.maxAppAttempts=4 \
    --conf spark.yarn.am.attemptFailuresValidityInterval=1h \
    --conf spark.yarn.max.executor.failures={8 * num_executors} \
    --conf spark.yarn.executor.failuresValidityInterval=1h

对于长时间运行的 job,你也可以考虑在放弃 job 之前提高最大 tasks 失败的次数。 默认情况下,tasks 将重试4次,然后作业失败。

spark-submit --master yarn --deploy-mode cluster \
    --conf spark.yarn.maxAppAttempts=4 \
    --conf spark.yarn.am.attemptFailuresValidityInterval=1h \
    --conf spark.yarn.max.executor.failures={8 * num_executors} \
    --conf spark.yarn.executor.failuresValidityInterval=1h \
    --conf spark.task.maxFailures=8

性能

将 Spark Streaming 应用程序提交给群集时,必须定义作业运行的 YARN 队列。 我强烈建议使用 YARN Capacity Scheduler 并将长时间运行的作业提交到单独的队列中。 如果没有使用单独的 YARN 队列,你长期运行的作业迟早会被大量的 Hive 查询抢占。

spark-submit --master yarn --deploy-mode cluster \
    --conf spark.yarn.maxAppAttempts=4 \
    --conf spark.yarn.am.attemptFailuresValidityInterval=1h \
    --conf spark.yarn.max.executor.failures={8 * num_executors} \
    --conf spark.yarn.executor.failuresValidityInterval=1h \
    --conf spark.task.maxFailures=8 \
    --queue realtime_queue

Spark Streaming 工作的另一个重要问题是保持处理时间的稳定性和高度可预测性。 处理时间应该低于批处理时间以避免延迟。 我发现 Spark 推测执行(speculative execution)有很多帮助,特别是在繁忙的集群上。 当推测执行被启用时,批处理时间更稳定。 不幸的是,只有在 Spark actions 是幂等的情况下才能启用投机模式。

spark-submit --master yarn --deploy-mode cluster \
    --conf spark.yarn.maxAppAttempts=4 \
    --conf spark.yarn.am.attemptFailuresValidityInterval=1h \
    --conf spark.yarn.max.executor.failures={8 * num_executors} \
    --conf spark.yarn.executor.failuresValidityInterval=1h \
    --conf spark.task.maxFailures=8 \
    --queue realtime_queue \
    --conf spark.speculation=true

安全

在安全的 HDFS 集群上,由于 Kerberos ticket 到期,导致长时间运行的 Spark Streaming 作业失败。 如果没有其他设置,则在 Spark Streaming 作业提交到群集时会发出 Kerberos ticket 验证。 ticket 到期时 Spark Streaming 作业将无法从 HDFS 写入或读取数据。

在理论上(基于文档),将 Kerberos 的 principal 和 keytab 添加到 spark-submit 参数中应该足够了:

spark-submit --master yarn --deploy-mode cluster \
     --conf spark.yarn.maxAppAttempts=4 \
     --conf spark.yarn.am.attemptFailuresValidityInterval=1h \
     --conf spark.yarn.max.executor.failures={8 * num_executors} \
     --conf spark.yarn.executor.failuresValidityInterval=1h \
     --conf spark.task.maxFailures=8 \
     --queue realtime_queue \
     --conf spark.speculation=true \
     --principal user/hostname@domain \
     --keytab /path/to/foo.keytab

实际上,由于一些Bug (HDFS-9276, SPARK-11182) ,HDFS 缓存必须被禁用。如果没有禁用的话,Spark 将无法从 HDFS 上的文件读取更新的 token。

spark-submit --master yarn --deploy-mode cluster \
     --conf spark.yarn.maxAppAttempts=4 \
     --conf spark.yarn.am.attemptFailuresValidityInterval=1h \
     --conf spark.yarn.max.executor.failures={8 * num_executors} \
     --conf spark.yarn.executor.failuresValidityInterval=1h \
     --conf spark.task.maxFailures=8 \
     --queue realtime_queue \
     --conf spark.speculation=true \
     --principal user/hostname@domain \
     --keytab /path/to/foo.keytab \
     --conf spark.hadoop.fs.hdfs.impl.disable.cache=true

Mark Grover 指出,这些 Bug 只影响 NameNode 配置为 HA 模式的 HDFS 集群。 谢谢,Mark。


注:本文翻译自:http://mkuthan.github.io/blog/2016/09/30/spark-streaming-on-yarn/