文章目录
- 概述
- 累加器
- 累加器的实现原理
- 系统提供的累加器
- 自定义累加器
- 广播变量
- 广播变量的特性
- 广播变量的使用
概述
Spark 核心的三大数据结构是RDD、累加器、和广播变量。前面的文章中已经详细的讲解了RDD的使用,在此文中将详细的讲解累加器和广播变量的使用。
累加器
累加器用来将Executor端变量的信息聚合到Driver端。在Driver程序中定义的变量,在Executor端的每个Task都会得到这个变量的一个新的副本,每个Task更新这些副本的值以后,穿回Driver端进行merge,得到最终的值。
累加器的实现原理
累加器变量会将计算结果回传给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 会为每个任务分别发送。
广播变量的特性
- 普通变量会在每个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()
}
}