我想使用累加器来收集有关我在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门票的讨论谈论“结果任务”而不是“行动”。例如here和here。这似乎表明结果确实可以保证是正确的,因为我们在之前和行动之前使用acc
因此应该作为单个阶段计算。
我猜这个“结果任务”的概念与所涉及的操作类型有关,是包含一个动作的最后一个,就像在这个例子中一样,它显示了几个操作如何被分成几个阶段(在洋红色,图像取自here):
假设,该链末尾的count()
动作将是同一个最后阶段的一部分,我可以保证在最后一个地图上使用的累加器不会包含任何重复项吗?
围绕这个问题澄清会很棒!感谢。
答案 0 :(得分:19)
回答问题"累积器何时真正可靠?"
答案:当他们出现在操作操作中时。
根据Action Task中的文档,即使存在任何重新启动的任务,它也只会更新一次Accumulator。
对于仅在操作内执行的累加器更新,Spark保证每个任务对累加器的更新仅应用一次,即重新启动的任务不会更新该值。在转换中,用户应该知道,如果重新执行任务或作业阶段,每个任务的更新可能会被多次应用。
并且操作允许运行自定义代码。
对于前。
val accNotEmpty = sc.accumulator(0)
ip.foreach(x=>{
if(x!=""){
accNotEmpty += 1
}
})
但是,为什么 Map + Action即。对于累加器操作,结果任务操作不可靠?
因此可能会发生同一个函数可能在同一个数据上运行多次。因为Map操作,Spark不会为累加器更新提供任何保证。
因此最好在Spark中使用Accumulator in Action操作。
要了解有关累积器及其问题的更多信息,请参阅此Blog Post - 作者:Imran Rashid。
答案 1 :(得分:18)
成功完成任务后,累积器更新将发送回驱动程序。因此,当您确定每个任务只执行一次并且每个任务按预期执行时,您的累加器结果将保证正确。
我更倾向于依赖reduce
和aggregate
而不是累加器,因为很难枚举任务执行的所有方式。
也就是说,有许多简单的情况可以完全信任累加器。
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)
这会保证是否正确(没有重复)?
是的,如果禁用推测执行。 map
和count
将是一个阶段,所以就像你说的那样,任务都无法成功执行多次。
但是累加器会更新为副作用。因此,在考虑如何执行代码时,您必须非常小心。请考虑这一点,而不是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中完成的阶段。