通常情况下,当向Spark操作(如map,reduce)传递一个函数时,它会在一个远程集群节点上执行,它会使用函数中所有变量的副本。
这些变量被复制到所有的机器上,远程机器上并没有被更新的变量会向驱动程序回传。在任务之间使用通用的,支持读写的共享变量是低效的。
尽管如此,Spark提供了两种有限类型的共享变量,广播变量和累加器
广播变量的好处,不是每个task一份变量副本,而是变成每个节点的executor才一份副本。这样的话,就可以让变量产生的副本大大减少。
广播变量允许程序员在每台机器上保留只读变量,而不是使用任务运送它的副本。 例如,可以使用它们以有效的方式为每个节点提供大型输入数据集的副本。 Spark还尝试使用高效的广播算法分发广播变量,以降低通信成本。
Spark动作通过一组分阶段执行,由分散的“随机播放”操作隔开。 Spark自动广播每个阶段任务所需的通用数据。 以这种方式广播的数据以序列化形式进行缓存,并在运行每个任务之前进行反序列化。 这意味着,显式创建广播变量仅在跨多个阶段的任务需要相同数据或者以反序列化格式缓存数据很重要时才有用。
scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)
scala> val number = 10
广播变量
scala> val broadcastNumber = sc.broadcast(number)
scala> val data = sc.parallelize(1 to 10)
调用广播变量
scala> val bn = data.map(_* broadcastNumber.value)
每个task执行是都要拷贝一个全局的副本, 没个task处理数据时,都要拷贝一个数据副本,
如果有一百万个task并行执行, 那么就要拷贝一百万份数据, 这个数据量时非常大的,如果这个全局变量时非常大的,就会导致oom
每个task执行时,都要拷贝一份它所需的变量
使用广播,所需的变量被广播, 每个exector中一份,放在内存中,供线程池中内部使用
广播是由Driver发给当前Application分配到所有executor内存级别的全局只读变量,
Exeutorz中的线程池中的线程共享全局变量,极大的减少网络传输(否则每个task要拷贝一份),
并极大的节省内存,
蓄能器是仅通过关联和交换操作“添加”的变量,因此可以并行有效地支持。 它们可用于实现计数器(如MapReduce)或总和。 Spark本身支持数字类型的累加器,程序员可以添加对新类型的支持。
作为用户,您可以创建命名或未命名的累加器。 如下图所示,命名的累加器(在本例中为计数器)将显示在Web UI中,用于修改该累加器的阶段。 Spark在“任务”表中显示由任务修改的每个累加器的值。
可以通过调用SparkContext.longAccumulator()或SparkContext.doubleAccumulator()来分别创建Long或Double类型的值来创建一个数字累加器。 然后可以使用add方法将在群集上运行的任务添加到其中。 但是,他们看不到它的价值。 只有驱动程序可以使用其值方法读取累加器的值。
下面的代码显示了一个累加器用于将数组的元素相加:
scala> val accum = sc.longAccumulator("My Accumulator")
accum: org.apache.spark.util.LongAccumulator = LongAccumulator(id: 0, name: Some(My Accumulator), value: 0)
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s
scala> accum.value
res2: Long = 10
实际操作出现问题:
scala> val accum = sc.longAccumulator("My Accumulator")
:27: error: value longAccumulator is not a member of org.apache.spark.SparkContext
val accum = sc.longAccumulator("My Accumulator")
^
scala>
测试案例:
val accum = sc.accumulator(0)
val dataset = sc.parallelize(1 to 10)
dataset.foreach(x=>accum.add(x))
accum.value