從0開始學習spark(6)Spark共享變量之累加器和廣播變量的使用!!!

在這裏插入圖片描述
每日福利來一個。


沒有看前面的同學可以回顧一下:

5.RDD常用算子用法訓練(附習題答案)(aggregateByKey與combineByKey)!!!
4.Spark Rdd常用算子和RDD必備知識
3.spark core 核心知識
2.spark 之 wordcount入門
1.spark 入門講解

1. spark共享變量(Shared Variables)

1.1 簡介:

在 Spark 程序中,當一個傳遞給 Spark 操作(例如 map 和 reduce)的函數在遠程節點上面運行 時,Spark 操作實際上操作的是這個函數所用變量的一個獨立副本。這些變量會被複制到每臺機器上,並且這些變量在遠程機器上的所有更新都不會傳遞迴驅動程序。通常跨任務的讀 寫變量是低效的,但是,Spark 還是爲兩種常見的使用模式提供了兩種有限的共享變量:
廣播變(Broadcast Variable)和累加器(Accumulator)

1.2 爲什麼要定義廣播變量 :

如果我們要在分佈式計算裏面分發大對象,例如:字典,集合,黑白名單等,這個都會由 Driver 端進行分發,一般來講,如果這個變量不是廣播變量,那麼每個 task 就會分發一份, 這在 task 數目十分多的情況下 Driver 的帶寬會成爲系統的瓶頸,而且會大量消耗 task 服務 器上的資源,如果將這個變量聲明爲廣播變量,那麼知識每個 executor 擁有一份,這個 executor 啓動的 task 會共享這個變量,節省了通信的成本和服務器的資源。
如果沒有使用廣播變量如下圖:
在這裏插入圖片描述
使用了廣播變量之後:

在這裏插入圖片描述

1.3 、如何定義和還原一個廣播變量 :

//定義: 
val a = 3 
val broadcast = sc.broadcast(a) 
//還原: 
val c = broadcast.value 
//注意:變量一旦被定義爲一個廣播變量,那麼這個變量只能讀,不能修改 

注意事項 :
1、能不能將一個 RDD 使用廣播變量廣播出去? 不能,因爲 RDD 是不存儲數據的。可以將 RDD 的結果廣播出去。
2、廣播變量只能在 Driver 端定義,不能在 Executor 端定義。
3、在 Driver 端可以修改廣播變量的值,在 Executor 端無法修改廣播變量的值。
4、如果 executor 端用到了 Driver 的變量,如果不使用廣播變量在 Executor 有多少 task 就有 多少 Driver 端的變量副本。
5、如果 Executor 端用到了 Driver 的變量,如果使用廣播變量在每個 Executor 中都只有一份 Driver 端的變量副本。

2. Accumulators(累加器)

2.1 爲什麼要定義累加器 :

在 Spark 應用程序中,我們經常會有這樣的需求,如異常監控,調試,記錄符合某特性的數 據的數目,這種需求都需要用到計數器,如果一個變量不被聲明爲一個累加器,那麼它將在 被改變時不會在 driver 端進行全局彙總,即在分佈式運行時每個 task 運行的只是原始變量的 一個副本,並不能改變原始變量的值,但是當這個變量被聲明爲累加器後,該變量就會有分 布式計數的功能。

圖解累加器:

錯誤的圖解:
在這裏插入圖片描述

正確的圖解:

在這裏插入圖片描述

2.2 如果定義和還原一個累加器

定義累加器: 
val a = sc.longAccumulator(0) 
還原累加器:
val b = a.value

注意事項
1、累加器在 Driver 端定義賦初始值,累加器只能在 Driver 端讀取最後的值,在 Excutor 端更 新。
2、累加器不是一個調優的操作,因爲如果不這樣做,結果是錯的

累加器代碼案例:

