什么是累加器
累加器用来对信息进行聚合
1 算子在计算时,不会影响到driver里的变量的值(driver里的变量称之为共享变量)
2 算子使用的其实都是driver里的变量的一个副本
3 如果想要影响driver里的变量,需要搜集数据到Driver端才行
4 除了搜集之外,Spark提供的累加器也可以完成对Driver中的变量的更新.
为何需要累加器?
算子在计算时,不会影响到driver里的变量的值(driver里的变量称之为共享变量)
object Test_021 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("count").setMaster("local")
val sc = new SparkContext(conf)
var arr = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8), 2)
//sum是在driver上的sum
var sum = 0
//算子是在worker里的executor上里执行的
arr.foreach(x => {
//sum是driver上传送过来的,初始值0,然后再worker上进行累加,并没有累加到driver端的sum上
sum += x
})
//打印的是driver自己的sum,所以结果是0
println(sum) //0
}
}
不用累加器进行求和
object Test_021 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("count").setMaster("local")
val sc = new SparkContext(conf)
var arr = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8), 2)
//sum是在driver上的sum
var sum = 0
arr.collect()foreach(x => {
sum += x
})
println(sum) //36
}
}
低版本累加器
- 低版本累加器,可以帮我们完成求和等操作
- SparkContext有一个accumulator方法
- 调用时,传入一个初始值
- 在累加时,调用累加器的add方法
- 在获取累加器的值时,调用累加器的value方法
object Test_021 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("count").setMaster("local")
val sc = new SparkContext(conf)
var arr = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8), 2)
//使用低版本累加器
var myacc: Accumulator[Int] =sc.accumulator(0)
arr.foreach(x=>{
myacc.add(x)
})
println(myacc.value)//36
}
}
高版本累加器AccumulatorV2
- 本身是个抽象类
- 有一些可用的子类累加器 比如 CollectionAccumulator,DoubleAccumulator,LongAccumulator
- 使用时需要创建子类型对象并在Spark-Context里面注册
object Test_021 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("sum").setMaster("local")
val sc = new SparkContext(conf)
var arr = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8), 2)
//创建累加器对象
//An accumulator for computing sum, count, and average of 64-bit integers.
var myAcc = new LongAccumulator()
//向上下文注册累加器
//Register the given accumulator with given name.
sc.register(myAcc, "sum")
arr.foreach(x => {
myAcc.add(x)
})
println(myAcc.value) //36
}
}
自定义累加器
1 继承AccumulatorV2
2 规定泛型,第一个泛型是要输入的数据类型,第二个是要输出的数据类型
3 定义一个成员变量
object Test_021 {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("sum").setMaster("local")
val sc = new SparkContext(conf)
var arr = sc.parallelize(Array(1, 2, 3, 4, 5, 6, 7, 8), 2)
//创建累加器对象
//An accumulator for computing sum, count, and average of 64-bit integers.
var myAcc = new SumAccumulator
//向上下文注册累加器
//Register the given accumulator with given name.
sc.register(myAcc, "sum")
arr.foreach(x => {
myAcc.add(x)
})
println(myAcc.value) //36
}
}
class SumAccumulator extends AccumulatorV2[Long,Long]{
//定义一个变量,存储累加后的结果
var sum:Long=0
//判断累加器是否为空,true表示空
override def isZero: Boolean = {
//sum的结果为0表示没有累加过,即为空
sum==0
}
//复制累加器对象到别的worker上,也就是创建一个新的累加器对象
override def copy(): AccumulatorV2[Long, Long] ={
val other =new SumAccumulator
//将累加器的对象的值得对象赋值到新的累加器对象上
other.sum=this.sum
other
}
//重置累加器,就是回归初始值
override def reset(): Unit = {
sum=0
}
//将要累加的数据累加到累加器的值上
override def add(v: Long): Unit = {
sum+=v
}
//用于两两合并累加器的值,
override def merge(other: AccumulatorV2[Long, Long]): Unit = {
sum+=other.value
}
override def value: Long = {
sum
}
}
利用自定义累加器统计单词
import org.apache.spark.rdd.RDD
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable
//利用累加器统计单词
object _TestAcc {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("wordcount").setMaster("local")
val sc = new SparkContext(conf)
val words: RDD[String] = sc.parallelize(Array("hello", "word", "hello", "word", "kitty", "word"))
val myAcc = new WordCountAccumulator
sc.register(myAcc)
words.foreach(myAcc.add)
for (elem <- myAcc.value) {
println(elem)
}
}
}
class WordCountAccumulator extends AccumulatorV2[String, mutable.HashMap[String, Int]] {
//成员变量的维护
var map = new mutable.HashMap[String, Int]()
override def isZero: Boolean = {
map.isEmpty
}
override def copy(): AccumulatorV2[String, mutable.HashMap[String, Int]] = {
val newAcc = new WordCountAccumulator
newAcc.map = this.map
newAcc
}
override def reset(): Unit = {
map.clear()
}
override def add(v: String): Unit = {
//分区类累加,查看这个单词是否存在map中,如果不存在,则value是1,如果存在,取出value,累加1
map模式匹配只有两种,要么None,要么Some(value)
map.get(v) match {
case None => map.put(v, 1)
case Some(x) => map.put(v, x + 1)
}
}
override def merge(other: AccumulatorV2[String, mutable.HashMap[String, Int]]): Unit = {
//两个累加器进行合并时,如果有相同单词,就累加value值.如果没有相同的单词,就直接封装原来的值
for (elem <- other.value) {
//表示的是other里的每一个单词的kv对象
//查看this的map中是否有other里的这个单词
map.get(elem._1) match {
case Some(e) => map.put(elem._1, e + elem._2)
case None => map.put(elem._1, elem._2)
}
}
}
override def value: mutable.HashMap[String, Int] = {
map
}
}
注意事项
1.累加器的创建:
1.1.创建一个累加器的实例
1.2.通过sc.register()注册一个累加器
1.3.通过累加器实名.add来添加数据
1.4.通过累加器实例名.value来获取累加器的值
2.最好不要在转换操作中访问累加器(因为血统的关系和转换操作可能执行多次),最好在行动操作中访问
3 由于最终还是要返回到Driver端进行汇报,因此要注意累加的数据量结果的大小问题.
作用:
1.能够精确的统计数据的各种数据例如:
可以统计出符合userID的记录数,在同一个时间段内产生了多少次购买,可以使用ETL进行数据清洗,并使用Accumulator来进行数据的统计
2.作为调试工具,能够观察每个task的信息,通过累加器可以在sparkIUI观察到每个task所处理的记录数