文章地址:http://www.haha174.top/article/details/257359
项目源码:https://github.com/haha174/spark.git
spark 一个非常重要的特性就是共享变量,默认情况下如果一个算子函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task 中,此时每个task 只能操作自己那份变量副本。如果多个task 想要共享某个变量这种方式是做不到的。
spark 为此提供了两种共享变量,一种是Broadcast(广播变量),另一种是Accumulator(累加变量)。broadcast Variable 会将使用到的变量。仅仅为每个节点拷贝一份。更大的用处是优化性能。减少网络传输以及内存的消耗。Accumulator则可以让多个task 共同操作一份变量,主要可以进行累加操作。
默认情况下,函数的算子使用到了外部的变量,会拷贝一份到执行这个函数的每一个task中,如果那个变量特别大的话那么想想看这个网络传输是不是特别的大,而且在每个节点上,占用的内存空间,是不是也特别的大。
不使用共享变量如图所示:
如果把算子函数使用到的变量做成共享变量的话那么每个变量,只会拷贝一份到每个节点上,节点上所有的task ,都会共享这个一份变量。
spark 提供Broadcast Variable 是只读的,并且每个节点上会只有一个副本,而不会给每个task 拷贝一份副本,因此最大其最大作用,就是减少到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外spark自己内部也使用了高效的广播算法来减少网络消耗。
可以通过调用SparkContext的broadcast()方法。来针对某个变量创建广播变量。然后在算子的范围内部使用到广播变量时,每个节点只会拷贝一份副本。每个节点可以使用广播变量的value()方法取值。备注广播变量时只读的。
下面给出一个java 示例:
public class BroadCastDemo {
public static void main(String[] args){
SparkConf conf=new SparkConf().setAppName("BroatCast").setMaster("local");
JavaSparkContext sc=new JavaSparkContext(conf);
List<Integer> listNumber= Arrays.asList(1,2,3,4,5);
final int factor=3;
//在java 中 创建广播变量
final Broadcast<Integer> factoryBroatCast=sc.broadcast(factor);
JavaRDD<Integer> rddList=sc.parallelize(listNumber);
JavaRDD<Integer> result=rddList.map(new Function<Integer, Integer>() {
public Integer call(Integer integer) throws Exception {
//在java 中 获取广播变量
return integer*factoryBroatCast.value();
}
});
result.foreach(new VoidFunction<Integer>() {
@Override
public void call(Integer integer) throws Exception {
System.out.println(integer);
}
});
sc.close();
}
}
下面给出一个scala 示例
object BroadCast {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setMaster("local").setAppName("BroadCast")
val sc=new SparkContext(conf)
var factor=3
var fac=sc.broadcast(factor);
var numAray=Array(1,2,3,4,5)
var numList=sc.parallelize(numAray)
var numResult=numList.map(num=>num*fac.value)
numResult.foreach(num=>println(num))
}
}
Accumulator
spark 提供Accumulator 主要针对于多个节点对一个变量进行操作,Accumulator只提供累加的功能,但是却给我们提供了多个task 操作一个变量的功能。但是task只能对Accumulator进行累加操作却不能读取他的值。是有Driver 程序可以读取Accumulator.
下面给出java 示例
public class AccumulatorDemo {
public static void main(String[] args){
SparkConf conf=new SparkConf().setAppName("BroatCast").setMaster("local");
JavaSparkContext sc=new JavaSparkContext(conf);
Accumulator<Integer> sum=sc.accumulator(0);
List<Integer> listNumber= Arrays.asList(1,2,3,4,5);
JavaRDD<Integer> rddList=sc.parallelize(listNumber);
rddList.foreach(new VoidFunction<Integer>(){
public void call(Integer integer) throws Exception {
//在java 中 获取广播变量
sum.add(integer);
}
});
System.out.println(sum.value());
}
}
下面给出scala 示例:
object AccumulatorDemo {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setMaster("local").setAppName("AccumulatorDemo")
val sc=new SparkContext(conf)
val accum = sc.accumulator(0)
sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x)
println(accum)
}
累加器与容错
对于失效节点和慢节点,Spark会自动通过重新执行(re-executing)失效任务或慢任务。而有时,即使没有节点失效,Spark可能会需要重新执行一遍tasks来重建一个被移出内存的缓存值,这就导致同一数据上的同一函数可能会因此执行多次。
对于在RDD转换(Transformation)操作中的累加器,一个累加器的更新可能会出现多次。出现这种现象的一种可能情况是,一个被缓存,但是不经常使用的RDD被第一次弹出LRU cache队列,但是之后又需要使用了。这时RDD会根据其血统(lineage)被重新计算,而累加器上的更新也因此多执行了一遍,并返回给driver。因此,建议在转换操作中使用的累加器仅用于调试目的。