1、广播变量
广播变量的定义:
广播变量可以让程序高效地向所有工作节点发送一个较大的只读值,以供一个或多个spark操作使用,在机器学习中非常有用。广播变量是类型为spark.broadcast.Broadcast[T]的一个对象,其中存放着类型为T的值。它由运行SparkContext的驱动程序创建后发送给会参与计算的节点,非驱动程序所在节点(即工作节点)访问改变量的方法是调用该变量的value方法,这个值只会被发送到各节点一次,作为只读值处理。
广播变量的使用场景:
如果我们要在分布式计算里面分发大对象,例如字典、集合、黑白名单等,这个都会由Driver端进行分发,一般来讲,如果这个变量不是广播变量,那么每个task就会分发一份,这在task数目十分多的情况下Driver的带宽会成为系统的瓶颈,而且会大量消耗task服务器上的资源,如果将这个变量声明为广播变量,那么这时每个executor拥有一份,这个executor启动的task会共享这个变量,节省了通信的成本和服务器的资源。
广播变量图解:
不使用广播变量,每一个Task发送一个
使用广播变量的情况,每一个Executor发送一个
广播变量的使用:
val conf = new SparkConf().setAppName("WordCount").setMaster("local")
val sc = new SparkContext(conf)
val pairRdd = sc.parallelize(List((1,1), (5,10), (5,9), (2,4), (3,5), (3,6),(4,7), (4,8),(2,3), (1,2)),4)
//将字段收集到Driver端,并广播发送到Executor
val allIpAndProvince: Array[(Int, Int)] = pairRdd.reduceByKey(_+_).collect()
//广播发送 返回引用
val broadCast: Broadcast[Array[(Int, Int)]] = sc.broadcast(allIpAndProvince)
val value24: RDD[(Int, Int)] = pairRdd.map(x => {
//使用广播变量 进行优化
val value: Array[(Int, Int)] = broadCast.value
val map: Map[Int, Int] = value.toMap
(x._1, x._2 + map.get(x._1).get)
})
注意:
1、不能将RDD作为广播变量广播出去,RDD是不存储数据的。可以将RDD的结果广播出去。
2、广播变量只能在Driver端定义,不能在Executor端定义。
2、累加器
累加器的定义:
spark累加器就是定义在驱动程序的一个变量,但是在集群中的每一个任务都会有一份新的副本。在各个任务中更新副本的值都不会对驱动程序中的值产生影响,只有到最后所有的任务都计算完成后才会合并每一个任务的副本到驱动程序。
累加器的使用场景:
异常监控,调试,记录符合某特性的数据的数目等。如果一个变量不被声明为一个累加器,那么它将在被改变时不会再driver端进行全局汇总,即在分布式运行时每个task运行的只是原始变量的一个副本,并不能改变原始变量的值,但是当这个变量被声明为累加器后,该变量就会有分布式计数的功能。
累加器图解:
累加器的使用:
val rdd: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("b", 2), ("a", 3), ("b", 4)))
// 声明累加器
val sumAcc: LongAccumulator = sc.longAccumulator("sumAcc")
rdd.foreach {
case (word, count) => {
// 使用累加器
sumAcc.add(count)
}
}
// 累加器的toString方法
//println(sumAcc)
//取出累加器中的值
println(sumAcc.value)
自定义累加器:
object WordCount {
def main(args: Array[String]): Unit = {
val sc = SparkSession.builder().appName("WordCount").master("local").getOrCreate().sparkContext
val rdd: RDD[String] = sc.makeRDD(List("Hello", "Hello", "Hello", "Hello", "Spark", "Spark","Hive","Hive"), 2)
//创建累加器
val acc: myaccumulator = new myaccumulator()
//注册累加器
sc.register(acc,"wordcount")
//使用累加器
rdd.foreach{
word => {
acc.add(word)
}
}
//获取累加器的累加结果
println(acc.value)
sc.stop()
}
}
class myaccumulator extends AccumulatorV2[String,mutable.Map[String,Long]] {
// 定义输出数据集合,一个可变的Map
var map: mutable.Map[String, Long] = mutable.Map[String, Long]()
// 是否为初始化状态,如果集合数据为空,即为初始化状态
override def isZero: Boolean = map.isEmpty
// 复制累加器
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = new myaccumulator()
// 重置累加器
override def reset(): Unit = map.clear()
// 增加数据
override def add(v: String): Unit = {
if (v.startsWith("H")) {
map(v) = map.getOrElse(v, 0L) + 1
}
}
// 合并累加器
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
val map2: mutable.Map[String, Long] = other.value
map2.foreach {
case (word, count) => {
map(word) = map.getOrElse(word, 0L) + count
}
}
}
//累加器的值
override def value: mutable.Map[String, Long] = map
}
输出:Map(Hello -> 4, Hive -> 2)
注意:
1、累加器在Driver端定义赋初始值,累加器只能在Driver端读取最后的值,在Excutor端更新。
2、累加器不是一个调优的操作,因为如果不这样做,结果是错的。