一、累加器
定义:累加器用来把 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()
}
}