Spark累加器

Spark累加器的作用是Driver端将一个公共的可操作对象共享给所有的容器,使得所有容器运行任务时可以同步某一个需要的信息,比如记录某一个数据的出现次数等

这个时候就有人会说,记录次数不是可以直接用Driver端代码的一个变量之后自增不就解决了吗,这里要给大家纠正一个误区,Spark在运行的时候,容器中任务执行所需的资源在向Driver获取时是以副本的形式获取的,并不是直接用原有的

给大家举个例子

package com.wy

import org.apache.spark.{SparkConf, SparkContext}

object Accumulator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setMaster("local").setAppName("accumulator")
    val sc = new SparkContext(conf)
    
    var sum = 0
    
    sc.textFile("./words.txt").foreach { x =>{sum = sum + 1;println(sum)}}

    println(sum+"================================")
    sc.stop()
  }
}

如果运行上面这个流程,看结果会发现sum最后还是0,但是每个executor运行时,是正常自增的,就说明了我上面说的问题,所以我们不能这样操作

而累加器在一开始的时候很简单,功能也很单一,只能是做一个值累加,如下

package com.wy

import org.apache.spark.{SparkConf, SparkContext}

object Accumulator {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setMaster("local").setAppName("accumulator")
    val sc = new SparkContext(conf)
    val accumulator = sc.accumulator(0,"MyA")
    
    sc.textFile("./words.txt").foreach { x =>{accumulator.add(1)}}

    println(accumulator.value)
    sc.stop()
  }
}

但是现在这个API官方不推荐使用了,对它进行了升级,分成了三种更详细的累加器

org.apache.spark.util.CollectionAccumulator;
org.apache.spark.util.DoubleAccumulator;
org.apache.spark.util.LongAccumulator;

创建的时候直接用调用SparkContext的方法就可以,方法名为小驼峰的类名,不过要注意的是,CollectionAccumulator的定义的时候需要对存放的容器泛型,不泛型也可以但是有时候会出莫名其妙的问题

sc.collectionAccumulator[mutable.Map[String, String]]("MyCo")
sc.longAccumulator("MyLong");
sc.doubleAccumulator("MyDouble"); 

用的时候正常用就行

longAccumulator.add(1);
doubleAccumulator.add(1.2);
accum.add(mutable.Map("111" -> "222"))

Spark还支持自定义累加器,但是自定义的时候要注意,Spark提供的自定义累加器API有两个一个是Accumulator,另一个是AccumulatorV2,我们要继承AccumulatorV2,Accumulator太老了过时了

package com.dtdream.driver

import org.apache.spark.rdd.RDD
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}

//继承AccumulatorV2[in,out]类,对输入输出类型做泛型,我们做一个求总次数的累加器
class MyCount extends AccumulatorV2[Int, Int] {

  //准备运行时需要的变量
  var result = 0

  /** *
   * isZero,翻译过来的意思是是否零点
   * 它的作用就是用来做一个判断,在之后调用这个累加器时,每一个executor拿到副本会调用判断是否是对于executor自身来说理想的初始状态
   * 这里我用来判断是否初始值为0,也就是说每一个executor都只需要负责自己计算内容的累加数量就可以了
   * 返回值为真则当前状态是理想的直接使用,为假Spark会调用重置方法
   * @return
   */
  override def isZero: Boolean = {
    result == 0
  }

  /** *
   * 这个方法用途和其名字一样,不过它作用在子节点中的executor容器获取累加器时,用来副本当前累加器状态
   * 因为我一开始就说了,获取资源不是直接拿,而是拷贝副本
   * @return
   */
  override def copy(): AccumulatorV2[Int, Int] = {
    var r = new MyCount()
    r.result = this.result
    r
  }

  /** *
   * 重置方法,作用上面已经说了
   */
  override def reset(): Unit = {
    result=0
  }

  /**
   * 规定累加器如何累加
   *
   * @param v
   */
  override def add(v: Int): Unit = {
    result += v
  }

  /**
   * 合并,在累加最后调用,合并所有executor的结果
   *
   * @param other
   */
  override def merge(other: AccumulatorV2[Int, Int]): Unit = {
    result+=other.value
  }
  
  //返回结果
  override def value: Int= result


}

使用方式如下

def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("TextDriver").setMaster("local[1]")
    val context = new SparkContext(conf)
    val count = new MyCount
    context.register(count)

    val data: RDD[Int] = context.parallelize(Array(1, 1, 1, 1, 1))

    data.foreach(count.add(_))

    println(s"累加器的结果${count.value}")

  }

使用累加器的时候要注意,累加器也遵循Spark算子的规则,在遇到执行算子的时候才会正常触发,而且执行算子不能啥也不干,有很多人测试的时候执行算子直接方法体省略或者用下划线代替了,一运行发现累加器不生效,希望大家不要如这个坑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值