RDD累加器和广播变量
在默认情况下,当Spark在集群的多个不同节点的多个任务上并行运行一个函数时,它会把函数中涉及到的每个变量,在每个任务上都生成一个副本。但是,有时候需要在多个任务之间共享变量,或者在任务(Task)和任务控制节点(Driver Program)之间共享变量。
为了满足这种需求,Spark提供了两种类型的变量:
1.累加器accumulators:累加器支持在所有不同节点之间进行累加计算(比如计数或者求和)
2.广播变量broadcast variables:广播变量用来把变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上的每个任务都生成一个副本。
累加器
不使用累加器
var counter=0
val data=List(1,2,3)
val rdd = sc.parallelize(data)
rdd.foreach(x=>counter+=x)
//结果为:counter:6
println("counter:"+counter)
--------------------------------------------
var counter=0
val conf = new SparkConf().setAppName("tt").setMaster("local[*]")
val sc = new SparkContext(conf)
val data=List(1,2,3)
val rdd = sc.parallelize(data)
rdd.foreach(x=>counter+=x)
//结果为:counter:0
println("counter:"+counter)
使用累加器
通常在向 Spark 传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。这时使用累加器就可以实现我们想要的效果。
val xx: Accumulator[Int] = sc.accumulator(0)
上代码:
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("wc").setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("warn")
//使用scala集合累加
var counter1: Int = 0;
var data = Seq(1,2,3)
data.foreach(x => counter1 += x )
println(counter1)//6
println("-------------------------------------")
//使用rdd累加,未使用累加器
var counter2: Int = 0;
val dataRDD= sc.parallelize(data) //分布式集合的[1,2,3]
dataRDD.foreach(x => counter2 += x)
println(counter2)//0
//RDD操作运行结果是0
//因为foreach中的函数是传递给Worker中的Executor执行,用到了counter2变量
//而counter2变量在Driver端定义的,在传递给Executor的时候,各个Executor都有了一份counter2
//最后各个Executor将各自个x加到自己的counter2上面了,和Driver端的counter2没有关系
println("-------------------------------------")
//使用累加器
val counter3 = sc.accumulator(0)
dataRDD.foreach(x=>counter3+=x)
println(counter3)//6
}
广播变量
不使用广播变量
使用广播变量
上代码演示
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("wc").setMaster("local[*]")
val sc = new SparkContext(conf)
//不适用广播变量
val add = sc.parallelize(List((1,"apple"),(2,"orange"),(3,"banana"),(4,"grape")))
val fruitMap = add.collectAsMap()
val fruitIds = sc.parallelize(List(2,4,1,3))
//根据水果编号取水果名称
val fruitNames = fruitIds.map(x=>fruitMap(x))
fruitNames.foreach(println)
//注意:以上代码看似一点问题没有,但是考虑到数据量如果较大,且Task数较多,
//那么会导致,被各个Task共用到的fruitMap会被多次传输
//应该要减少fruitMap的传输,一台机器上一个,被该台机器中的Task共用即可
//如何做到?---使用广播变量
println("----------------------------------")
val broadcastFruitMap = sc.broadcast(fruitMap)
val fruitname2 = fruitIds.map(x=>broadcastFruitMap.value(x))
fruitname2.foreach(println)
}