累加器和广播变量

1. 共享变量

累加器 (accumulator):用来对信息进行聚合,主要用于累计计数等场景;

广播变量 (broadcast variable):主要用于在节点间高效分发大对象。

2. 理解闭包

2.1 Scala 中闭包的概念

这里先介绍一下 Scala 中关于闭包的概念:

var more = 10

val addMore = (x: Int) => x + more

x : 是一个绑定变量 (bound variable),因为其是该函数的入参,在函数的上下文中有明确的定义;

more : 是一个自由变量 (free variable),因为函数字面量本生并没有给 more 赋予任何含义。

按照定义:在创建函数时,如果需要捕获自由变量,那么包含指向被捕获变量的引用的函数就被称为闭包函数。

2.2 Spark 中的闭包

在实际计算时,Spark 会将对 RDD 操作分解为 Task,Task 运行在 Worker Node 上。

在执行之前,Spark 会对任务进行闭包,如果闭包内涉及到自由变量,则程序会进行拷贝,并将副本变量放在闭包中,之后闭包被序列化并发送给每个执行者。

因此,当在 foreach 函数中引用 counter 时,它将不再是 Driver 节点上的 counter,而是闭包中的副本 counter,默认情况下,副本 counter 更新后的值不会回传到 Driver,所以 counter 的最终值仍然为零。

累加器的原理实际上很简单:就是将每个副本变量的最终值传回 Driver,由 Driver 聚合后得到最终值,并更新原始变量。

累加器在Driver端定义,在Executor端计算,计算结束之后,把各个分区的计算结果收集一下到Driver端,再做累加

 

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


//累加器
object accumulator {
  def main(args: Array[String]): Unit = {
    //本地模式
    val conf: SparkConf = new SparkConf().setAppName("My scala word count").setMaster("local")

    //创建spark上下文对象
    val sc = new SparkContext(conf)

    val dataRDD: RDD[Int] = sc.makeRDD(List(1,2,3,4,5))
    var sum=0
    val unit: RDD[Unit] = dataRDD.map(x => {
      sum += x
    })
    unit.collect()
    println(sum)


    //累加器

    val sum2: LongAccumulator = sc.longAccumulator
    val unit2: RDD[Unit] = dataRDD.map(x => {
      sum2.add(x)
    })
    unit2.collect()
    println(sum2.value)

    var data: RDD[String] = sc.makeRDD(List("hadoop","hive","python","linux"),2)

//    创建累加器
    val wordAccumulator = new WordAccumulator
    //注册累加器
    sc.register(wordAccumulator)

    data.foreach{
      case word =>{
        wordAccumulator.add(word)
      }
    }

    //获取累加器的值
    println(wordAccumulator.value)





  }



}


//声明累加器
//1. 继承AccumulatorV2
//2. 实现抽象方法
//3. 创建累加器

class WordAccumulator extends AccumulatorV2 [String,java.util.ArrayList[String]]{
  val list = new java.util.ArrayList[String]()

  //当前的累加器是否为初始状态
  override def isZero: Boolean = {
    list.isEmpty()
  }

  //复制累加器对象
  override def copy():AccumulatorV2[String,java.util.ArrayList[String]]={
    new WordAccumulator()
  }

  //重置累加器对象
  override def reset(): Unit = {
    list.clear()
  }

  //向累加器中添加元素
  override def add(v: String): Unit = {
    if(v.contains("h")){
      list.add(v)
    }
  }

  //合并累加器
  override def merge(other: AccumulatorV2[String, java.util.ArrayList[String]]): Unit = {
    list.addAll(other.value)
  }

  //获取累加器的结果
  override def value: java.util.ArrayList[String]=list
}

 

3、广播变量

在上面介绍中闭包的过程中我们说道每个 Task 任务的闭包都会持有自由变量的副本,如果变量很大且 Task 任务很多的情况下,这必然会对网络 IO 造成压力,为了解决这个情况,Spark 提供了广播变量。

广播变量的做法很简单:就是不把副本变量分发到每个 Task 中,而是将其分发到每个 Executor,Executor 中的所有 Task 共享一个副本变量。

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


//累加器
object broadcast {
  def main(args: Array[String]): Unit = {
    //本地模式
    val conf: SparkConf = new SparkConf().setAppName("My scala word count").setMaster("local")

    //创建spark上下文对象
    val sc = new SparkContext(conf)
    val broadcastVar: Broadcast[List[Int]] = sc.broadcast(List(1,2,3,4,5))

    val result: List[Int] = broadcastVar.value.map(x=>x*10)
    println(result)
  }
}

 

posted on 2020-10-09 11:20  happygril3  阅读(306)  评论(0)    收藏  举报

导航