在默認情況下,當Spark在集羣的多個不同節點的多個任務上並行運行一個函數時,它會把函數中涉及到的每個變量,在每個任務上都生成一個副本。但是,有時候需要在多個任務之間共享變量,或者在任務(Task)和任務控制節點(Driver Program)之間共享變量。
爲了滿足這種需求,Spark提供了兩種類型的變量:
- 累加器accumulators:累加器支持在所有不同節點之間進行累加計算(比如計數或者求和)
- 廣播變量broadcast variables:廣播變量用來把變量在所有節點的內存之間進行共享,在每個機器上緩存一個只讀的變量,而不是爲機器上的每個任務都生成一個副本。
一、累加器
不使用累加器
使用累加器
通常在向 Spark 傳遞函數時,比如使用 map() 函數或者用 filter() 傳條件時,可以使用驅動器程序中定義的變量,但是集羣中運行的每個任務都會得到這些變量的一份新的副本,更新這些副本的值也不會影響驅動器中的對應變量。這時使用累加器就可以實現我們想要的效果。
val xx: Accumulator[Int] = sc.accumulator(0)
代碼演示
import org.apache.spark.rdd.RDD import org.apache.spark.{Accumulator, SparkConf, SparkContext} object AccumulatorTest { def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]") val sc: SparkContext = new SparkContext(conf) sc.setLogLevel("WARN") //使用scala集合完成累加 var counter1: Int = 0; var data = Seq(1,2,3) data.foreach(x => counter1 += x ) println(counter1)//6 println("+++++++++++++++++++++++++") //使用RDD進行累加 var counter2: Int = 0; val dataRDD: RDD[Int] = sc.parallelize(data) //分佈式集合的[1,2,3] dataRDD.foreach(x => counter2 += x) println(counter2)//0 //注意:上面的RDD操作運行結果是0 //因爲foreach中的函數是傳遞給Worker中的Executor執行,用到了counter2變量 //而counter2變量在Driver端定義的,在傳遞給Executor的時候,各個Executor都有了一份counter2 //最後各個Executor將各自個x加到自己的counter2上面了,和Driver端的counter2沒有關係 //那這個問題得解決啊!不能因爲使用了Spark連累加都做不了了啊! //如果解決?---使用累加器 val counter3: Accumulator[Int] = sc.accumulator(0) dataRDD.foreach(x => counter3 += x) println(counter3)//6 } } |
二、廣播變量
不使用廣播變量
使用廣播變量
代碼演示
import org.apache.spark.broadcast.Broadcast import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} object BroadcastVariablesTest { def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]") val sc: SparkContext = new SparkContext(conf) sc.setLogLevel("WARN") //不使用廣播變量 val kvFruit: RDD[(Int, String)] = sc.parallelize(List((1,"apple"),(2,"orange"),(3,"banana"),(4,"grape"))) val fruitMap: collection.Map[Int, String] =kvFruit.collectAsMap //scala.collection.Map[Int,String] = Map(2 -> orange, 4 -> grape, 1 -> apple, 3 -> banana) val fruitIds: RDD[Int] = sc.parallelize(List(2,4,1,3)) //根據水果編號取水果名稱 val fruitNames: RDD[String] = fruitIds.map(x=>fruitMap(x)) fruitNames.foreach(println) //注意:以上代碼看似一點問題沒有,但是考慮到數據量如果較大,且Task數較多, //那麼會導致,被各個Task共用到的fruitMap會被多次傳輸 //應該要減少fruitMap的傳輸,一臺機器上一個,被該臺機器中的Task共用即可 //如何做到?---使用廣播變量 println("=====================") val BroadcastFruitMap: Broadcast[collection.Map[Int, String]] = sc.broadcast(fruitMap) val fruitNames2: RDD[String] = fruitIds.map(x=>BroadcastFruitMap.value(x)) fruitNames2.foreach(println) } } |