一、闭包
1、什么是闭包,闭包的本质是什么?
闭包的本质是一个函数,在Scala中函数是一个特殊的类型FunctionX。
闭包就是一个封闭的作用域, 也是一个对象。
如下closure
返回的函数areaFunction
就是一个闭包
def closure(): Int => Double = {
val factor = 3.14
val areaFunction = (r: Int) => {
math.pow(r, 2) * factor
}
areaFunction
}
2、spark如何分发闭包?
如下flatMap
中传入的是另外一个函数, 传入的这个函数就是一个闭包, 这个闭包会被序列化运行在不同的 Executor 中
val value = 3
sc.textFile("dataset/access_log_sample.txt")
.flatMap(item => item + value)
.collect()
问题:如果有10个Task,value就会被分发十份,就会有10个value,但是如果集群中只有2个Executor,无论有多少Task,都会被放在这2个Executor上执行。
解决:使用广播变量
- 在使用广播之前,复制value数和Task数量一致
- 在使用广播之后,复制次数和Executor数量一致。
Spark 对共享变量也提供了两种支持:(1)累加器 (2)广播变量
二、累加器:(解决了共享变量写的问题)
1)特性:
- 累加器能保证在 Spark 任务出现问题被重启的时候不会出现重复计算
- 累加器只有在 Action 执行的时候才会被触发
2)使用方式:
- 只有一个方法:acc.add()
- Driver 端调用
value
来获取数值:acc.value
3)使用场景:
- 需要对共享变量进行修改
- 对同一个rdd需要遍历多次计算多个指标
1、系统自带累加器:
- sc.longAccumulator
- sc.doubleAccumulator
- sc.collectionAccumulator
object AccumulatorDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("AccumulatorDemo").setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val numlist = List(30, 50, 70, 60, 10, 20)
val numrdd = sc.parallelize(numlist, 2)
val acc = sc.longAccumulator
val accresultrdd = numrdd.map(x => {
acc.add(1)
x
})
accresultrdd.collect
println(acc.value)
sc.stop()
}
}
2、自定义int累加器:
/**
* AccumulatorV2[Int, Int]泛型说明
* a. 对什么值进行累加
* b. 累加器最终的值
*/
class CustomIntAccumulator extends AccumulatorV2[Int, Int] {
private var sum = 0
//对缓冲区的值进行判"零"
override def isZero: Boolean = sum == 0
//复制累加器(把当前的累加器复制为一个新的累加器)
override def copy(): AccumulatorV2[Int, Int] = {
val newIntAcc = new CustomIntAccumulator
newIntAcc.sum = sum
newIntAcc
}
//重置累加器(把缓冲区的值重置为"零")
override def reset(): Unit = sum = 0
//真正的累加方法(分区内的累加)
override def add(v: Int): Unit = sum += v
//分区间的合并(把other的sum合并到this的sum中)
override def merge(other: AccumulatorV2[Int, Int]): Unit = other match {
case acc: CustomIntAccumulator => this.sum += acc.sum
case _ => this.sum += 0
}
//返回累加后的最终值
override def value: Int = sum
}
//使用自定义累加器
object MyIntAccDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("MyIntAccDemo").setMaster("local[2]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val numlist = List(30, 50, 70, 60, 10, 20)
val numrdd = sc.parallelize(numlist, 2)
//先注册自定义累加器
val customIntAccumulator = new CustomIntAccumulator
sc.register(customIntAccumulator,"intAcc")
val resultrdd = numrdd.map(x =>{
customIntAccumulator.add(1)
x
})
resultrdd.collect
println(customIntAccumulator.value)
sc.stop()
}
}
3、自定义map累加器
/**
* 累加器的值同时包含sum,count,avg
* Map("sum" -> 1000,"count" -> 10,"avg" -> 100)
*/
class CustomMapAccumulator extends AccumulatorV2[Double, Map[String, Any]] {
private var map = Map[String, Any]()
override def isZero: Boolean = map.isEmpty
override def copy(): AccumulatorV2[Double, Map[String, Any]] = {
val newMapAcc = new CustomMapAccumulator
newMapAcc.map = map
newMapAcc
}
//不可变集合, 直接赋值一个空的集合
override def reset(): Unit = Map[String, Any]()
// 对sum和count进行累加. avg在最后value()函数中进行计算
override def add(v: Double): Unit = {
map += "sum" -> (map.getOrElse("sum", 0D).asInstanceOf[Double] + v)
map += "count" -> (map.getOrElse("count", 0L).asInstanceOf[Long] + 1L)
}
// 合并两个map
override def merge(other: AccumulatorV2[Double, Map[String, Any]]): Unit = {
other match {
case mapAcc: CustomMapAccumulator =>
map +=
"sum" -> (map.getOrElse("sum", 0D).asInstanceOf[Double] + mapAcc.map.getOrElse("sum", 0D).asInstanceOf[Double])
map +=
"count" -> (map.getOrElse("count", 0L).asInstanceOf[Long] + mapAcc.map.getOrElse("count", 0L).asInstanceOf[Long])
case _ => throw new UnsupportedOperationException
}
}
override def value: Map[String, Any] = {
map += "avg" -> (map.getOrElse("sum", 0D).asInstanceOf[Double] / map.getOrElse("count", 0L).asInstanceOf[Long])
map
}
}
三、广播变量(解决共享变量读的问题)
1、解决大变量读的问题
广播变量将一个 Read-Only
的变量缓存到集群中每个executor, 而不是传递给每个 Task,executor下的所有task可以共享这个大变量,减少内存占用。
每一个Executor对应一个BlockManager,广播变量存储在BlockManager内存中。
2、广播变量API
id | 唯一标识 |
value | 广播变量的值 |
unpersist | 在 Executor 中异步的删除缓存副本 |
destroy | 销毁所有此广播变量所关联的数据和元数据 |
toString | 字符串表示 |
3、使用广播变量
//数组中存在的元素保留,不存在过滤
object BroadCastDemo {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("BroadCastDemo").setMaster("local[2]")
val sc: SparkContext = new SparkContext(conf)
val bigArr = 1 to 1000 toArray
// 广播变量
val broadcastArr = sc.broadcast(bigArr)
val listnum = List(30, 50000000, 70, 600000, 10, 20)
val listrdd = sc.parallelize(listnum, 4)
val resultrdd = listrdd.filter(x => broadcastArr.value.contains(x))
resultrdd.collect.foreach(println)
Thread.sleep(1000000)
sc.stop()
}
}