多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
Spark 的难点之一便是理解跨集群执行代码时变量和方法的范围和生命周期。在范围之外修改变量的 RDD 操作可能经常引起混淆。比如,在 foreach() 之外修改除累加器之外的其他变量可能会导致未定义的行为。如下代码所示: ```scala var counter = 0 var rdd = sc.parallelize(1 to 100) // 不要像这样做,在RDD中修改RDD外部变量counter rdd.foreach(x => counter += x) println("Counter value: " + counter) //永远为 0 ``` 上述代码的行为可能无法按预期工作。为了执行作业,Spark 将 RDD 操作的处理分解为任务,每个任务由执行程序执行。在执行之前,Spark 计算任务的闭包。<ins>闭包是那些执行程序在 RDD 上执行其计算时必须可见的变量和方法(在本例中为 foreach())</ins>。这个闭包被序列化并发送给每个执行器。 <br/> <ins>传递给每个执行器的闭包中的变量现在是副本</ins>,因此,当在 foreach 函数中引用 counter 时,它不再是驱动节点上的计数器。在驱动节点的内存中仍然有一个计数器,但它对执行器不再可见!执行者只看到来自序列化闭包的副本。因此,counter 的最终值仍然是零,因为 counter 上的所有操作都引用了序列化闭包中的值。<br/> 为了确保在这类场景中定义良好的行为,应该使用累加器。Spark 中的**累加器**专门用于提供一种机制,以便在集群中的工作节点之间执行分割时安全地更新变量。 <br/> 一般来说,像循环或局部定义方法这样的闭包结构不应该用来改变全局状态。<ins>一些这样做的代码可能在本地模式下工作,但那只是偶然的</ins>,而且这样的代码在分布式模式下不会像预期的那样工作。如果需要全局聚合,则使用累加器。