spark累加器
1.系统累加器
object accumulator01_system {
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)
//普通算子实现 reduceByKey 代码会走shuffle 效率低
//val rdd1: RDD[(String, Int)] = dataRDD.reduceByKey(_ + _)
//普通变量无法实现
//结论:普通变量只能从driver端发给executor端,在executor计算完以后,结果不会返回给driver端
/*
var sum = 0
dataRDD.foreach{
case (a,count) => {
sum += count
println("sum = " + sum)
}
}
println(("a",sum))
*/
//累加器实现
//1 声明累加器
val accSum: LongAccumulator = sc.longAccumulator("sum")
dataRDD.foreach{
case (a,count) => {
//2 使用累加器累加 累加器.add()
accSum.add(count)
// 4 不要在executor端获取累加器的值,因为不准确
//因此我们说累加器叫分布式共享只写变量
//println("sum = " + accSum.value)
}
}
//3 获取累加器的值 累加器.value
println(("a",accSum.value))
sc.stop()
注意:Executor端的任务不能读取累加器的值(例如:在Executor端调用sum.value,获取的值不是累加器最终的值)。因此我们说,累加器是一个分布式共享只写变量。
1.1累加器要放在行动算子中
因为转换算子执行的次数取决于job的数量,如果一个spark应用有多个行动算子,
那么转换算子中的累加器可能会发生不止一次更新,导致结果错误。所以,如果想要
一个无论在失败还是重复计算时都绝对可靠的累加器,我们必须把它放在foreach()
这样的行动算子中。
2.自定义累加器
自定义累加器类型的功能在1.X版本中就已经提供了,但是使用起来比较麻烦,在2.0版本后,累加器的易用性有了较大的改进,而且官方还提供了一个新的抽象类:AccumulatorV2来提供更加友好的自定义类型累加器的实现方式。
1)自定义累加器步骤
(1)继承AccumulatorV2,设定输入、输出泛型
(2)重写6个抽象方法
(3)使用自定义累加器需要注册::sc.register(累加器,"累加器名字")
2)需求:自定义累加器,统计RDD中首字母为“H”的单词以及出现的次数。
List("Hello", "Hello", "Hello", "Hello", "Hello", "Spark", "Spark")
代码:
object accumulator03_define {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3. 创建RDD
val rdd: RDD[String] = sc.makeRDD(List("Hello", "Hello", "Hello", "Hello", "Spark", "Spark"), 2)
//3.1 创建累加器
val acc: MyAccumulator = new MyAccumulator()
//3.2 注册累加器
sc.register(acc,"wordcount")
//3.3 使用累加器
rdd.foreach(
word =>{
acc.add(word)
}
)
//3.4 获取累加器的累加结果
println(acc.value)
//4.关闭连接
sc.stop()
}
}
// 自定义累加器
// 1.继承AccumulatorV2,设定输入、输出泛型
// 2.重写方法
class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Long]] {
// 0.定义输出数据集合,一个可变的Map
var map = mutable.Map[String, Long]()
// 1.是否为初始化状态,如果集合数据为空,即为初始化状态
override def isZero: Boolean = map.isEmpty
// 2.复制累加器
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
new MyAccumulator()
}
// 3.重置累加器
override def reset(): Unit = map.clear()
// 4.增加数据
override def add(v: String): Unit = {
// 业务逻辑
if (v.startsWith("H")) {
map(v) = map.getOrElse(v, 0L) + 1L
}
}
// 5.合并累加器
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
other.value.foreach{
case (word, count) =>{
map(word) = map.getOrElse(word, 0L) + count
}
}
}
// 6.累加器的值,其实就是累加器的返回结果
override def value: mutable.Map[String, Long] = map
}