SparkCore之累加器&广播变量

一、累加器

  • 累加器:分布式共享只写变量。(Task和Task之间不能读数据)
  • 累加器用来对数据的聚合,通常在向Spark传递函数时,比如使用map()函数或者用 filter()传条件时,可以使用驱动器程序中定义的变量,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能,那么累加器可以实现我们想要的效果。

1.1 系统累加器

  • 代码实现
package com.spark.day06

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

import scala.util.parsing.json.JSON

/*
* 累加器:
*   分布式共享只写变量——task之间不能读取数据
*
* */

object Spark06_Accumulator {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("Test").setMaster("local[*]")
    val sc = new SparkContext(conf)

    // 创建一个RDD,单值RDD,并对其进行求和,不存在shuffle过程
//    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
//    println(rdd.sum())
//    println(rdd.reduce(_+_))

    // 存在shuffle,效率低
//    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("d", 4)))
//    val resRDD: RDD[(String, Int)] = rdd.reduceByKey(_ + _)
//
//    resRDD.map(_._2).collect().foreach(println)

    // 如果定义一个普通变量,那么在Driver定义,Executor会创建变量的副本,算子都是对副本进行操作,Driver端的变量不会更新
    // 在driver端创建一个sum。
//    var sum:Int = 0
//    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("d", 4)))
//    rdd.foreach{
//      case (word, count) => {
//        sum += count  // 在这里会在每个executor上创建一个sum的副本,每个执行自己的
//      }
//    }
//
//    println(sum)

    // 如果需要通过excutor,对Driver端定义的变量进行更新,需要定义为累加器
    // 累加器和普通变量相比,会将Excutor端的结果,收集到Driver端进行汇总

    // 创建累加器
//    val sum: Accumulator[Int] = sc.accumulator(10)  // 过时了
    val sum: LongAccumulator = sc.longAccumulator("myAcc")
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("d", 4)), 2)
    rdd.foreach{
      case (word, count) => {
        sum.add(count)
        println(sum.value)
      }
    }

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



    sc.stop()
  }
}

1.2 自定义累加器

  • 自定义累加器类型的功能在1.X版本中就已经提供了,但是使用起来比较麻烦,在2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式。

1.2.1 自定义累加器步骤

  • 继承AccumulatorV2,设定输入、输出泛型
  • 重写方法

1.2.2 举例

  • 需求:自定义累加器,统计集合中首字母为“H”单词出现的次数。
  • 代码实现
package com.spark.day06

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

import scala.collection.mutable

/*
* 累加器:
*   自定义累加器
*     自定义累加器,统计出RDD中,所有以"H"开头的单词以及出现的次数
*
* */

object Spark07_Accumulator {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("Test").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //创建RDD
    val rdd: RDD[String] = sc.makeRDD(List("Hello", "Hello", "Haha", "Haha", "Hello", "Spark", "Spark"))

    // 创建累加器对象
    val myAcc = new MyAccumulator

    // 注册累加器
    sc.register(myAcc)

    // 使用累加器
    rdd.foreach{
      word => {
        myAcc.add(word)
      }
    }

    // 输出累加器结果
    println(myAcc.value)

    sc.stop()
  }
}


// 定义一个类,继承AccumulatorV2
// 泛型累加器输入和输出数据的类型
// 执行过程:先copy、再reset、然后isZero,
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Int]] {

  // 定义一个集合,记录单词以及出现次数
  var map = mutable.Map[String, Int]()

  // 是否为初始状态
  override def isZero: Boolean = map.isEmpty

  // 拷贝,在不同的Excutor上都有副本,通过这个方法进行拷贝
  override def copy(): AccumulatorV2[String, mutable.Map[String, Int]] = {
    val newAcc = new MyAccumulator
    newAcc.map = this.map
    newAcc
  }

  // 重置,回到初始状态
  override def reset(): Unit = map.clear()

  // 向累加器中添加元素
  override def add(elem: String): Unit = {
    // 向可变集合中添加或者更新元素
    if (elem.startsWith("H")) {
      map(elem) = map.getOrElse(elem, 0) + 1
    }
  }

  // 合并
  override def merge(other: AccumulatorV2[String, mutable.Map[String, Int]]): Unit = {
    // 当前Excutor的map
    var map1 = map

    // 另一个Excutor的map
    var map2 = other.value

    map = map1.foldLeft(map2) {
      // mm表示map2,kv表示map1中的每一个元素
      (mm, kv) => {
        // 指定合并规则
        val k: String = kv._1
        val v: Int = kv._2

        // 根据map1中元素的key,到map2中获取元素
        mm(k) = mm.getOrElse(k, 0) + v
        mm
      }
    }
  }

  // 获取累加器的值
  override def value: mutable.Map[String, Int] = map
}

二、广播变量

  • 广播变量:分布式共享只读变量。
  • 在多个并行操作中(Executor)使用同一个变量,Spark默认会为每个任务(Task)分别发送,这样如果共享比较大的对象,会占用很大工作节点的内存。广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起来都很顺手。

2.1 使用广播变量步骤

  • 通过对一个类型T的对象调用SparkContext.broadcast创建出一个Broadcast[T]对象,任何可序列化的类型都可以这么实现。
  • 通过value属性访问该对象的值(在Java中为value()方法)。
  • 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。

2.2 原理说明

2.3 具体实现

package com.spark.day06

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

/*
* 广播变量
*
* */

object Spark08_Broadcast {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
    val sc = new SparkContext(conf)

    // 想实现类似join功能, (a, (1, 4))
    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val list = List(("a", 1), ("b", 2), ("c", 3))

    // 创建一个广播变量
    val broadcastList: Broadcast[List[(String, Int)]] = sc.broadcast(list)

    val resRDD: RDD[(String, (Int, Int))] = rdd.map {
      case (k1, v1) => {
        var v3 = 0
//        for ((k2, v2) <- list) {
        for ((k2, v2) <- broadcastList.value) {
          if (k1 == k2) {
            v3 = v2
          }
        }
        (k1, (v1, v3))
      }
    }

    resRDD.collect().foreach(println)

    sc.stop()
  }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值