我有一个递归函数,需要将当前调用的结果与之前的调用进行比较,以确定它是否已达到收敛。我的函数不包含任何action
- 它只包含map
,flatMap
和reduceByKey
。由于Spark不评估转换(直到调用一个动作),我的下一次迭代没有得到正确的值来比较收敛。
这是函数的骨架 -
def func1(sc: SparkContext, nodes:RDD[List[Long]], didConverge: Boolean, changeCount: Int) RDD[(Long] = {
if (didConverge)
nodes
else {
val currChangeCount = sc.accumulator(0, "xyz")
val newNodes = performSomeOps(nodes, currChangeCount) // does a few map/flatMap/reduceByKey operations
if (currChangeCount.value == changeCount) {
func1(sc, newNodes, true, currChangeCount.value)
} else {
func1(sc, newNode, false, currChangeCount.value)
}
}
}
performSomeOps
仅包含map
,flatMap
和reduceByKey
转换。由于它没有任何操作,performSomeOps
中的代码不会执行。所以我的currChangeCount
没有得到实际数。这意味着,检查收敛的条件(currChangeCount.value == changeCount
)将无效。要克服的一种方法是通过调用count
在每次迭代中强制执行操作,但这是不必要的开销。
我想知道我可以做些什么来强制执行不需要太多开销的操作,还是有其他方法可以解决这个问题?
答案 0 :(得分:2)
我相信这里有一个非常important thing:
对于仅在操作内执行的累加器更新,Spark保证每个任务对累加器的更新仅应用一次,即重新启动的任务不会更新该值。在转换中,用户应该知道,如果重新执行任务或作业阶段,每个任务的更新可能会被多次应用。
由于累加器无法可靠地用于管理控制流程,因此更适合作业监控。
此外,执行操作不是不必要的开销。如果你想知道计算的结果是什么,你必须执行它。除非结果当然是微不足道的。最便宜的行动是:
rdd.foreach { case _ => }
但它不会解决你在这里遇到的问题。
一般来说,Spark中的迭代计算结构如下:
def func1(chcekpoinInterval: Int)(sc: SparkContext, nodes:RDD[List[Long]],
didConverge: Boolean, changeCount: Int, iteration: Int) RDD[(Long] = {
if (didConverge) nodes
else {
// Compute and cache new nodes
val newNodes = performSomeOps(nodes, currChangeCount).cache
// Periodically checkpoint to avoid stack overflow
if (iteration % checkpointInterval == 0) newNodes.checkpoint
/* Call a function which computes values
that determines control flow. This execute an action on newNodes.
*/
val changeCount = computeChangeCount(newNodes)
// Unpersist old nodes
nodes.unpersist
func1(checkpointInterval)(
sc, newNodes, currChangeCount.value == changeCount,
currChangeCount.value, iteration + 1
)
}
}
答案 1 :(得分:0)
我看到这些map/flatMap/reduceByKey
转换正在更新累加器。因此,执行所有更新的唯一方法是执行所有这些功能,与count
+ cache
{{{1} {1}}或count
)。
答案 2 :(得分:0)
以前的答案让我走上正确的轨道来解决类似的收敛检测问题。
foreach
在the docs中显示为:
foreach(func)
:在数据集的每个元素上运行函数 func 。这通常用于副作用,例如更新累加器或与外部存储系统交互。
似乎而不是使用 rdd.foreach()
作为廉价操作来触发放置在各种转换中的累加器增量,它应该用于执行递增本身。
我无法生成一个scala示例,但这是一个基本的java版本,如果它仍然可以帮助:
// Convergence is reached when two iterations
// return the same number of results
long previousCount = -1;
long currentCount = 0;
while (previousCount != currentCount){
rdd = doSomethingThatUpdatesRdd(rdd);
// Count entries in new rdd with foreach + accumulator
rdd.foreach(tuple -> accumulator.add(1));
// Update helper values
previousCount = currentCount;
currentCount = accumulator.sum();
accumulator.reset();
}
// Convergence is reached