Spark入門詳解(三)-RDD編程

個人博客原文鏈接

RDD簡介

RDD是彈性分佈式數據集,完全彈性的,如果數據丟失一部分還可以重建。有自動容錯、位置感知調度和可伸縮性。

RDD共包含兩種計算方式,一種是transformations轉換,一種是actions操作,每種計算方式包含一些常用的方法。
注:Transformations轉換是Lazy的,也就是說從一個RDD轉換生成另一個RDD的操作不是馬上執行,Spark在遇到Transformations轉換時只會記錄需要這樣的轉換,並不會去執行,需要等到有Actions操作的時候纔會真正啓動計算過程進行計算。

創建RDD對象

1、 讀取外部文件
2、 並行一個集合

// 構建conf對象 SparkConf對象
// 必須設置的兩個參數:運行模式,app的名字
val conf = new SparkConf().setMaster("local").setAppName("My Spark")
// 構建sc對象 SparkContext對象
val sc = SparkContext.getOrCreate(conf)

// 1.讀取外部文件
// textFile objectFile sequenceFile hadoopFile newAPIHadoopFile
val text:RDD[String] = sc.textFile("src/data/fist.txt")
// 2.通過並行一個集合
val num:RDD[Int] = sc.parallelize(List(1,2,5,8,4))

RDD編程操作

轉換操作(transformations)

object TestTransfer {
  def main(args: Array[String]): Unit = {
    // 設置日誌級別
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc對象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)
    // 準備測試數據
    val num: RDD[Int] = sc.parallelize(1 to 10)
    val num1: RDD[Int] = sc.parallelize(List(1,5,3,2,5))
    val num2: RDD[Int] = sc.parallelize(List(2,7,3,5,1,0))
    val list: RDD[Int] = sc.parallelize(List(1,5,2,6,3,2))
    // 轉化爲PairRDD,(key,value)的形式
    val pairRdd: RDD[(Int, Int)] = list.map(x => (x,1))
    val pairRdd2: RDD[(Int, Int)] = list.map(x => (x,0))

    // 1. 普通的RDD
    // 2. PairRDD(鍵值對操作)
    // 我們在進行並行聚合、分組等操作時,常常需要利用鍵值對形式的RDD,稱爲Pair RDD
    // PairRDD相比於普通的RDD多了一些方法

    // 映射
    // 1. map(func) RDD
    // 作用於RDD的每一行,產出新RDD每一行爲經過函數處理後的結果。
    list.map(_*2).foreach(println)

    // 2. flatMap(func) RDD
    // 展平元素,將每個符合條件的元素都放在一個集合中
    list.flatMap(Range(0,_)).foreach(println)

    // 過濾
    // filter(func) RDD
    // 將每個元素根據指定函數條件過濾
    list.filter(_ % 3 != 0).foreach(println)

    // 聯合
    // union(other) RDD
    num1.union(num2).foreach(x => print(x + " "))

    // 交集
    // intersection(other) RDD
    num1.intersection(num2).foreach(x => print(x + " "))

    // 集合相減
    // subtract(other) RDD
    num1.subtract(num2).foreach(x => print(x + " "))
    
    // 去重複
    // distinct() RDD
    num.distinct().foreach(x => print(x + " "))
    
    // 聚合操作
    // 1. reduceByKey(func) PairRDD
    pairRdd.reduceByKey(_+_).foreach(println)

    // 2. combineByKey PairRDD
    // createCombiner: V => C ,這個函數把當前的值作爲參數,此時我們可以對其做些附加操作(類型轉換)並把它返回 (這一步類似於初始化操作)
    // mergeValue: (C, V) => C,該函數把元素V合併到之前的元素C(createCombiner)上 (這個操作在每個分區內進行)
    // mergeCombiners: (C, C) => C,該函數把2個元素C合併 (這個操作在不同分區間進行)
    val sum = pairRdd.combineByKey(v => (v,1),(a:(Int,Int),b:Int) => (a._1+b,a._2+1),(c1:(Int,Int),c2:(Int,Int)) => (c1._1+c2._1,c1._2+c2._2))
    val avg = sum.map{case(key,value)=>(key,value._1/value._2.toFloat)}
    avg.foreach(println)

