SPark学习笔记:06-SPark的累加器和广播变量

概述

Spark 核心的三大数据结构是RDD、累加器、和广播变量。前面的文章中已经详细的讲解了RDD的使用,在此文中将详细的讲解累加器和广播变量的使用。

累加器

累加器用来将Executor端变量的信息聚合到Driver端。在Driver程序中定义的变量,在Executor端的每个Task都会得到这个变量的一个新的副本,每个Task更新这些副本的值以后,穿回Driver端进行merge,得到最终的值。

累加器的实现原理

image
累加器变量会将计算结果回传给Driver做merge操作,而普通变量不会,导致在Driver中打印输出的统计结果是错误的。

  • 普通变量做RDD元素的统计:

sensor.txt文件中有19条记录

package com.hjt.yxh.hw.transmate

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

object AccTest {

  def main(args: Array[String]): Unit = {
    val conf:SparkConf = new SparkConf()

    conf.setMaster("local").setAppName("AccTest")

    val sc:SparkContext = new SparkContext(conf)

    var accCount:Long = 0

    val input = "D:\\javaworkspace\\BigData\\Spark\\SparkApp\\src\\main\\resources\\sensor.txt"

    val sensorRdd:RDD[SensorReading] = sc.textFile(input,2)
      .filter(_.nonEmpty)
      .map(data=>{
        val arrs = data.split(",")
        SensorReading(arrs(0),arrs(1).toLong,arrs(2).toDouble)
      })

    sensorRdd.foreach(data=>{
      accCount+=1
      println(accCount)
    })
    sensorRdd.count()
    println("===================="+accCount)
    sc.stop()
  }
}

执行上述代码,我们可以看到foreach分成两个task在执行(因为我们的RDD设置了2个partition),所以foreach会分别输出19和110,最终输出的accCount结果是0.

  • 累加器变量代码
import org.apache.spark.rdd.RDD
import org.apache.spark.util.{LongAccumulator}
import org.apache.spark.{SparkConf, SparkContext}


object AccApp {
  def main(args: Array[String]): Unit = {
    val conf:SparkConf = new SparkConf()

    conf.setMaster("local").setAppName("AccTest")

    val sc:SparkContext = new SparkContext(conf)
    val longAcc:LongAccumulator = sc.longAccumulator("longAcc")

    val input = "D:\\javaworkspace\\BigData\\Spark\\SparkApp\\src\\main\\resources\\sensor.txt"

    val sensorRdd:RDD[SensorReading] = sc.textFile(input,2)
        .filter(_.nonEmpty)
        .map(data=>{
          val arrs = data.split(",")
          SensorReading(arrs(0),arrs(1).toLong,arrs(2).toDouble)
        })

    sensorRdd.foreach(data=>{
      longAcc.add(1)
      println("--------------------"+longAcc.value)
    })
    sensorRdd.count()
    println("===================="+longAcc.value)
    sc.stop()
  }
}

执行上述代码:我们同样的,在foreach过程也是分为2个task输出,longAcc的值分别输出是19和110,最终输出的结果是19.

  • 累加器建议放到行动算子中操作,不要放到转换算子中操作,否则可能因为有多个行动算子触发多次计算导致结果错误。
import org.apache.spark.rdd.RDD
import org.apache.spark.util.{LongAccumulator}
import org.apache.spark.{SparkConf, SparkContext}


object AccApp {
  def main(args: Array[String]): Unit = {
    val conf:SparkConf = new SparkConf()

    conf.setMaster("local").setAppName("AccTest")

    val sc:SparkContext = new SparkContext(conf)
    val longAcc:LongAccumulator = sc.longAccumulator("longAcc")

    val input = "D:\\javaworkspace\\BigData\\Spark\\SparkApp\\src\\main\\resources\\sensor.txt"

    val sensorRdd:RDD[SensorReading] = sc.textFile(input,2)
        .filter(_.nonEmpty)
        .map(data=>{
          val arrs = data.split(",")
          SensorReading(arrs(0),arrs(1).toLong,arrs(2).toDouble)
          longAcc.add(1)
        })

    sensorRdd.foreach(data=>{
      println("--------------------"+longAcc.value)
    })
    sensorRdd.count()
    println("===================="+longAcc.value)
    sc.stop()
  }
}

上面代码中累加器放置在了map算子中,由于map是懒加载算子,后面foreach和count两个行动算子,会触发map执行2次,从而导致累加器被多执行了一次,导致结果错误。

系统提供的累加器

  • LongAccumulator长整型的累加器
val longAcc:LongAccumulator = sc.longAccumulator("longAcc")
longAcc.add(1)
val value:Long = longAcc.value
  • DoubleAccumulator Double类型的累加器
