文章目录
一 自定义累加器实现奇数和偶数分别相加
需求描述:给定一个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端因此变量的拉去,占用的内存空间