    // 分組操作
    // 1. groupByKey()  PairRDD
    pairRdd.groupByKey().foreach(println)
    // 2. groupBy(func) RDD
    pairRdd.groupBy(x => x._1).foreach(println) // 根據key分組,相當於groupByKey
    pairRdd.groupBy(x => x._2).foreach(println) // 根據value分組

    // 排序
    // sortByKey(boolean) PairRDD
    // 默認true 升序 false 降序
    pairRdd.sortByKey().foreach(println)
    // sortBy(func,boolean) 自定義排序 RDD
    pairRdd.sortBy(x => x._2).foreach(println)

    // 連接操作
    // join(other) 等值連接 PairRDD
    val jnum: RDD[(Int, (Int, Int))] = pairRdd.join(pairRdd2)
    jnum.foreach(println)
    // rightOuterJoin(other) 右外連接 PairRDD
    val rnum: RDD[(Int, (Option[Int], Int))] = pairRdd.rightOuterJoin(pairRdd2)
    rnum.foreach(println)
    // leftOuterJoin(other) 左外連接 PairRDD
    val lnum: RDD[(Int, (Int, Option[Int]))] = pairRdd.leftOuterJoin(pairRdd2)
    lnum.foreach(println)
    // cogroup 全連接 PairRDD
    val cnum: RDD[(Int, (Iterable[Int], Iterable[Int]))] = pairRdd.cogroup(pairRdd2)
    cnum.foreach(println)
    // subtractByKey 根據key集合相減 PairRDD
    val snum: RDD[(Int, Int)] = pairRdd.subtractByKey(pairRdd2)
    snum.foreach(println)

    sc.stop()
  }
}

行動操作(actions)

object TestActions {
  def main(args: Array[String]): Unit = {
    // 設置日誌級別
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc對象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)

    // 創建測試數據
    val num: RDD[Int] = sc.parallelize(1 to 10)

    // 1. collect()
    // 將rdd內容轉化爲本地集合,返回一個List集合
    // retrieve rdd contents as a local collection
    num.collect().foreach(println)

    // 2. take(n)
    // 返回前幾個元素
    // return fist n elements
    num.take(3).foreach(println)

    // 3. top(n)
    // 返回最大的n個值
    num.top(3).foreach(println)

    // 4. takeOrdered(n)
    // 返回最小的n個值
    num.takeOrdered(3).foreach(println)

    // 5. takeSample(withReplace,num,[seed])
    // 隨機採樣(是否放回,採樣數量,隨機種子)
    num.takeSample(false,5).foreach(println)

    // 6. foreach(func)
    // RDD的每個元素會被func處理。在分區節點上運行,不會將結果收集到Drive節點
    num.foreach(println)

    // 7. count()
    // 返回RDD元素個數
    // count number of elements
    println(num.count())

    // 8. countByKey()
    // 統計PairRDD的key出現的次數
    val pairRdd = num.map(x => (x,1))
    println(pairRdd.countByKey())

    // 9. countByValue()
    // 統計RDD的value出現的次數
    println(num.countByValue())

    // 10. reduce(func)
    // 合併操作
    // merge elements with an associative function
    println(num.reduce(_+_))  // 計算總和

    // 11. fold(初始值)(func)
    // 帶初始值的合併操作,每個分區計算一次+driver
    println(num.fold(10)(_+_))

    // 12. saveAsTextFile
    // 將RDD的元素輸出到指定的外部存儲介質中,如HDFS/AFS中
    num.saveAsTextFile("src/sql_out/text/te_1")

    sc.stop()
  }
}

RDD持久化方法

Spark通過cache()方法可以將RDD持久化到內存中,一旦首次被觸發,該RDD將會被保留在計算節點的內存中,之後再調用這個RDD就不會再重複計算。

object TestActions {
  def main(args: Array[String]): Unit = {
    // 設置日誌級別
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc對象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)

    // 創建測試數據
    val num: RDD[Int] = sc.parallelize(1 to 10)
    // 持久化到內存
    num.cache()

    sc.stop()
  }
 }

