大數據組件-Spark原理分析,寬窄依賴,閉包,spark全局累加器

RDD之間的關係

判斷是寬依賴還是窄依賴,取決於RDD的分區是否能放在同一個流水線上執行,取決於這兩個RDD是否是Shuffle關係,
如果是shuffle有kv對的就是寬依賴不能放在一個流水線上執行,不是shuffle關係的就是窄依賴可以放在一個流水線上執行

(1)窄依賴示例

import org.apache.spark.{SparkConf, SparkContext}
import org.junit.Test

class sparkDemo {

  @Test
  def narrowDependency: Unit ={
    //1.生成RDD
    val conf = new SparkConf().setMaster("local[3]").setAppName("dke")
    val sc = new SparkContext(conf)

    val rdd1 = sc.parallelize(Seq(1,2,3,4,5,6))
    val rdd2 = sc.parallelize(Seq("a","b","c"))

    //2.計算,笛卡爾積
    val resultRDD = rdd1.cartesian(rdd2)

    //3.獲取結果
    //collect()返回包含此RDD中所有元素的數組。
    //forecah遍歷每個元素
    resultRDD.collect().foreach(println(_))

    //關閉
    sc.stop()
  }
}

閉包

/**
    * 編寫一個函數,在這個函數內要有一個變量,返回一個函數值
    * 通過這個變量完成一個計算
    */
  @Test
  def test(): Unit ={
    val f: Int => Double = closure()
    val result = f(5)
    println(result)
  }
  //返回一個新的函數,執行一個計算

  def closure(): Int=>Double ={ //返回一個函數傳入int函數返回出double
    val factor = 3.14
    val function: Int => Double = (r:Int) => {
      math.pow(r,2) * factor //函數嵌套函數,內函數訪問外函數變量,就是閉包
    }
    function
  }

spark全局累加器

(1)全局計數的問題

var cunt = 0
//1.生成RDD
val conf = new SparkConf().setMaster("local[3]").setAppName("dke")
val sc = new SparkContext(conf)
sc.parallelize(Seq(1,2,3,4,5)).foreach(count+=_)

上述代碼是一個使用的錯誤案例,我們的spark是分佈式計算,在不同的節點進行計算,我們的RDD算子會被閉包分發到不同的節點上去,每一個節點都會有一個count = 0的變量,不同的節點使用不同的初始值爲0的count進行計算.所有他們的結果並不是預想的那樣會累加

(2)spark的全局累加器

//1.生成RDD
val conf = new SparkConf().setMaster("local[3]").setAppName("dke")
val sc = new SparkContext(conf)

val counter = sc.longAccummlator("counter")
sc.parallelize(Seq(1,2,3,4,5)).foreach(counter.add(_))

(3)自定義累加器

我們可以參照官方實現的累加器longAccummlator來自定義累加器
默認情況下,Scala 使用的是不可變集合,如果你想使用可變集合,需要引用 scala.collection.mutable.Set 包。

class Accumulator{
  @Test
  def acc(): Unit ={
    //1.生成RDD
    val conf = new SparkConf().setMaster("local[3]").setAppName("dke")
    val sc = new SparkContext(conf)

    val numAccumulator = new NumAccumulator
    //註冊,告訴spark框架我自己定義了一個累加器
    sc.register(numAccumulator,"num")

    sc.parallelize(Seq("1","2","3")).foreach(item=>numAccumulator.add(item))
    println(numAccumulator.value)
  }
}


class NumAccumulator extends AccumulatorV2[String,Set[String]]{
  private val nums: mutable.Set[String] = mutable.Set()

  /**
    * 告訴spark框架這個累加器是否爲空
    * @return
    */
  override def isZero: Boolean = {
    nums.isEmpty
  }

  /**
    * 提供給spark框架一個拷貝的累加器
    * @return
    */
  override def copy(): AccumulatorV2[String, Set[String]] = {
    val newAccumulator = new NumAccumulator()
    nums.synchronized{
      newAccumulator.nums ++= this.nums
    }
    newAccumulator
  }

  /**
    * 幫助spark框架清理累加器的內容
    */
  override def reset(): Unit = {
    nums.clear()
  }

  /**
    * 外部傳入要累加的內容,在這個方法中進行累加
    * @param v
    */
  override def add(v: String): Unit = {
    nums += v
  }

  /**
    * 累加器進行累加的時候,每個分佈式節點都有一個實例
    * 在最後Driver進行一次合併,把所有的實例的內容合併起來
    * @param other
    */
  override def merge(other: AccumulatorV2[String, Set[String]]): Unit = {
    nums ++= other.value
  }

  /**
    * 提供給外部累加結果
    * 外部有可能進行修改,如果是可變的集合,其外部的修改會影響內部的值
    * @return:返回一個不可變的nums集合
    */
  override def value: Set[String] = {
    nums.toSet
  }

}

廣播變量

(1)廣播變量的作用和原理

對於RDD算子內計算調用的變量,就產生了閉包,會一起被分發到不同的節點上的不同任務,就會產生很多冗餘的變量存在,我們想通過一個廣播變量在一個節點上就產生一個變量供不同的任務共同使用

class broadCast(){
  @Test
  def test02(): Unit ={
    //1.生成RDD
    val conf = new SparkConf().setMaster("local[3]").setAppName("dke")
    val sc = new SparkContext(conf)

    val b = sc.broadcast(1) //定義一個廣播變量,值爲1
    //下面正常進行rdd計算
    val dataSet = sc.parallelize(Seq(1,2,3,4))
    val resultSet = dataSet.map(item=>b.value+item).collect().foreach(println(_))
    
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章