Spark累计器和广播变量

一 自定义累加器实现奇数和偶数分别相加

需求描述:给定一个List集合,对list集合中的元素进行一次遍历统计出奇数和偶数的和
实现步骤:

1.1 确定累加器的输入和输出

两种思路:

  • 直接将值传入累加器,在累加器中进行判断
  • 在外层进行判断,将判断后的记过以kv的形式传入累加器(采用此种思路)
  • 返回结果为map

1.2 实现累加系统提供的AccumulatorV2的类

import scala.collection.mutable

class MyAccMap  extends  AccumulatorV2[(String,Long),mutable.Map[String,Long]] {
  var map =new  mutable.HashMap[String,Long]();

  override def isZero: Boolean = map.isEmpty //判断零值
//拷贝当前累加器
  override def copy(): AccumulatorV2[(String, Long), mutable.Map[String, Long]] = {
    val accMap = new MyAccMap
    accMap.map=this.map
    accMap
  }
//清零
  override def reset(): Unit = map.clear()
// 累加逻辑
  override def add(v: (String, Long)): Unit = {
    this.map+=v._1->(this.map.getOrElse(v._1,0L)+v._2) // 先从当前取,取不到默认0 再加上传入的值
  }
// 合并读个Executer累加后的结果
  override def merge(other: AccumulatorV2[(String, Long), mutable.Map[String, Long]]): Unit = {
    // 合并数据
    val accMap = other.asInstanceOf[MyAccMap]
    accMap.map.map(t=>{
      this.map+=t._1->(this.map.getOrElse(t._1,0L)+t._2)
    })
  }
  // 返回累加器的值
  override def value: mutable.Map[String, Long] = map
}

1.3 编写测试类

object Accumlator1 {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4)
    val conf = new SparkConf()
    conf.setAppName("Accumlator1")
    conf.setMaster("local[2]")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(list)
    // 构建累加器
    val acc = new MyAccMap
    // 注册累加器
    sc.register(acc, "accCount")
    // 在行动算子中调用累加器
    rdd.foreach(t => {
      if (t % 2 == 0) {
        acc.add("even_numbers", t)
      } else {
        acc.add("odd_number", t)
      }
      (t, acc.value)
    })
    println("-----------")
    println(acc.value)
    sc.stop()
  }
}

运行结果:

Map(odd -> 4, doubleNumber -> 6)

二 自定义累加器实现统计总数,最大数、最小数平均数

2.1 累加器输入输出分析

总数、最大数、最小数可通过各个Executer执行结果统计比较得出,

平均数需要不能再各个Executer 端计算,否则会导致计算结果不准确。
所以在外层直接将数据传入到累加器中,在累加器中对数据进行计算,
并在返回结果的时候,计算出平均值
输入类型为Long 输出类型为Map

2.2 编写累加器类

package com.gc.spark.day05

import org.apache.spark.util.AccumulatorV2

import scala.collection.mutable

class StatDemoAcc extends AccumulatorV2[Long,mutable.Map[String,Double]]{
  private var accMap: mutable.Map[String, Double] =new  mutable.HashMap[String,Double] ()
 private var _count:Long=0L;

  override def isZero: Boolean = {accMap.isEmpty && _count==0L}

  override def copy(): AccumulatorV2[Long, mutable.Map[String, Double]] = {
    val accDemo: StatDemoAcc = new StatDemoAcc
    accDemo.accMap=this.accMap
    accDemo._count=this._count
    accDemo
  }

  override def reset(): Unit = {
    accMap.clear();
    _count=0L;
  }

  override def add(v: Long): Unit = {
    //计算和
    this.accMap+="sum"->(this.accMap.getOrElse("sum",0D)+v)
    //计算count值
    _count+=1
    //计算最大值
    this.accMap += "max"->this.accMap.getOrElse("max",Double.MinValue).max(v)
    //计算做小值
    this.accMap += "min" ->this.accMap.getOrElse("min",Double.MaxValue).min(v)

  }
// 合并计算结果
  override def merge(other: AccumulatorV2[Long, mutable.Map[String, Double]]): Unit = {
    val demoAcc = other.asInstanceOf[StatDemoAcc]// 强制转换
    this._count+= demoAcc._count // 计算总数
    // 计算总数和
    this.accMap+="sum"->(this.accMap.getOrElse("sum",0D)+demoAcc.accMap.getOrElse("sum",0D))
    // 计算最大值 用当前累加器中的值和 传入的累加器的中的值进行对比
    this.accMap+="max"->this.accMap.getOrElse("max",Double.MinValue).max(demoAcc.accMap.getOrElse("max",Double.MinValue))
    //计算最小值
    this.accMap+="min"->this.accMap.getOrElse("min",Double.MaxValue).min(demoAcc.accMap.getOrElse("min",Double.MaxValue))
  }

  override def value: mutable.Map[String, Double] = {
    // 计算出平均值 返回数据
    accMap+="avg"->accMap.getOrElse("sum",0D)/this._count
    accMap
  }
}

2.3 测试

object StatDemoAccTest {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    val conf = new SparkConf()
    conf.setAppName("StatDemoAccTest")
    conf.setMaster("local[2]")
    val sc = new SparkContext(conf)
    val rdd = sc.makeRDD(list)
    val acc = new StatDemoAcc
    sc.register(acc)
    rdd.foreach(t => {  // rdd.foreach   rdd 的foreach 是行动算子
      acc.add(t)
    })
    println("-----------")
    println(acc.value)
    sc.stop()
  }
}

计算结果:

Map(max -> 10.0, avg -> 5.5, min -> 1.0, sum -> 55.0)

三 广播变量

object BroadParam {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
        conf.setAppName("BroadParam")
        conf.setMaster("local[2]")
        val list = List(1,2,3,4,5,6)
        val sc = new SparkContext(conf)
    // 广播变量 将数据发送到所有的executer端,避免每个task拉取一份数据
        val set = sc.broadcast((1 to 1000).toSet )
        val rdd = sc.makeRDD(list,2)
       val rdd1 = rdd.filter(t => {
         // 通过.value获取广播变量的值
         set.value.contains(t)
    })
    rdd1.collect().foreach(println)
    sc.stop()
  }
}

四 总结

累加器:解决了分布式环境下,写的问题
广播变量:解决了分布式环境下,读的问题
注意点:

  • 累加器应使用在行动算子中,如果在转换算子中使用,可能会在后续出现错误时,根据RDD的血源关系进行从新计算,导致累加器中的数据重复
  • 编写累加器的时候,应该注意线程安全问题,scala默认的集合为不可变集合,默认是线程安全的,但是在进行集合数据的增改时,会生成新的集合,造成资源的浪费,一般使用可变的集合
  • 广播变量,通过变量的只读,保证多Task之间的共同访问
  • 广播变量在共享数据只允许访问的情况下,最大可能的减少了executer端因此变量的拉去,占用的内存空间
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Master_slaves

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

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

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

打赏作者

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

抵扣说明:

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

余额充值