Spark共享變量(廣播變量、累加器)

Spark兩種共享變量:廣播變量(broadcast variable)與累加器(accumulator)

累加器用來對信息進行聚合,而廣播變量用來高效分發較大的對象。

共享變量出現的原因:

通常在向 Spark 傳遞函數時,比如使用 map() 函數或者用 filter() 傳條件時,可以使用驅動器程序中定義的變量,但是集羣中運行的每個任務都會得到這些變量的一份新的副本,更新這些副本的值也不會影響驅動器中的對應變量。Spark 的兩個共享變量,累加器與廣播變量,分別爲結果聚合與廣播這兩種常見的通信模式突破了這一限制。

 廣播變量的引入:

Spark 會自動把閉包中所有引用到的變量發送到工作節點上。雖然這很方便,但也很低效。原因有二:首先,默認的任務發射機制是專門爲小任務進行優化的;其次,事實上你可能會在多個並行操作中使用同一個變量,但是 Spark 會爲每個操作分別發送。

用一段代碼來更直觀的解釋:

list是在driver端創建的,但是因爲需要在excutor端使用,所以driver會把list以task的形式發送到excutor端,如果有很多個task,就會有很多給excutor端攜帶很多個list,如果這個list非常大的時候,就可能會造成內存溢出(如下圖所示)。這個時候就引出了廣播變量。

使用廣播變量後:

使用廣播變量的過程很簡單:

(1) 通過對一個類型 T 的對象調用 SparkContext.broadcast 創建出一個 Broadcast[T] 對象。任何可序列化的類型都可以這麼實現。

(2) 通過 value 屬性訪問該對象的值(在 Java 中爲 value() 方法)。

(3) 變量只會被髮到各個節點一次,應作爲只讀值處理(修改這個值不會影響到別的節點)。
 

案例如下:

object BroadcastTest {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local").setAppName("broadcast")

    val sc = new SparkContext(conf)

    val list = List("hello java")

    val broadcast = sc.broadcast(list)

    val linesRDD = sc.textFile("./word")

    linesRDD.filter(line => {

      broadcast.value.contains(line)

}).foreach(println)

 sc.stop()

  }

}

注意事項:

能不能將一個RDD使用廣播變量廣播出去?

不能,因爲RDD是不存儲數據的。可以將RDD的結果廣播出去。

廣播變量只能在Driver端定義,不能在Executor端定義。

在Driver端可以修改廣播變量的值,在Executor端無法修改廣播變量的值

我們發現打印的結果爲:

依然是driver和excutor端的數據不能共享的問題。excutor端修改了變量,根本不會讓driver端跟着修改,這個就是累加器出現的原因。

累加器的作用:

提供了將工作節點中的值聚合到驅動器程序中的簡單語法。(如下圖)

常用場景:

調試時對作業執行過程中的事件進行計數。

累加器的用法如下所示:

(1)通過在driver中調用 SparkContext.accumulator(initialValue) 方法,創建出存有初始值的累加器。返回值爲 org.apache.spark.Accumulator[T] 對象,其中 T 是初始值initialValue 的類型。

(2)Spark閉包(函數序列化)裏的excutor代碼可以使用累加器的 += 方法(在Java中是 add )增加累加器的值。

(3)driver程序可以調用累加器的 value 屬性(在 Java 中使用 value() 或 setValue() )來訪問累加器的值。

案例如下:

object AccumulatorTest {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local").setAppName("accumulator")

    val sc = new SparkContext(conf)

    val accumulator = sc.accumulator(0); //創建accumulator並初始化爲0

    val linesRDD = sc.textFile("./word")

    val result = linesRDD.map(s => {

      accumulator.add(1) //有一條數據就增加1

      s

    })

    result.collect();

    println("words lines is :" + accumulator.value)

    sc.stop()

  }

}

輸出結果:

注意事項

累加器在Driver端定義賦初始值,累加器只能在Driver端讀取,在Excutor端更新(如下圖)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章