Spark中RDD轉換算子以及案例

1 Spark核心編程

Spark計算框架爲了能對數據進行高併發和搞吞吐的處理,封裝了三大數據結構,分別是:

  • RDD:彈性分佈式數據集
  • 累加器:分佈式共享只寫變量
  • 廣播變量:分佈式共享只讀變量

1.1 RDD

代表是一個彈性的,不可變,可分區,裏面的元素可並行計算的集合

彈性:
存儲的彈性:內存與磁盤的自動切換
容錯的彈性:數據丟失可以自動修復
計算的彈性:計算出錯重試機制
分片的彈性:可根據需要重新分片
分佈式: 數據存儲在大數據集羣不同節點上
數據集: RDD封裝了計算邏輯,並不保存數據
數據抽象: RDD是一個抽象類,需要子類實現
不可變: RDD封裝了計算邏輯,是不可以改變的,想要改變,只能產生新的RDD,在新的RDD裏面封裝計算邏輯
可分區,並行計算

1.2 核心屬性

源碼

1.2.1 分區列表

RDD數據結構中存在分區列表,用於執行任務時並行計算,是實現分佈式計算的重要屬性

1.2.2 分區計算函數

計算時,使用分區計算函數對每個分區進行計算

1.2.3 RDD之間的依賴關係

RDD是計算模型的封裝,計算模型進行組合時,需要建立依賴關係

1.2.4 分區器(可選)

只有數據爲KV類型數據時,可以通過設定分區器自定義數據的分區,簡單說就是給任務分配不同的分區

1.2.5 首選位置(可選)

可以根據計算節點的狀態選擇不同的節點位置進行計算

1.3 實現原理

RDD體現了裝飾者設計模式,一層一層進行裝飾,與IO基本類似
裝飾者模式
RDD執行是延遲加載的
只有當collect進行採集的時候,纔會執行。

1.4 執行原理

執行時,先申請資源,然後將應用程序的數據處理邏輯分解成一個一個計算任務,將任務分配給資源不同節點,然後按照計算模型進行數據計算,最後得到計算結果
1)啓動Yarn集羣環境
2)Spark通過申請資源創建調度節點和計算節點
調度和計算節點

3)Spark框架根據需求將計算邏輯按分區劃分成不同的任務
分任務
4)調度節點將任務根據計算節點狀態發送到對應的計算節點進行計算
分給節點

1.5 基礎編程

1.5.1 RDD創建

1)從集合(內存)中創建RDD
Spark提供了兩種方法:parallelize和makeRDD

object SparkCollect {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[Int] = sparkContext.parallelize(
      List(1, 2, 4, 3)
    )

    val rdd2: RDD[Int] = sparkContext.makeRDD(
      List(1, 2, 3, 4)
    )
    rdd1.collect().foreach(println)
    rdd2.collect().foreach(println)
    sparkContext.stop()
  }

}

從底層代碼來看,makeRDD其實就是parallelize方法

2)從外部存儲(文件)創建RDD
外部數據集創建RDD:本地的文件系統,所有Hadoop支持的數據集,比如HDFS,HBase等

object SparkCollect {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
    val sparkContext = new SparkContext(sparkConf)

    val fileRDD: RDD[String] = sparkContext.textFile("D:\\1input\\word")
    fileRDD.collect().foreach(println)
    sparkContext.stop()
  }
}

3)從其他RDD創建
通過一個RDD運算完後,再產生新的RDD。
4)直接創建RDD(new)

1.5.2 RDD並行度與分區

讀取內存數據時
對於:val rdd: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4,5,6), 3)
底層進行數據分區的時候關鍵源碼:

def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
      (0 until numSlices).iterator.map { i =>
        val start = ((i * length) / numSlices).toInt
        val end = (((i + 1) * length) / numSlices).toInt
        (start, end)
      }
    }
case _ =>
        val array = seq.toArray // To prevent O(n^2) operations for List etc
        positions(array.length, numSlices).map { case (start, end) =>
            array.slice(start, end).toSeq
        }.toSeq

通過元組(start,end)進行操作,左閉右開截取數組數據,放入分區文件

讀取文件數據的分區和讀取內存中集合數據的分區是不一樣的
讀取文件時:
bytesRemaining:剩餘的字節
splitSize:它總共的字節
goalSize:每個分區的數據的大小
按照hadoop的行讀取規則,依照偏移量進行讀取,不足一行讀取一行:
詳細的過程:根據自定義的分區數,或者默認的2個分區,然後(long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);)計算得出目標分區的每個分區的數據,bytesRemaining)/splitSize > SPLIT_SLOP 這個條件成立,則進去一個分區,與hadoop的行讀取類似

* RDD轉換算子

1)map

函數說明:將處理的數據逐條進行映射轉換,這裏的轉換可以是類型的轉換,也可以是值的轉換
注意:分區不變,
mapValues操作的是隻有value,不改變key


    val dataRDD: RDD[Int] = rdd.map(
      num => {
        num * 2
      }
    )
    