val doubleAcc:DoubleAccumulator = sc.doubleAccumulator("doubleAcc")
doubleAcc.add(1.0)
val value:Double = doubleAcc.value
  • CollectionAccumulator collection类型的累加器
val collectionAccumulator:CollectionAccumulator[String] = sc.collectionAccumulator("collectionAccumulator")
collectionAccumulator.add("name1")
val value: util.List[String] = collectionAccumulator.value

常用方法:

  • accumulator.value 获取累加器的值
  • accumulator.isZero 判断累加器是否是初始状态
  • accumulator.copy() 拷贝累加器
  • accumulator.add() 累加器累加值
  • accumulator.merge(accumulator2) 合并累加器的值
  • accumulator.reset() 重置累加器为初始状态

自定义累加器

比如我们需要统计ID相同的温度传感器的数据条数,输出MAP[String,Count]类型的这类结果,使用系统提供的累加器就实现不了,这时候就需要自定义累加器来实现了。

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

case class SensorReading(id:String,timestamp:BigInt,temperature:Double)

class SensorCountAccumulator extends AccumulatorV2[SensorReading,mutable.Map[String,Long]]{
  var map:mutable.Map[String,Long] = mutable.Map()
  /*
  * 判断累加器是否是初始状态
  * */
  override def isZero: Boolean = {
    map.isEmpty
  }

  /*
  *
  * 拷贝累加器
  * */
  override def copy(): AccumulatorV2[SensorReading, mutable.Map[String, Long]] = {
    val obj = new SensorCountAccumulator
    //深拷贝
    obj.map = map.clone()
    obj
  }

  /*
  * 重置累加器
  * */
  override def reset(): Unit = {
    map.clear()
  }

  /*
  *
  * 累加运算
  * */
  override def add(v: SensorReading): Unit = {
    val sensorId:String = v.id
    var count = map.getOrElse(sensorId,0L)
    map(sensorId) = count + 1
  }

  /*
  * 结果合并
  * */
  override def merge(other: AccumulatorV2[SensorReading, mutable.Map[String, Long]]): Unit = {
    var map1 = map
    val map2 = other.value
    //合并两个map
    map = map1.foldLeft(map2)(
      (tempMap,kv) => {
        tempMap(kv._1)= tempMap.getOrElse(kv._1,0L) + kv._2
        tempMap
    })
  }

  /*
  * 累加器的結果
  * */
  override def value: mutable.Map[String, Long] = {
    map
  }
}

object AccSensorCountApp {

  def main(args: Array[String]): Unit = {
    val conf:SparkConf = new SparkConf()

    conf.setMaster("local").setAppName("AccTest")

    val sc:SparkContext = new SparkContext(conf)

    val acculator:SensorCountAccumulator = new SensorCountAccumulator
    sc.register(acculator,"sensorCountAccumulator")
    val input = "D:\\javaworkspace\\BigData\\Spark\\SparkApp\\src\\main\\resources\\sensor.txt"

    val sensorRdd:RDD[SensorReading] = sc.textFile(input,2)
      .filter(_.nonEmpty)
      .map(data=>{
        val arrs = data.split(",")
        SensorReading(arrs(0),arrs(1).toLong,arrs(2).toDouble)
      })

    sensorRdd.foreach(data=>{
      acculator.add(data)
    })
    sensorRdd.count()

    println(acculator.value)

    val acc2 = acculator.copyAndReset()
    println(acc2.value)
    println(acculator.value)

    sc.stop()
  }
}

广播变量

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

广播变量的特性

image

  • 普通变量会在每个task中都创建一个副本,而广播变量则是共享的,因此广播变量的性能更高(尤其是共享的值很大时)
  • 广播变量是只读的,值不能被改变

广播变量的使用

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

object BroadCastApp {

  def main(args: Array[String]): Unit = {
    val conf:SparkConf = new SparkConf()

    conf.setMaster("local").setAppName("AccTest")

    val sc:SparkContext = new SparkContext(conf)


    val input = "D:\\javaworkspace\\BigData\\Spark\\SparkApp\\src\\main\\resources\\sensor.txt"

    val sensorRdd:RDD[SensorReading] = sc.textFile(input,2)
      .filter(_.nonEmpty)
      .map(data=>{
        val arrs = data.split(",")
        SensorReading(arrs(0),arrs(1).toLong,arrs(2).toDouble)
      })
    
    // 声明broadcast变量
    val name:String = "zhangsan"
    val broadCast = sc.broadcast(name)

    sensorRdd.foreach(data=>{
      //获取broadcast变量的值
      if(broadCast.value == "lisi")
        println(data)
    })
    sc.stop()
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值