Spark 之 Accumulator 累加器

累加器作用

  • 累加器:分布式只写变量(Executor端的task不能互相访问累加器的值)。
  • 累加器对信息进行聚合。

源码

累加器的基类,可以累加 IN 类型的输入,并产生 OUT 类型的输出。
OUT 应该是可以原子读取的类型(例如,IntLong)或线程安全的(例如,synchronized collections),因为它将从其他线程读取。

abstract class AccumulatorV2[IN, OUT] extends Serializable {
		...
}

累加器原理图

在这里插入图片描述

Spark中累加器的执行流程:

  • 1.首先序列化 driver 端 accumulator 到 executor ,序列化前调用 reset 重置 value 并使用 isZero 检测是否重置成
  • 2.有几个task,spark engine就调用copy方法拷贝几个累加器(不注册的)
  • 3.单个 executor 内使用 add 进行累加(注意在此过程中,被最初注册的累加器的值是不变的),
  • 4.最终 driver 端对多个 executor 间的 accumulaotr 使用merge 进行合并得到结果。

累加器使用demo

  • demo1
package test
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, SparkSession}
import org.apache.spark.util.AccumulatorV2

import scala.collection.mutable

object AccumulatorTest {
  def main(args: Array[String]): Unit = {
    val session: SparkSession = SparkSession.builder().appName(this.getClass.getSimpleName)
      .master("local[6]").getOrCreate()
    val sc: SparkContext = session.sparkContext

    val rdd2: RDD[(String, String)] = sc.makeRDD(List(("home", "北京"),("name", "小宽"), ("age", "30"), ("tools", "篮球"), ("sex", "男")), 4)

    //注册累加器
    val myAcc2: MyAccumulator = new MyAccumulator
    sc.register(myAcc2,"myAcc2")

    println(rdd2.getNumPartitions)

//    rdd2.mapPartitions(iter=>{
//     iter.map(f=>{
//       myAcc2.add(f._1,f._2)
//     })
//    })                       //map算子累加器不加

    rdd2.foreachPartition(iter=>{
      iter.foreach(f=>{
        myAcc2.add(f._1,f._2)
      })
    })

    myAcc2.value.foreach(println)

    while (true){}
    session.close()
  }
}
//(String,String) -> 输入数据格式 , mutable.Map[String,AnyRef]-> 输出格式
class MyAccumulator extends AccumulatorV2[(String,String),mutable.Map[String,AnyRef]] {
  //声明一个Map 存放最终结果
  private val map: mutable.Map[String, AnyRef] = mutable.Map[String, AnyRef]()

  override def reset(): Unit = map.clear()

  override def isZero: Boolean = map.isEmpty

  //driver copy累加器到executor
  override def copy(): AccumulatorV2[(String, String), mutable.Map[String, AnyRef]] = new MyAccumulator

  //executor 内部累加
  override def add(v: (String, String)): Unit = map.put(v._1,v._2)

  //累加器在driver端聚合
  override def merge(other: AccumulatorV2[(String, String), mutable.Map[String, AnyRef]]): Unit = {

    val thisMap: mutable.Map[String, AnyRef] = this.map //driver端的map
    val otherMap: mutable.Map[String, AnyRef] = other.value //executor返回来的计算结果
    thisMap ++= otherMap
  }

  //返回累加器
  override def value: mutable.Map[String, AnyRef] = map
}

spark ui

在这里插入图片描述

  • demo2 WC
    val rdd2 = sc.makeRDD(List("spark","flink","java","scala","java"))

    val myAcc2: MyAccumulator2 = new MyAccumulator2
    sc.register(myAcc2,"myAcc2")

    rdd2.foreachPartition(iter=>{
      iter.foreach(f=>{
        myAcc2.add(f)
      })
    })
    
// 自定义累加器
class MyAccumulator2 extends AccumulatorV2[String,mutable.Map[String,Long]] {
  //声明一个Map 存放最终结果
  private val map = mutable.Map[String, Long]()

  override def reset(): Unit = map.clear()

  override def isZero: Boolean = map.isEmpty

  override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = new MyAccumulator2


  override def add(v: String): Unit = {
    val l: Long = map.getOrElse(v, 0L) +1
    map.update(v,l)
  }

  override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
    val map1: mutable.Map[String, Long] = this.map
    val map2: mutable.Map[String, Long] = other.value

    map2.foreach{
      case (k,v)=>{
        val l: Long = map1.getOrElse(k,0L) + v
        map1.update(k,l)
      }
    }
  }
  override def value: mutable.Map[String, Long] = this.map
}

	输出:
	//(spark,1)
	//(scala,1)
	//(flink,1)
	//(java,2)

使用累加器中可能遇到的坑

当我们把累加器的操作放在 map 中执行的时候,后续如果有多个 action 操作共用该累加器的 RDD ,将会导致重复执行。也就意味着累加器会重复累加。为了避免这种错误,我们最好只在 action 算子如 foreach 中使用累加器,如果实在需要在 transformation 中使用,记得使用 cache 操作

1.累加器少加

 //注册累加器
    val myAcc: LongAccumulator = sc.longAccumulator("myAcc")

    //todo 1.累加器少加 : 累计器是lazy加载的 没action算子 不执行
   sc.parallelize(1 to 20).map(myAcc.add(_))
   println(myAcc.value) // 0

2.累加器多加

   //todo 2.累加器多加: 多个action算子 重复执行
    val rdd: RDD[Int] = sc.parallelize(1 to 10).map(f => {
      myAcc.add(1)
      f + 1
    })

    rdd.count()
    println("AccumuLator1: " + myAcc.value)
    rdd.reduce(_+_)
    println("AccumuLator2: " + myAcc.value)
	  
	  //AccumuLator1: 10
      //AccumuLator2: 20

map算子实际上被执行了两次,在reduce操作提交作业后累加器又完成了一轮技数,所以最终的累加器的值为20。究其原因是因为count虽然促使numberRDD累计出来,但是由于没有对其进行缓存,所以下次再次需要使用numberRDD这个数据集时,还需要从并行化数据集的部分开始执行计算

2.1避免累加器多加

//2.1 cache
//在count之前调用rdd的cache方法(或persist),这样在count后数据集就会被缓存下来
//reduce操作就会读取缓存的数据集,而无需从头开始计算。
 val rdd: RDD[Int] = sc.parallelize(1 to 10).map(f => {
      myAcc.add(1)
      f + 1
    })
	
	rdd.cache()
    rdd.count()
    println("AccumuLator1: " + myAcc.value)
    rdd.reduce(_+_)
    println("AccumuLator2: " + myAcc.value)
    
    //AccumuLator1: 10
    //AccumuLator2: 10
  //todo 2.2. 如果累计器在actions操作算子里面执行时,只会累加一次
    val rdd2: RDD[Int] = sc.parallelize(1 to 10)
    rdd2.foreach(f=>{
      myAcc.add(1)
      f+1
    })
    println("AccumuLator1: " + myAcc.value)
    rdd2.count()
    println("AccumuLator2: " + myAcc.value)

    //AccumuLator1: 10
    //AccumuLator2: 10
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值