2)mapPartitions

函數說明:以分區爲單位發送到計算節點進行處理,這裏的處理是指可以進行任意的處理,哪怕是過濾數據
返回的值需要可以迭代,可變可不變
注意:分區數據沒有處理完就不能釋放,容易造成內存溢出

3)mapPartitionsWithIndex

函數說明:處理的同時可以獲取當前分區索引

val dataRDD1 = dataRDD.mapPartitionsWithIndex(
    (index, datas) => {
         datas.map(index, _)
    }
)

4)flatMap

函數說明:將數據先進行扁平化,然後再進行映射處理,所以稱作扁平映射

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Any] = sparkContext.makeRDD(
      List(List(1, 2), 3, List(4, 5)), 1
    )

    val value: RDD[Any] = rdd.flatMap(
      data => {
        data match {
          case list: List[_] => list
          case d => List(d)
        }
      }
    )
    value.collect().foreach(println)

    sparkContext.stop()

  }

}

5)glom

函數說明:將同一個分區的數據轉換爲相同類型的數據處理

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4,2,1, 3, 44, 55), 2
    )

    val value: RDD[Array[Int]] = rdd.glom()
    value.collect().foreach(
      data =>{
        println("--------------------")
        for(i <- data){
          println(i)
        }
      }
    )

    sparkContext.stop()

  }

}

6)groupBy

函數說明:數據根據指定規則進行分組,分區默認不變,數據會被打亂,此過程爲shuffle

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4,2,1, 3, 44, 55), 2
    )

    val value: RDD[(Int, Iterable[Int])] = rdd.groupBy(
      data => {
        data % 10
      }
    )

    value.collect().foreach(
      data => {
        println("\nkey is :" + data._1)
        for(word <- data._2){
          print(word + " ")
        }
      }
    )

    sparkContext.stop()

  }

}

7)filter

函數說明:按照指定的規則將數據進行過濾,有可能出現數據傾斜

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4,2,1, 3, 44, 55), 2
    )

    val value: RDD[Int] = rdd.filter(_ % 2 == 0)
    value.collect().foreach(println)
    sparkContext.stop()

  }

}

8)sample

函數說明:根據規則從數據集中抽取數據,第一個參數爲是否放回,第二個參數是概率(放回爲大於1,不放回取0~1之間),第三個參數是隨機種子

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4, 2, 1, 3, 44, 55), 2
    )

    val value: RDD[Int] = rdd.sample(true, 2,1)
    value.collect().foreach(
      data =>{
        print( data +" ")
      }
    )

    println()
    val value1: RDD[Int] = rdd.sample(true, 2,1)
    value1.collect().foreach(
      data =>{
        print(data + " ")
      }
    )
  }

}

9)distinct

函數說明:將數據集中的重複數據去重

val dataRDD = sparkContext.makeRDD(List(
    1,2,3,4,1,2
),1)
val dataRDD1 = dataRDD.distinct()

10)coalesce

函數說明:縮減分區,提高小數據集的執行效率

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4, 2, 1, 3, 44, 55), 3
    )
    val value1: RDD[Int] = rdd.coalesce(2)

    val value: RDD[Int] = value1.mapPartitionsWithIndex(
      (par, data) => {
        println("分區號:" + par)
        for (num <- data) {
          print(num + " ")
        }
        println()
        data
      }
    )
    value.collect().foreach(println)


  }

}

11)repartition

函數說明:內部執行的就是coalesce,只是將coalesce默認的參數shuffle爲false改成了true,一個縮減分區用coalesce,擴大分區採用repartition

12)sortBy

函數說明:按照指定的規則,第一個參數指定規則,第二個參數爲是否正序排列,第三個參數是分成多少個分區

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[Int] = sparkContext.makeRDD(
      List(4, 2, 1, 9, 44, 55), 3
    )
    val value: RDD[Int] = rdd.sortBy(num => num%10, true, 6)
    println(value.getNumPartitions)
    value.collect().foreach(println)


  }

}

13)pipe

函數說明:管道,針對每個分區,都調用一次shell腳本,返回輸出的RDD

14)intersection

函數說明:對源RDD和參數RDD求交集後返回一個新的RDD

val rdd: RDD[Int] = sparkContext.makeRDD(
  List(4, 2, 1, 9, 44, 55), 3
)
val rdd2: RDD[Int] = sparkContext.makeRDD(List(1, 2, 3, 4))
val rdd3: RDD[Int] = rdd.intersection(rdd2)
rdd3.collect().foreach(println)

15)union

函數說明:求並集

16)substract

函數說明:求差集,rdd1-rdd2
注意:數據類型不一致會出錯

17)zip

函數說明:rdd1和rdd2兩兩打包形成元組
注意:類型可以不一致

18)partitionBy

