共享变量
当RDD中的转换算子需要用到定义在Driver中的变量的时候,计算节点在运行该转换算子之前,会通过网络将Driver中定义的变量下载到计算节点。同时如果计算节点在本地修改了下载的变量,该修改对Driver端定义的变量不可变(也就是计算节点的变量修改后并不会修改Driver端的变量)。
scala> sc.textFile("file:///root/data/word").foreach(line=> i=i+1)
scala> print(i)
0
广播变量
问题:
当出现超大数据集和小数据集进行连接的时候,能否使用join算子直接进行join,如果不行为什么?
//100GB
var orderItems=List("001 apple 2 4.5","002 pear 1 2.0","001 瓜子 1 7.0")
//10MB
var users=List("001 zhangsan","002 lisi","003 wangwu")
//日志富化操作 001 apple 2 4.5 -> 001 zhangsan apple 2 4.5
var rdd1= sc.makeRDD(orderItems).map(line=>(line.split(" ")(0),line))
var rdd2=sc.makeRDD(users).map(line=>(line.split(" ")(0),line))
rdd1.join(rdd2).collect().foreach(println)
系统在做join的操作的时候会产生shuffle,会在各个计算节点当中传输100GB的数据用于完成join操作,因此join网络代价和内存代价都很高。
因此可以考虑将小数据定义成Driver中成员变量,在Map操作的时候完成join。
scala> var users=List("001 zhangsan","002 lisi","003 wangwu").map(line=>line.split(" ")).map(ts=>ts(0)->ts(1)).toMap
users: scala.collection.immutable.Map[String,String] = Map(001 -> zhangsan, 002 -> lisi, 003 -> wangwu)
scala> var orderItems=List("001 apple 2 4.5","002 pear 1 2.0","001 瓜子 1 7.0")
orderItems: List[String] = List(001 apple 2 4.5, 002 pear 1 2.0, 001 瓜子 1 7.0)
scala> import org.apache.spark.rdd.RDD
import org.apache.spark.rdd.RDD
scala> var rdd1= sc.makeRDD(orderItems).map(line=>(line.split(" ")(0),line))
rdd1: org.apache.spark.rdd.RDD[(String, String)] = MapPartitionsRDD[3] at map at <console>:27
scala> rdd1.collect
res2: Array[(String, String)] = Array((001,001 apple 2 4.5), (002,002 pear 1 2.0), (001,001 瓜子 1 7.0))
scala> rdd1.map(t=> t._2+"\t"+users.get(t._1).getOrElse("未知")).collect().foreach(println)
001 apple 2 4.5 zhangsan
002 pear 1 2.0 lisi
001 瓜子 1 7.0 zhangsan
但是上面的写法会存在一个问题,每当一个map算子遍历元素的时候都会向Driver下载userMap变量,虽然该值不大,但是在计算节点会频繁的下载。正是因为此种情景会导致没有必要的重复变量的copy,Spark提出了广播变量
Spark在程序运行前期,提前将需要广播的变量通知给所有的计算节点,计算节点会对需要广播的变量在计算之前进行下载操作,并且将该变量缓存(也就是一开始下载广播后,之后的操作直接读取自己的jvm内的变量,这里的下载次数是取决于分区数,也就是线程数,有几个线程下载几次)。该计算节点其他线程在使用到该变量的时候就不需要下载。
也就是没广播的话每个线程都得下载一次,有广播就整体下载一次就完事了
下载次数在广播之前取决于线程数,广播之后取决于进程数
scala> val ub = sc.broadcast(users)
ub: org.apache.spark.broadcast.Broadcast[scala.collection.immutable.Map[String,String]] = Broadcast(4)
scala> rdd1.map(t=>t._2+"\t"+ub.value.get(t._1).getOrElse("未知")).collect().foreach(println)
001 apple 2 4.5 zhangsan
002 pear 1 2.0 lisi
001 瓜子 1 7.0 zhangsan
计数器(累加器)
Spark提供的Accumulator,主要用于多个节点对一个变量进行共享性的操作。Accumulator只提供了累加的功能。但是确给我们提供了多个task对一个变量并行操作的功能。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。
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