累加器
累加器:分布式共享只写变量。(Executor和Executor之间不能读数据)
累加器用来把Executor端变量信息聚合到Driver端。在Driver中定义的一个变量,在Executor端的每个task都会得到这个变量的一份新的副本,每个task更新这些副本的值后,传回Driver端进行合并计算。
系统累加器
1)累加器使用
(1)累加器定义(SparkContext.accumulator(initialValue)方法)
val sum: LongAccumulator = sc.longAccumulator(“sum”)
(2)累加器添加数据(累加器.add方法)
sum.add(count)
(3)累加器获取数据(累加器.value)
sum.value
2)创建包名:com.atguigu.accumulator
需求
val dataRDD: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)))
//需求:统计a出现的所有次数 ("a",10)
3)代码实现
package com.atguigu.accumulator
import org.apache.spark.rdd.RDD
import org.apache.spark.util.LongAccumulator
import org.apache.spark.{SparkConf, SparkContext}
/**
* @author dxy
* @date 2021/2/25 14:18
*/
object accumulator01_system {
def main(args: Array[String]): Unit = {
//TODO 1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//TODO 2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
val dataRDD: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)))
//需求:统计a出现的所有次数 ("a",10)
//1.普通算子实现,底层会走shfffle,效率低
val rdd: RDD[(String, Int)] = dataRDD.reduceByKey(_+_)
//rdd.collect().foreach(println)
//2.普通变量实现
//普通变量没法实现需求,因为普通变量只能从driver端传到executor端,不能从executor端返回到driver端
/* var sum:Int=0
dataRDD.foreach{
case (a,cnt)=>{
sum +=cnt
println("sum = "+ sum)
}
}
println("a",sum)*/
//3.累加器实现
//3.1 创建累加器
val accSum: LongAccumulator = sc.longAccumulator("sum")
//3.2使用累加器
dataRDD.foreach{
case (a,cnt)=>{
accSum.add(cnt)
//3.4 在excutor端读取的累加器的值,不是最终的值,不对 因此我们累加器为分布式共享只写变量
println("accSum = "+accSum.value)
}
}
//3.3获取累加器的值
println(accSum.value)
//TODO 3.关闭连接
sc.stop()
}
}
结果:
accSum = 4
accSum = 3
accSum = 2
accSum = 1
10
注意:Executor端的任务不能读取累加器的值(例如:在Executor端调用sum.value,获取的值不是累加器最终的值)。因此我们说,累加器是一个分布式共享只写变量。
3)累加器要放在行动算子中
因为转换算子执行的次数取决于job的数量,如果一个spark应用有多个行动算子,那么转换算子中的累加器可能会发生不止一次更新,导致结果错误。所以,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在foreach()这样的行动算子中。
对于在行动算子中使用的累加器,Spark只会把每个Job对各累加器的修改应用一次。
object accumulator02_updateCount {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
val sc = new SparkContext(conf)
val dataRDD: RDD[(String, Int)] = sc.makeRDD(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)))
//需求:统计a出现的所有次数 ("a",10)
//累加器实现
//1 声明累加器
val accSum: LongAccumulator = sc.longAccumulator("sum")
val mapRDD: RDD[Unit] = dataRDD.map {
case (a, count) => {
//2 使用累加器累加 累加器.add()
accSum.add(count)
// 4 不要在executor端获取累加器的值,因为不准确 因此我们说累加器叫分布式共享只写变量
//println("sum = " + accSum.value)
}
}
//调用两次行动算子,map执行两次,导致最终累加器的值翻倍
mapRDD.collect()
mapRDD.collect()
/**
* 结论:使用累加器最好要在行动算子中使用,因为行动算子只会执行一次,而转换算子的执行次数不确定!
*/
//2 获取累加器的值 累加器.value
println(("a",accSum.value))
sc.stop()
}
}
运行结果:
accSum = 2
accSum = 3
accSum = 1
accSum = 4
accSum = 2
accSum = 3
accSum = 4
accSum = 1
20
总结
1.普通变量在driver初始化=>executor端,不能从executor端返回driver端
2.spark有一个自带累加器sc.longAccumulator可以实现excutor端=>driver
3.spark自带累加器使用步骤:
第一步:创建累加器
val accSum: LongAccumulator = sc.longAccumulator("sum")
sum名字而已:最好见名知意
第二步:使用累加器
dataRDD.foreach{
case (a,cnt)=>{
accSum.add(cnt)
//3.4 在excutor端读取的累加器的值,不是最终的值,不对 因此我们累加器为分布式共享只写变量
println("accSum = "+accSum.value)
}
}
第三步:获取累加器的值
println(accsum.value)
4.使用累加器最好要在行动算子中使用,因为行动算子只会执行一次,而转换算子的执行次数不确定!
因为行动算子执行RDD从头开始,没加缓存的话