1. 累加器是什么
累加器用来对信息进行聚合,通常在向 Spark传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用driver端中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,
更新这些副本的值也不会影响驱动器(driver)中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。
累加器可以理解为共享只写变量
2. 为什么使用累加器
先看这个例子,计算俩个分区内数据,这里使用了foreach算子
完sum=0;
/**
* 对RDD内数据做累加,原始RDD有俩个分区
*/
object Test {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("test1")
val sc: SparkContext = new SparkContext(conf)
val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
//普通reduce计算
val res: Int = rdd1.reduce(_ + _)
//结果10
println(res)
//利用foreach算子计算
var sum:Int=0
rdd1.foreach(
i=>sum=sum+i
)
//结果0
println(sum)
}
}
下面是分析原因: 为什么结果是0 不是正確的15呢???
这里做画图解释:
a.sum变量定义在driver 端
//driver 端执行
var sum:Int=0
b. foreach算子内计算逻辑( i=>sum=sum+i)在俩个excutor端
rdd1.foreach(
//算子内代码在excutor 端执行
//sum 变量利用java 序列化传到excutor
i=>sum=sum+i
)
c. sum 会序列化到每个excuror,每个excutor会计算出一个sum
一个分区sum =1+2=3
一个分区sum=3+4=7
d. 但是最終输出代码在driver端执行,此时excutor的俩个sum 无法传给driver ,这导致sum 还是初始值0
//driver 端执行
println(sum)
e. 假如现在有一个变量driver 和各个excutor都能访问并且修改
这样就可以做到foreach算子做不到的累加了,这样累加器就出现了
3. spark系统自带累加器
通过在driver中调用SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。
返回值为 org.apache.spark.Accumulator[T] 对象,其中 T 是初始值 initialValue 的类型。
Spark闭包里的执行器代码可以使用累加器的 += 方法(在Java中是 add)增加累加器的值。
driver可以调用累加器的value属性
(在Java中使用value()或setValue())来访问累加器的值。
注意:工作节点上的任务不能访问累加器的值。从这些任务的角度来看,累加器是一个只写变量。
对于要在行动操作中使用的累加器,Spark只会把每个任务对各累加器的修改应用一次。因此,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在 foreach() 这样的行动操作中。转化操作中累加器可能会发生不止一次更新
spark系统自带累加器是spark 对excutor 端数据进行返回的
下面直接上代码康康累加器使用
object Test {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("test1")
val sc: SparkContext = new SparkContext(conf)
val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5),2)
//创建累加器
val accumulator: LongAccumulator = sc.longAccumulator
//对比foreach 算子
/* rdd1.foreach(
i=>sum=sum+i
)*/
//使用累加器
rdd1.foreach(
i=>accumulator.add(i)
)
//获取累加器值 結果10
println(accumulator.value)
}
}
4. spark自定义累加器
spark系统自带累加器只有对数字累加,需要其他功能需要定义累加器,
自定义累加器需要继承AccumulatorV2抽象类,实现父类的六个方法
该案例累加器内部维护的是mutable.HashMap
class SesionAccumulator extends AccumulatorV2[String,mutable.HashMap[String,Int]]{
//必须可变的hashMap
var countMap = new HashMap[String,Int]()
//初始值
override def isZero: Boolean = {
countMap.isEmpty
}
//把当前累加器复制给其他累加器
override def copy(): AccumulatorV2[String, mutable.HashMap[String, Int]] = {
var acc = new SesionAccumulator
acc.countMap ++= this.countMap
acc
}
//累加器重置
override def reset(): Unit = {
countMap.clear()
}
//累加器实现的加操作
override def add(k: String): Unit = {
if(!this.countMap.contains(k)){
this.countMap += (k->0)
}else{
this.countMap.update(k,countMap(k)+1)
}
}
//俩个累加器合并
override def merge(other: AccumulatorV2[String, mutable.HashMap[String, Int]]): Unit = {
other match{
//this.countMap 是foldLeft的初始值
// (map,(k,v))=>map += (k->(map.getOrElse(k,0)+v)) 是foldleft的操作
case acc:SesionAccumulator =>acc.countMap.foldLeft(this.countMap){
case (map,(k,v))=>map += (k->(map.getOrElse(k,0)+v))
}
}
}
//最后返回
override def value: mutable.HashMap[String, Int] = {
this.countMap
}
}
注册累加器:
val acc = new SesionAccumulator
sparkSession.sparkContext.register(acc)
使用累加器
//只要在标记key,就给当前key 的值加一
acc.add(Constants.SESSION_COUNT)
5. 累加器实际工作中使用
更新ing