spark—累加器和广播变量

一、累加器

定义:累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后,传回 Driver 端调用merge方法对数据进行合并。

package com.bigdata.SparkCore.wd

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

/**
 * @author wangbo
 * @version 1.0
 */
object test1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("test1")
    val sc = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))

    val sumAcc: LongAccumulator = sc.longAccumulator("sum")

    rdd.foreach(
      num => {
        //使用累机器
        sumAcc.add(num)
      }
    )

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

    sc.stop()
  }
}

累机器的类型有很多,如sc.doubleAccumulator()、sc.collectionAccumulator()集合类型 等,但这里的集合是list,如果是map的话还需要其他操作。

累加器的一些小问题

少加的情况:转换算子中调用累加器,如果没有行动算子的话,则不会执行累机器

多加的情况:转换算子中调用累加器,如果有多个行动算子,则会多次执行,多次累加

一般情况下,累加器会放置在行动算子里进行操作

自定义累加器

package com.bigdata.SparkCore.wd

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

import scala.collection.mutable

/**
 * @author wangbo
 * @version 1.0
 */
object test1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("test1")
    val sc = new SparkContext(sparkConf)
    val rdd: RDD[String] = sc.makeRDD(List("java", "scala", "java"))


    //累加器:WordCount
    //创建累加器
    val wcAcc = new MyAccumulator()
    //向spark进行注册
    sc.register(wcAcc,name = "wordCountAcc")


    rdd.foreach(
      word => {
        //数据的累加(使用累加器)
        wcAcc.add(word)
      }
    )

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

    sc.stop()
  }

  /**
   * 继承AccumulatorV2
   * 自己定义泛型
   * IN:累加器输入的数据类型  String
   * OUT:累加器返回的数据类型  mutable.Map[String,Long]
   */
  class MyAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
    //定义一个Map来接收wordCount
    private val wcMap: mutable.Map[String, Long] = mutable.Map[String, Long]()

    //判断是否为初始状态
    override def isZero: Boolean = {
      wcMap.isEmpty
    }

    //复制一个新的累加器
    override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
      new MyAccumulator()
    }

    //重置累机器
    override def reset(): Unit = {
      wcMap.clear()
    }

    //获取累机器需要计算的值
    override def add(word : String): Unit = {
      val newCount = wcMap.getOrElse(word ,0L) + 1
      wcMap.update(word,newCount)
    }

    //Driver合并多个累机器,两个累机器的map合并
    override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
      val map1: mutable.Map[String, Long] = this.wcMap
      val map2: mutable.Map[String, Long] = other.value

      map2.foreach{
        case ( word ,count) => {
          val newCount1 = map1.getOrElse(word, 0L) + count
          map1.update(word,newCount1)
        }
      }
    }

    //累机器结构
    override def value: mutable.Map[String, Long] = {
      wcMap
    }
  }
}

二、广播变量

定义:广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。

问题引入

要求:给出两组数据将两个RDD,相同的key的value连接到一起

方法一:使用join方法

package com.bigdata.SparkCore.wd

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable

/**
 * @author wangbo
 * @version 1.0
 */
object test1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("test1")
    val sc = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val rdd2: RDD[(String, Int)] = sc.makeRDD(List(("a", 4), ("b", 5), ("c", 6)))

    val joinRDD: RDD[(String, (Int, Int))] = rdd1.join(rdd2)
    joinRDD.collect().foreach(println)
    
    sc.stop()
  }
}

缺点:

join会有笛卡尔乘积从而导致数据量几何的增长,并且会影响shuffle的性能,不推荐使用

方法二:使用map模式匹配

package com.bigdata.SparkCore.wd

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable

/**
 * @author wangbo
 * @version 1.0
 */
object test1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("test1")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val map1 = mutable.Map(("a", 4), ("b", 5), ("c", 6))

    rdd.map {
      case (w, c) => {
        //getOrElse判断map是中的value是否存在 是则返回该值,否则返回指定的值
        val i: Int = map1.getOrElse(w, 0)	
        (w, (c, i))
      }
    }.collect().foreach(println)

    sc.stop()
  }
}

缺点

闭包数据都是以Task为单位发送的,每个task任务中包含闭包数据。(当数据量很大的时候)这样可能会导致,一个Executor中含有大量重复的数据,并且占用大量的内存,所以性能就会下降。

方法三:使用广播变量

说明:
Executor其实就一个JVM,所以在启动时,会自动分配内存,完全可以将任务中的闭包数据放置在Executor内存中,这样每个task就可以使用Executor共享的数据

spark中的广播变量就可以将闭包的数据保存到Executor的内存中,spark中的广播变量不能够更改(广播变量称为分布式只读共享变量)

package com.bigdata.SparkCore.wd

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

import scala.collection.mutable

/**
 * @author wangbo
 * @version 1.0
 */
object test1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("test1")
    val sc = new SparkContext(sparkConf)

    val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3)))
    val map1 = mutable.Map(("a", 4), ("b", 5), ("c", 6))

    //封装广播变量
    val bc: Broadcast[mutable.Map[String, Int]] = sc.broadcast(map1)

    rdd.map {
      case (w, c) => {
        //访问广播变量
        val i: Int = bc.value.getOrElse(w, 0)
        (w, (c, i))
      }
    }.collect().foreach(println)

    sc.stop()
  }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王博1999

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值