業務場景
採集工具採集的文件中以日期爲標識,但是該文件中卻可能包含了多天的數據,所以在統計當天推送文件記錄總數時,需要記錄當天的文件錄入了詳單表的哪些分區。後續業務開發利用這種映射關係,就可以縮小詳單表分區範圍,儘快的統計出當天入庫記錄數。(這僅僅是該業務統計的一個指標,利用此處的累加器,還可以高效率的統計過濾,髒數據條數)
解決方案
方案一、再觸發一次action
採即對落地的dataframe提取出分區時間列和文件全路徑列,再進行去重,最終調用一次collect,即可獲取上述的映射關係。
方案二、利用spark累加器
採此處定義的累加器數據結構爲【String,String】其中:key爲文件全路徑,value爲該文件的記錄數。自定義累加器,主要是重寫add、merge方法,累加器的add方法是針對於一個task,merge方法針對的是各個task運算結果的累加。
累加器代碼樣例如下:
class FilterAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
private val _result = mutable.Map[String,Long]();
override def isZero: Boolean = true
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
val copy = new FilterAccumulator
copy.merge(this)
copy
}
override def reset(): Unit = _result.clear()
override def add(v: String): Unit = {
_result.synchronized {
var dd = _result.get(v) match {
case Some(x) => x
case _ =>
val tmp = 0L
tmp
}
dd += 1
_result.put(v,dd)
}
}
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = other match{
case o: FilterAccumulator =>
_result.synchronized(
o._result.foreach(x => {
var temp :Long = _result.get(x._1) match {
case Some(y) => y
case _ =>
val tmp = 0L
tmp
}
temp += x._2
_result.put(x._1, temp)
}))
case _ =>
throw new UnsupportedOperationException(
s"Cannot merge ${this.getClass.getName} with ${other.getClass.getName}")
}
override def value: mutable.Map[String, Long] = _result
}
說明:
1、 此處定義key爲文件全路徑,是考慮到task重算時,避免記錄數重複,但是這邊有一個問題——一個文件不止拆成一個任務執行時,累加器中的記錄數會不對。(考慮到現場這種文件很少,如果有這種文件,也可以手動的將他們split成小文件)
累加器總結
1、 累加器放在foreach動作中使用時,計算結果不會有誤;但是放在map等轉換函數中使用時,需要考慮任務重算導致計算結果有誤;
2、 累加器工作時,可以看做每個task都有一個“副本”,在任務執行結束後,就執行merge操作,最終在driver端調用accumulator.value方法時,獲取累加器執行結果。
3、從DAG圖上來看,如果累加器部分代碼的dataset/rdd被多個action觸發,那麼累加器統計結果也會重複,這種情況下,如果不想統計重複,就需要添加cache/persist函數。