函數說明:將數據按照指定的partitioner進行分區,Spark默認的分區器是HashPartitioner
默認分區器:

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
      List((1,"aaa"),(2,"bbb"),(3,"ccc")), 3
    )
    val rdd2: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(3))

    val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
      (num, data) => {
        data.map(
          d => {
            (num, d)
          }
        )
      }
    )
    rdd3.collect().foreach(println)


    sparkContext.stop()
  }

}

自定義分區器:

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, Partitioner, SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(Int,String)] = sparkContext.makeRDD(
      List((1,"aaa"),(20,"bbb"),(30,"ccc")), 3
    )
    val rdd2: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(3))

    val rdd3: RDD[(Int, (Int, String))] = rdd2.mapPartitionsWithIndex(
      (num, data) => {
        data.map(
          d => {
            (num, d)
          }
        )
      }
    )
    rdd3.saveAsTextFile("output")
    rdd3.collect().foreach(println)


    sparkContext.stop()
  }

  class MyPartitioner(num:Int) extends Partitioner{
    override def numPartitions: Int = num

    override def getPartition(key: Any): Int = {

      val zzuli: Int = key.toString.toInt
      if(zzuli < 10)
        0
      else if(zzuli == 10)
        1
      else
        2
    }
  }

}

19)reduceByKey

函數說明:將數據按照相同的key對value進行聚合操作

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 1),("a",22), ("b", 2), ("c", 3)))
    val value: RDD[(String, Int)] = rdd.reduceByKey((a:Int,b:Int)=>{
      a + b
    })
    value.collect().foreach(println)
    sparkContext.stop()

  }

}

20)groupByKey

函數說明:按照key來進行分組

val dataRDD1 =
    sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.groupByKey()

21)aggregateByKey

函數說明:將數據根據不同的規則進行分區計算和分區間計算

案例:取每個分區內相同key的最大值然後分區間相加
此算子是函數柯里化,存在兩個參數列表
第一個參數列表中的參數表示初始值
第二個參數列表含有兩個參數,一個爲分區內的計算規則,一個分區間的計算規則

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Aggregate {

  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setAppName("zzuli").setMaster("local[*]")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(String, Int)] = sparkContext.makeRDD(List(
      ("a", 1), ("a", 2), ("c", 3),
      ("b", 4), ("c", 5), ("c", 6))
      , 2
    )
    val result: RDD[(String, Int)] = rdd.aggregateByKey(0)(
      (x, y) => math.max(x, y), (x, y) => x + y
    )

   result.collect().foreach(println)
  }

}

22)foldByKey

當分區內計算規則和分區間計算規則相同時,直接一個參數就能達到aggregate算子的效果

23)combineByKey

函數說明:對k-v型rdd進行聚集操作的聚集函數,類似於aggregate,combineByKey允許用戶返回值的類型與輸入不一致
練習:求每個key的平均值
第一個參數:改變數據格式
第二個參數:分區內計算
第三個參數:分區間計算

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val list = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
    val sparkConf = new SparkConf().setAppName("zzuli").setMaster("local")
    val sparkContext = new SparkContext(sparkConf)
    val rdd: RDD[(String, Int)] = sparkContext.makeRDD(list, 2)
    val combineRdd: RDD[(String, (Int, Int))] = rdd.combineByKey(
      (a: Int) => ((a, 1)),
      (acc: (Int, Int), v: Int) => (acc._1 + v, acc._2 + 1),
      (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
    )
    val value: RDD[(String, Int)] = combineRdd.mapValues(
      data => {
        (data._1 / data._2)
      }
    )
    value.collect().foreach(println)

    sparkContext.stop()

  }

}

24)sortByKey

函數說明:按照(k,v)中的k進行排序,如果是自定義的k值,需要實現Ordered接口

val value: RDD[(String, Int)] = rdd1.sortByKey(true)

25)join

函數說明:相當於sql中的內連接

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
    val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("a", 9), ("a", 8)))

    val value: RDD[(String, (Int, Int))] = rdd1.join(rdd2)

    value.collect().foreach(println)

  }

}

26)leftOuterJoin

函數說明:類似於sql中的左外連接,遍歷每個(k,v),返回存在的值

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("e", 5)))
    val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("s", 8)))

    val value: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)

    value.collect().foreach(println)

  }

}

27)cogroup

函數說明:在(k,v)的RDD上調用,返回(K,(iterable,Iterable))的RDD

package com.baidu.exer

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Cogroup1 {
  def main(args: Array[String]): Unit = {
    val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("zzuli")
    val sparkContext = new SparkContext(sparkConf)

    val rdd1: RDD[(String, Int)] = sparkContext.makeRDD(List(("a", 2), ("b", 3), ("c", 5)))
    val rdd2: RDD[(String, Int)] = sparkContext.makeRDD(List(("b", 7), ("b", 9), ("c", 8)))

    val value: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)

    value.collect().foreach(println)

  }

}

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