object _03SparkRDDAccumulatorOps {
    def main(args: Array[String]): Unit = {
        Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
        Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
        Logger.getLogger("org.project-spark").setLevel(Level.WARN)
        val conf = new SparkConf()
            .setMaster("local[2]")
            .setAppName(s"${_03SparkRDDAccumulatorOps.getClass.getSimpleName}")
        val sc = new SparkContext(conf)
        //創建一個累加器
        val countAccu = sc.longAccumulator("localAccu")
        val linesRDD:RDD[String] = sc.textFile("data/10w-line-data.txt")
        val wordsRDD:RDD[String] = linesRDD.flatMap(_.split("\\s+"))
        val pairsRDD:RDD[(String, Int)] = wordsRDD.map(word => {
            if (word == "local") {
                countAccu.add(1L)
            }
            (word, 1)
        })//.map((_, 1))
        val rbkRDD:RDD[(String, Int)] = pairsRDD.reduceByKey(_+_)
        rbkRDD.foreach(println)
//        val count = wordsRDD.filter(word => word == "local").count()
        println("countAccu1=" + countAccu.value)

        countAccu.reset()
        println("單詞個數:" + pairsRDD.reduceByKey(_+_).count())

        println("countAccu2=" + countAccu.value)
//        countAccu.
        Thread.sleep(100000)
        sc.stop()
    }
}
object _02SparkRDDBroadcastOps {
    def main(args: Array[String]): Unit = {
        Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
        Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
        Logger.getLogger("org.project-spark").setLevel(Level.WARN)
        val conf = new SparkConf()
            .setMaster("local[2]")
            .setAppName(s"${_02SparkRDDBroadcastOps.getClass.getSimpleName}")
        val sc = new SparkContext(conf)

        val stu = List(
            "1  鄭祥楷 1",
            "2  王佳豪 1",
            "3  劉鷹 2",
            "4  宋志華 3",
            "5  劉帆 4",
            "6  OLDLi 5"
        )

        val cls = List(
            "1 1807bd-bj",
            "2 1807bd-sz",
            "3 1807bd-wh",
            "4 1807bd-xa",
            "7 1805bd-bj"
        )
//        joinOps(sc, stu, cls)
        /*
            使用廣播變量來完成上述操作
            一般用戶表都比較大,而班級表相對很小,符合我們在共享變量中提出的第一個假設
            所以我們可以嘗試使用廣播變量來進行解決
         */
        val stuRDD = sc.parallelize(stu)
        //cls-->map---->
        val map = cls.map{case line => {
            (line.substring(0, line.indexOf(" ")), line.substring(line.indexOf(" ")).trim)
        }}.toMap
        //map--->broadcast
        val clsMapBC:Broadcast[Map[String, String]] = sc.broadcast(map)

        stuRDD.map{case line => {
            val map = clsMapBC.value
            val fields = line.split("\\s+")
            val cid = fields(2)
//            map.get(cid)
            val className = map.getOrElse(cid, "UnKnown")
            s"${fields(0)}\t${fields(1)}\t${className}"//在mr中學習到的map join
        }}.foreach(println)
        sc.stop()
    }

    private def joinOps(sc: SparkContext, stu: List[String], cls: List[String]) = {
        val stuRDD = sc.parallelize(stu)
        val clsRDD = sc.parallelize(cls)
        val cid2STURDD: RDD[(String, String)] = stuRDD.map { case line => {
            val fields = line.split("\\s+")
            (fields(2), line)
        }
        }

        val cid2ClassRDD: RDD[(String, String)] = clsRDD.map { case line => {
            val fields = line.split("\\s+")
            (fields(0), fields(1))
        }
        }

        //兩張表關聯--join
        println("---------inner join-------------")//reduce join
        val cid2InfoRDD: RDD[(String, (String, String))] = cid2STURDD.join(cid2ClassRDD)
        cid2InfoRDD.foreach(println)
    }
}

需要注意的地方:
1.累加器的值得獲取只能在driver端來進行,在transformation中不建議獲取。
2.累加器的執行,必須要依賴一個action的觸發,而且累加器值的獲取只能在該action觸發之後獲取。
3.重複觸發相關action,會造成累加器的值重複計算,所以在使用過程中要儘量避免。(可以通過累加器的重置解決該問題,accumulator.reset() )
4.如果這些累加器滿足不了需求,請嘗試自定義累加器。

3. 自定義累加器:

自定義累加器需要繼承AccumulatorParam,實現addInPlace和zero方法。
例1:實現Long類型的累加器


object LongAccumulatorParam extends AccumulatorParam[Long]{
  override def addInPlace(r1: Long, r2: Long) = {
    println(s"$r1\t$r2")
    r1 + r2
  }

  override def zero(initialValue: Long) = {
    println(initialValue)
    0
  }

  def main(args: Array[String]): Unit = {
    val sc = new SparkContext(new SparkConf().setAppName("testLongAccumulator"))
    val acc = sc.accumulator(0L, "LongAccumulator")
    sc.parallelize(Array(1L,2L,3L,4L,5L)).foreach(acc.add)
    println(acc.value)
    sc.stop()
  }
} 

在這裏插入圖片描述
記得點贊加關注!!! 不迷路 !!!
人活着真累:上車得排隊,愛你又受罪,吃飯沒香味,喝酒容易醉,掙錢得交稅!

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