spark中有三大数据结构,它们分别是RDD(弹性分布式数据集)、累加器(分布式只写变量)和广播变量(分布式只读变量)。spark累加器就是定义在驱动程序的一个变量,但是在集群中的每一个任务都会有一份新的副本。在各个任务中更新副本的值都不会对驱动程序中的值产生影响,只有到最后所有的任务都计算完成后才会合并每一个任务的副本到驱动程序。累加器的提示如下:
spark有系统累加器,同时也支持自定义累加器,下面来通过案例来介绍这两种累加器:
- 系统累加器
现在又如下需求:通过两个分区计算出List(1,2,3,4,5,6,7,8)的每个元素之和,虽然不用累加器也能完成,但是我们要熟悉累加器,所以下面使用累加器来编写程序:
使用累加器的基本流程就是先创建累加器,然后就可以使用了。
object AccumulatorTest {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("AccumulatorTest")
val sc = new SparkContext(conf)
val dataRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7, 8))
//定义累加器,类型为Long类型
val longAccumulator: LongAccumulator = sc.longAccumulator
//遍历RDD的每个元素
dataRDD.foreach(x => {
//执行累加器的+功能
longAccumulator.add(x)
})
println(longAccumulator.value)
}
}
- 自定义累加器
如果我们要自定义累加器,肯定需要继承某个特定的类,不然随便创建一个类的话,系统是不会知道你这个类就是累加器的。那如果我们不知道继承哪个类的情况下,我们又该如何去找到这个父类呢?相信已经有读者想到了,我们上面已经使用过LongAccumulator这个累加器,那我们可以追根溯源,查看它的源码来找到这个类:
最终发现LongAccumulator继承的是AccumulatorV2这个类,所以我们自定义的累加器也可以继承这个类。那么问题来了,它的两个泛型又是什么意思呢?通过查看源码可知:
一个是输入,另一个是输出。那么又是哪里的输入输出呢?其实输入就是在Driver传给Executor的初始值,输出就是Executor回传给Driver的值。所以下面通过案例来详细说明:
需求:过滤掉List(“hadoop”, “hive”, “hbase”, “spark”, “scala”)没有含有每个字母的单词,至于是哪个字母,可以到创建对象的时候传进去。
通过继承了AccumulatorV2之后发现要实现的方法如下,具体哪个方法有什么作用我会在代码的注释说明:
object MyAccumulatorTest {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("MyAccumulatorTest").setMaster("local")
val sc = new SparkContext(conf)
val dataRDD: RDD[String] = sc.makeRDD(List("hadoop", "hbase", "hive", "spark", "scala"))
//声明累加器
val myAccumulator = new MyAccumulator("h")
//向Driver注册累加器
sc.register(myAccumulator)
dataRDD.foreach(word => {
myAccumulator.add(word)
})
//打印累加器的值
println(myAccumulator.value)
sc.stop()
}
}
//自定义累加器,继承AccumulatorV2,并实现抽象方法
class MyAccumulator(s: String) extends AccumulatorV2[String, util.ArrayList[String]]{
//自定义返回的ArrayList
private val list = new util.ArrayList[String]
//当前累加器是否为初始状态,如果list是空的则为初始状态
override def isZero: Boolean = list.isEmpty
//复制累加器对象
override def copy(): AccumulatorV2[String, util.ArrayList[String]] = {
new MyAccumulator(s)
}
//重置累加器
override def reset(): Unit = {
list.clear()
}
//为累加器添加元素
override def add(v: String): Unit = {
//如果变量的值包含s,则添加进list
if(v.contains(s)){
list.add(v)
}
}
//合并累加器,因为累加器的副本要发给集群的每个Executor,所以最后要在Driver合并
override def merge(other: AccumulatorV2[String, util.ArrayList[String]]): Unit = {
list.addAll(other.value)
}
//返回累加器的值,就是list
override def value: util.ArrayList[String] = list
}
运行结果: