spark 累加器

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值