Spark(二)-- RDD 的分布式共享变量(八) -- 累加器(一)

7.1. 累加器

一个小问题

var count = 0

val config = new SparkConf().setAppName("ip_ana").setMaster("local[6]")
val sc = new SparkContext(config)

sc.parallelize(Seq(1, 2, 3, 4, 5))
  .foreach(count += _)

println(count)

(笔记:count += _     会被分发到多个task,每个task的初始值count=0,这样的计算是不正确的)

上面这段代码是一个非常错误的使用, 请不要仿照, 这段代码只是为了证明一些事情

先明确两件事, var count = 0 是在 Driver 中定义的, foreach(count += _) 这个算子以及传递进去的闭包运行在 Executor 中

这段代码整体想做的事情是累加一个变量, 但是这段代码的写法却做不到这件事, 原因也很简单, 因为具体的算子是闭包, 被分发给不同的节点运行, 所以这个闭包中累加的并不是 Driver 中的这个变量

全局累加器

Accumulators(累加器) 是一个只支持 added(添加) 的分布式变量, 可以在分布式环境下保持一致性, 并且能够做到高效的并发.

原生 Spark 支持数值型的累加器, 可以用于实现计数或者求和, 开发者也可以使用自定义累加器以实现更高级的需求

val config = new SparkConf().setAppName("ip_ana").setMaster("local[6]")
val sc = new SparkContext(config)

val counter = sc.longAccumulator("counter")

sc.parallelize(Seq(1, 2, 3, 4, 5))
  .foreach(counter.add(_))

// 运行结果: 15
println(counter.value)

注意点:

  • Accumulator 是支持并发并行的, 在任何地方都可以通过 add 来修改数值, 无论是 Driver 还是 Executor

  • 只能在 Driver 中才能调用 value 来获取数值

在 WebUI 中关于 Job 部分也可以看到 Accumulator 的信息, 以及其运行的情况

41b76292cc02a2e51cb086171e3420fb

累计器件还有两个小特性, 第一, 累加器能保证在 Spark 任务出现问题被重启的时候不会出现重复计算. 第二, 累加器只有在 Action 执行的时候才会被触发.

val config = new SparkConf().setAppName("ip_ana").setMaster("local[6]")
val sc = new SparkContext(config)

val counter = sc.longAccumulator("counter")

sc.parallelize(Seq(1, 2, 3, 4, 5))
  .map(counter.add(_)) // 这个地方不是 Action, 而是一个 Transformation

// 运行结果是 0
println(counter.value)

自定义累加器

开发者可以通过自定义累加器来实现更多类型的累加器, 累加器的作用远远不只是累加, 比如可以实现一个累加器, 用于向里面添加一些运行信息

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.util.AccumulatorV2
import org.junit.Test

class Accumulator {

  @Test
  def acc() : Unit = {

    val config = new SparkConf().setMaster("local[6]").setAppName("acc")
    val sc = new SparkContext(config)

    val numAcc = new NumAccumulator()
    //注册给spark
    sc.register(numAcc,"num")

    sc.parallelize(Seq("1","2","3")).foreach(x => numAcc.add(x))

    println(numAcc.value)

  }

}

/**
  * 自定义累加器  实现 String -> Set[String]
  */
class NumAccumulator extends AccumulatorV2[String,Set[String]]{

  //需要声明可变的Set集合
  private val nums : scala.collection.mutable.Set[String] = scala.collection.mutable.Set()

  /**
    * 告诉Spark对象,这个累加器对象是否为空的
    * @return
    */
  override def isZero: Boolean = {
    nums.isEmpty
  }

  /**
    * 提供给Spark框架一个拷贝的累加器
    * @return
    */
  override def copy(): AccumulatorV2[String, Set[String]] = {
    val newAccumulator = new NumAccumulator()
    //这里需要保证线程安全
    nums.synchronized{
      newAccumulator.nums ++= this.nums
    }
    newAccumulator
  }

  /**
    * 帮助Spark框架,清理累加器的内容
    */
  override def reset(): Unit = {
    nums.clear()
  }

  /**
    * 外部传入的累加对象,在这个方法进行累加
    * @param v
    */
  override def add(v: String): Unit = {
    nums += v
  }

  /**
    * 累加器在进行累加的时候,可能每个分布式节点都有一个实例
    * 在最后Driver进行一次合并,把所有实例的内容合并起来,会调用这个merge方法进行合并
    * @param other
    */
  override def merge(other: AccumulatorV2[String, Set[String]]): Unit = {
    nums ++= other.value
  }

  /**
    * 提供给外部累加结果
    * 为什么一定要是不可变的,因为外部有可能再进行修改,如果有可变的集合,其外部的修改会影响内部的值
    * @return
    */
  override def value: Set[String] = {
    nums.toSet
  }
}

注意点:

  • 可以通过继承 AccumulatorV2 来创建新的累加器

  • 有几个方法需要重写

    • reset 方法用于把累加器重置为 0

    • add 方法用于把其它值添加到累加器中

    • merge 方法用于指定如何合并其他的累加器

  • value 需要返回一个不可变的集合, 因为不能因为外部的修改而影响自身的值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值