累加器 – 分布式共享只写变量
为什么要使用累加器
如果不使用累加器 求和,如下代码:
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("flatmap")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(1,2,3,4))
var sum= 0
rdd.foreach(num => sum += num)
println(sum)
sc.stop()
结果为0,因为变量sum和println 都在rdd之外,属于Driver端
rdd内的foreach方法,执行在Executor端,sum在分布式节点中累加之后,并没有将数据传回Driver端,
所以Driver端的sum还是0,打印出来也就是0
使用累加器之后
累加器用来把Executor端变量信息聚合到Driver端。在Driver程序中定义的变量,在Executor端的每个Task都会得到这个变量的一份新的副本,每个task更新这些副本的值后,传回Driver端进行merge。
累加器怎么使用
系统累加器
如果在转换算子中调用累加器,后续没有行动算子,累加器不会执行。后续如果调用了两次行动算子,会执行两次累加器,出现多加的情况。
所以一般情况下, 累加器会放在行动算子中进行操作。
如下代码所示:
val rdd = sc.makeRDD(List(1,2,3,4))
//注册累加器
val sum: LongAccumulator = sc.longAccumulator("sum")
rdd.foreach(num => sum.add(num))
println(sum.value)
自定义累加器
object spark_acc {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("flatmap")
val sc = new SparkContext(sparkConf)
//用自定义累加器实现wordCount
val rdd1 = sc.makeRDD(List("hello","spark","hello","word"))
//创建累加器
val wcAccmulator: MyAccmulator = new MyAccmulator
//注册累加器
sc.register(wcAccmulator,"wcAccmulator")
rdd1.foreach(word=>{
wcAccmulator.add(word)
})
println(wcAccmulator.value)
sc.stop()
}
}
class MyAccmulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
var map: mutable.Map[String, Long] = mutable.Map()
//判断是否 为 初始累加器
override def isZero: Boolean = {
map.isEmpty
}
//复制累加器
override def copy(): AccumulatorV2[String,mutable.Map[String,Long] ]= {
new MyAccmulator
}
//重置累加器
override def reset(): Unit = {
map.clear()
}
//向累加器中 累加数据
override def add(word: String): Unit = {
//传入新单词后,在map中查找该单词原始数量,进行+1 操作
val newCount = map.getOrElse(word,0L) +1
map.update(word,newCount)
}
//将多个map中累加的结果再相加
override def merge(other: AccumulatorV2[String,mutable.Map[String,Long]]): Unit = {
val map1 = this.map
val map2: mutable.Map[String, Long] = other.value
map2.foreach{
case (word,cnt)=>{
val newCount = map1.getOrElse(word,0L) + cnt
map1.update(word,newCount)
}
}
}
override def value: mutable.Map[String,Long] = map
}
广播变量 --分布式共享只读变量
广播变量的好处,不需要每个task带上一份数据,而是变成每个节点的executor一份数据,多个task共享这份数据。
代码演示:
object spark_bc {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("flatmap")
val sc = new SparkContext(sparkConf)
val rdd = sc.makeRDD(List(("a",1),("b",2),("c",3)))
val map = mutable.Map(("a",3),("b",4),("c",5))
//将map广播
val bc = sc.broadcast(map)
//最后需要实现的结果为 ("a",(1,3)),("b",(2,4)),("c",(3,5))
rdd.map{
case (key,num) =>{
val otherNum = bc.value.getOrElse(key,0)
(key,(num,otherNum))
}
}.collect().foreach(println)
}
}