共享變量

多個task想要共享某個變量,Spark爲此提供了兩個共享變量,一種是Broadcast Variable(廣播變量),另一種是Accumulator(累加變量)。

廣播變量

Broadcast Variable會將使用到的變量,僅僅爲每個節點拷貝一份,而不會爲每個task都拷貝一份。當變量很大時,其最大的用處是優化性能,減少網絡傳輸以及內存消耗。

  // 廣播變量
  // 廣播變量允許程序員將一個只讀的變量緩存在每臺機器上,而不用在任務之間傳遞變量。
  // 廣播變量可被用於有效地給每個節點一個大輸入數據集的副本
  // 注:爲了確保所有的節點獲得相同的變量,對象v在被廣播之後就不應該再修改
  @Test
  def counter3()={
    // 設置日誌級別
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc對象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)    // 小數據集
    val list = List(1,3,5,2,4,6)
    val rdd = sc.parallelize(List(1,3,2,3,4,5,6,1,3,4,5)).cache()
    // 廣播變量
    val broadcast = sc.broadcast(list)
    rdd.filter(num => {
      // 使用到了匿名函數的非局部變量
      // 從廣播變量中獲取數據  就近原則 本地的worker
      val list = broadcast.value
      list.contains(num)
    }).foreach(println)
  }

累加變量

Accumulator可以讓多個task共同操作一份變量,主要可以進行累加操作。但是task只能對Accumulator進行累加操作,不能讀取它的值。只有Driver程序可以讀取Accumulator的值。

  // 累加器
  // 累加器,提供了將工作節點中的值聚合到驅動器程序中的簡單語法。
  // 累加器的一個常見用途是在調式時對作業執行過程中的事件進行計數。
  // 四種形式
  // a. sc.longAccumulator("累加器名字");
  // b. sc.doubleAccumulator("累加器名字");
  // c. sc.collectionAccumulator[T]("");
  // d. 自定義累加器 extends AccumulatorV2[T,T]
  @Test
  def counter1()={
    // 設置日誌級別
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc對象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)
    // 累加器
    var countA = sc.longAccumulator("奇數個數")
    val list = List(1,3,5,2,4,6)
    // 計算rdd中所有的偶數和以及奇數個數
    val rdd = sc.parallelize(list)
    // 一次性計算兩個值
    val result = rdd.filter(
      x => {
        // 注:累加器最好寫在行動操作中
        // 如果寫在轉化操作中,後面每次行動操作都會調用之前轉化操作中的累加器,造成數值錯誤
        // 解決方案:1 在第一次行動操作前先cache 2 避免寫在轉化操作中
        if (x%2 != 0) countA.add(1L) // 累加器
        x%2 == 0
      }
    ).reduce(_+_)

    println(result)
    println(countA.value) // 通過累加器的value屬性獲取到累加值爲3
  }

  // 對於行動操作中的累加器,Spark只會把每個任務對各類假期的修改應用一次,無論失敗還是重複計算時都絕對可靠。
  // 對於轉化操作,可能會發生不止一次更新。
  // 解決方案:1 在第一次行動操作前先cache 2 避免寫在轉化操作中
  @Test
  def counter2()={
    // 設置日誌級別
    Logger.getLogger("org").setLevel(Level.WARN)
    // 初始化sc對象
    val conf = new SparkConf().setMaster("local[2]").setAppName("My Spark")
    val sc = SparkContext.getOrCreate(conf)
    // 累加器
    var countA = sc.longAccumulator("奇數個數")
    val list = List(1,3,5,2,4,6)
    // 計算rdd中所有的偶數和以及奇數個數
    val rdd = sc.parallelize(list)
    // 一次性計算兩個值
    val sum = rdd.filter(
      x => {
        if (x%2 != 0) countA.add(1L) // 累加器
        x%2 == 0
      }
    )
    // 沒有行動操作
    println(countA.value)
    sum.cache().reduce(_+_) // 第一次調用行動操作之前先緩存cache
//    sum.reduce(_+_)
    // 有一個行動操作
    println(countA.value)
    sum.count()
    // 有兩個行動操作
    println(countA.value)
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章