累加器何时真正可靠?

时间:2015-04-07 14:47:00

标签: apache-spark

我想使用累加器来收集有关我在Spark作业上操作的数据的一些统计信息。理想情况下,我会在作业计算所需的转换时执行此操作,但由于Spark会在不同情况下重新计算任务,因此累加器不会反映真实的指标。以下是文档对此的描述:

  

对于仅在操作内执行的累加器更新,Spark   保证每个任务对累加器的更新只会是   应用一次,即重新启动的任务不会更新该值。在   转换时,用户应该知道每个任务的更新可能   如果重新执行任务或工作阶段,则应用多次。

这很令人困惑,因为大多数 操作 不允许运行自定义代码(可以使用累加器),它们主要采用先前转换的结果(懒惰)。文档还显示了这一点:

val acc = sc.accumulator(0)
data.map(x => acc += x; f(x))
// Here, acc is still 0 because no actions have cause the `map` to be computed.

但是如果我们在最后添加data.count(),这是否会保证是正确的(没有重复)?显然,acc不是“仅在内部操作”,因为地图是一种转变。所以不应该保证。

另一方面,关于相关Jira门票的讨论谈论“结果任务”而不是“行动”。例如herehere。这似乎表明结果确实可以保证是正确的,因为我们在之前和行动之前使用acc因此应该作为单个阶段计算。

我猜这个“结果任务”的概念与所涉及的操作类型有关,是包含一个动作的最后一个,就像在这个例子中一样,它显示了几个操作如何被分成几个阶段(在洋红色,图像取自here):

A job dividing several operations into multiple purple stages

假设,该链末尾的count()动作将是同一个最后阶段的一部分,我可以保证在最后一个地图上使用的累加器不会包含任何重复项吗?

围绕这个问题澄清会很棒!感谢。

3 个答案:

答案 0 :(得分:19)

回答问题"累积器何时真正可靠?"

答案:当他们出现在操作操作中时。

根据Action Task中的文档,即使存在任何重新启动的任务,它也只会更新一次Accumulator。

  

对于仅在操作内执行的累加器更新,Spark保证每个任务对累加器的更新仅应用一次,即重新启动的任务不会更新该值。在转换中,用户应该知道,如果重新执行任务或作业阶段,每个任务的更新可能会被多次应用。

并且操作允许运行自定义代码。

对于前。

val accNotEmpty = sc.accumulator(0)
ip.foreach(x=>{
  if(x!=""){
    accNotEmpty += 1
  }
})

但是,为什么 Map + Action即。对于累加器操作,结果任务操作不可靠

  1. 由于代码中的一些异常,任务失败。 Spark将尝试4次(默认尝试次数)。如果任务失败,每次都会发出异常。如果成功,则Spark会继续,只更新累加器值以获得成功状态,失败状态累加器值将被忽略。判决:正确处理
  2. 阶段失败:如果执行程序节点崩溃,则没有用户故障而是硬件故障 - 如果节点在随机播放阶段出现故障。由于随机播放输出存储在本地,如果节点发生故障,则随机播放输出消失。所以Spark返回到生成shuffle输出的阶段,查看需要重新运行的任务,并在其中一个仍处于活动状态的节点上执行它们。重新生成缺失的shuffle输出后,生成map输出的阶段已多次执行其中的一些任务.Spark计算所有这些任务的累加器更新。
    判决:未在Result Task.Accumulator中处理错误输出。
  3. 如果某个任务运行缓慢,那么Spark可以在另一个节点上启动该任务的推测副本。
    判决:未处理.Accumulator将输出错误。
  4. 缓存的RDD很大,无法驻留在Memory.So中。每当使用RDD时,它将重新运行Map操作以获取RDD,并且再次累加器将由它更新。
    判决:未处理。累积器将输出错误。
  5. 因此可能会发生同一个函数可能在同一个数据上运行多次。因为Map操作,Spark不会为累加器更新提供任何保证。

    因此最好在Spark中使用Accumulator in Action操作。

    要了解有关累积器及其问题的更多信息,请参阅此Blog Post - 作者:Imran Rashid。

答案 1 :(得分:18)

成功完成任务后,累积器更新将发送回驱动程序。因此,当您确定每个任务只执行一次并且每个任务按预期执行时,您的累加器结果将保证正确。

我更倾向于依赖reduceaggregate而不是累加器,因为很难枚举任务执行的所有方式。

  • 动作启动任务。
  • 如果某个操作依赖于前一个阶段,并且该阶段的结果未被(完全)缓存,则将启动早期阶段的任务。
  • 当检测到少量慢速任务时,推测执行会启动重复任务。

也就是说,有许多简单的情况可以完全信任累加器。

val acc = sc.accumulator(0)
val rdd = sc.parallelize(1 to 10, 2)
val accumulating = rdd.map { x => acc += 1; x }
accumulating.count
assert(acc == 10)
  

这会保证是否正确(没有重复)?

是的,如果禁用推测执行。 mapcount将是一个阶段,所以就像你说的那样,任务都无法成功执行多次。

但是累加器会更新为副作用。因此,在考虑如何执行代码时,您必须非常小心。请考虑这一点,而不是accumulating.count

// Same setup as before.
accumulating.mapPartitions(p => Iterator(p.next)).collect
assert(acc == 2)

这也将为每个分区创建一个任务,并且每个任务将保证只执行一次。但是map中的代码不会在所有元素上执行,只是每个分区中的第一个元素。

累加器就像一个全局变量。如果你共享一个可以递增累加器的RDD引用,那么其他代码(其他线程)也会导致它递增。

// Same setup as before.
val x = new X(accumulating) // We don't know what X does.
                            // It may trigger the calculation
                            // any number of times.
accumulating.count
assert(acc >= 10)

答案 2 :(得分:1)

我认为Matei在推荐的文档中回答了这个问题:

  

正如https://github.com/apache/spark/pull/2524所讨论的那样   在一般情况下很难提供良好的语义   (累加器在非结果阶段内更新),以下内容   理由:

     
      
  • 可以将RDD计算为多个阶段的一部分。对于   例如,如果更新MappedRDD中的累加器,然后   洗牌,这可能是一个阶段。但是如果你再次调用map()   在MappedRDD上,并将其结果洗牌,你得到一秒钟   这个地图是管道的阶段。你想数这个吗?   累加器更新两次或不?

  •   
  • 如果是,则可以重新提交整个阶段   随机清理器删除随机文件或由于a而丢失   节点故障,所以跟踪RDD的任何事情都需要这样做   长时间(只要RDD在用户中是可引用的   程序),实施起来相当复杂。

  •   
     

所以我要去   将此标记为"不会修复"目前,除了结果部分   在SPARK-3628中完成的阶段。