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
}
運行結果: