Spark-RDD

RDD的概述

俯視整個Spark程序,所有Spark的Application都包含一個Driver程序,該程序是用戶的主函數以及在集羣中執行各種各樣的並行操作。Spark中提出了一個核心的概念 resilient distributed dataset 簡稱 RDD,RDD是一個並行的分佈式集合 ,該集合數據可以跨節點存儲,所有的RDD操作都是在集羣的計算節點中並行的執行。RDD可以直接通過Hadoop的文件系統創建(或者所有Hadoop支持的文件系統創建),也可以通過定義在main函數中定義的Scala集合創建。Spark可以將RDD中的數據緩存在內存中,這樣在後續的分佈式計算可以重複使用,提升程序運行效率,其次RDD可在計算節點故障的時候進行故障恢復。(RRD創建/RDD緩存/RDD故障恢復)

RDD的創建

RDD是一個並行的帶有容錯的分佈式數據集,創建一個RDD的方式有兩種方式

  1. 從Driver中將Scala的本地集合 並行化爲一個RDD;
  2. 可以同外圍系統的數據集創建,一般需要使用Hadoop的InputFormat

集合創建RDD

val lines=List("this is a demo","hello world","good good")
val linesRDD:RDD[String]=sc.parallelize(lines,3)

注:這裏的3表示將集合分爲3個區域,將數據均勻分散,用戶還可以使用那可RDD創建。

val lines=List("this is a demo","hello world","good good")
val linesRDD:RDD[String]=sc.makeRDD(lines,3)

外圍系統數據

textFile

val linesRDD:RDD[String] = sc.textFile("file:///D:/demo/words",10)

這裏的file://表示讀取本地文件,如果需要讀取HDFS,請指定爲hdfs://,如果讀取的是來自HDFS上的文本數據,一般不需要指定分區數,如果用戶不指定分區數,文件並行加載的並行度默認等於文件的塊的數目,用戶如果指定了分區數,該分區必須大於目標文件的block數目。

newAPIHadoopRDD(HBase)

  1. 引入相關依賴
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-core_${scala.version}</artifactId>
    <version>${spark.version}</version>
    <!--<scope>provided</scope>-->
    <exclusions>
        <exclusion>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-auth</artifactId>
    <version>2.9.2</version> </dependency>
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.2.4</version>
</dependency>
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-server</artifactId>
    <version>1.2.4</version>
</dependency>
  1. 創建應用程序
package com.hw.demo04

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HConstants
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @aurhor:fql
  * @date 2019/9/25 19:28 
  * @type:
  */
object RDD_hnase {
  def main(args: Array[String]): Unit = {
     //1.創建SparkContext
    val sparkConf = new SparkConf()
    sparkConf.setAppName("wordCount")
    sparkConf.setMaster("local[6]")

    val sc = new  SparkContext(sparkConf)

    //2,創建分佈式集合RDD細化
    val hconf = new Configuration()
    hconf.set(HConstants.ZOOKEEPER_QUORUM,"CentOS")   //指定與Zookeeper的連接
    hconf.set(TableInputFormat.INPUT_TABLE,"baizhi:t_user")
 
    val userRDD:RDD[(ImmutableBytesWritable,Result)]  = sc.newAPIHadoopRDD(hconf,classOf[TableInputFormat],classOf[ImmutableBytesWritable],classOf[Result])
    userRDD.map(t=>{
      val key = Bytes.toString(t._1.get())
      val name=Bytes.toString(t._2.getValue("cf1".getBytes(),"name".getBytes()))
      (key,name)
    }).collect().foreach(println)
    sc.stop();
  }

}

RDD剖析

RDD是一個彈性的分佈式、數據集,彈性強調的RDD的容錯。默認情況下有三種容錯策略

  1. RDD重複計算-默認策略,一旦計算過程中系統出錯了,系統可以根據RDD的轉換關係去追溯上游RDD,逆推出RDD計算過程。之所以RDD能夠逆推出上游RDD(父RDD),主要是因爲Spark會記錄RDD之間依賴關係(RDD血統)。
  2. 由於重複計算成本較高,因此Spark提供了緩存機制,用於存儲RDD計算的中間結果,有利於在故障時期能夠快速狀態恢復,提升計算效率,如果某些計算需要重複使用也可以使用RDD緩存機制去優化。
  3. 由於緩存存在時效問題,如果當RDD緩存失效了,一旦故障,系統依然需要重複計算。因此針對一些比較耗時且計算成本比較高的計算,Spark提供了一種更安全可靠機制稱爲Checkpoint,緩存一般具有時間限制,長時間不使用會失效,但是checkpoint不同,checpoint機制會將RDD的計算結果直接持久化到磁盤中,被checpoint的數據,會一直持久化到磁盤中,除非手動刪除。

分佈式:強調的是Stage劃分名,Spark會嘗試將一個任務拆分成若干個階段,所有的計算都會按照State劃分,有條不紊的執行。Spark在對任務劃分時根據RDD間相互依賴關係劃分任務的階段。Spark中RDD的依賴關係稱爲RDD的血統-lineage,RDD血統依賴又分兩種依賴形式寬依賴/窄依賴如果遇到窄依賴系統會嘗試將RDD轉換歸併爲一個Stage、如果是寬依賴Spark會產生新的Satge。
數據集:強調的是RDD操作簡單和易用性,操作並行集合就等價於操作Scala的本地集合這麼簡單

在這裏插入圖片描述

上訴代碼通過textFile創建RDD並且指定分區數,如果不指定系統默認會按照HDFS上的Block的數目計算分區,該參數不能小於Block的數目。然後使用可flatMap,map算子對分區數據做轉換,不難看出Spark將textFile->flatMap->map規劃爲了一個State0,在執行到reduceByKey轉換的時候將開始又劃分出State1在執行到collect動作算子的時候,Spark任務提交,並且內部通過DAGScheduler計算出了state0和state1兩個狀態。

在這裏插入圖片描述

RDD容錯

在這裏插入圖片描述
上圖中描述了一個程序猿起源變化的過程,我們可以近似的理解類似於RDD的轉換過程,Spark的計算本質就是對RDD做各種轉換,由於RDD是一個不可變帶有分區只讀的集合,因此每次的轉換都需要上一次的RDD數據作爲本次轉換的輸入,因此RDD的lineage描述的是RDD間的相互依賴關係。爲了保證RDD中數據的健壯性,RDD數據集通過所謂的血統關係(Lineage)記住了它是如何從其它RDD中轉換過來的。Spark將RDD之間的關係歸類爲寬依賴和窄依賴。Spark會根據Lineage存儲的RDD的依賴關係對RDD計算做故障容錯,目前Saprk的容錯策略根據RDD依賴關係重新計算、RDD做Cache、RDD做Checkpoint手段完成RDD計算的故障容錯。

寬依賴|窄依賴

RDD在Lineage依賴方面分爲兩種Narrow Dependencies與Wide Dependencies用來解決數據容錯的高效性。Narrow Dependencies是指父RDD的每一個分區最多被一個子RDD的分區所用,表現爲一個父RDD的分區對應於一個子RDD的分區或多個父RDD的分區對應於子RDD的一個分區,也就是說一個父RDD的一個分區不可能對應一個子RDD的多個分區。Wide Dependencies父RDD的一個分區對應一個子RDD的多個分區。Spark在任務的提交的時候會調用DAGScheduler方法更具最後一個RDD逆向推導出任務的階段(根據寬、窄依賴)。
在這裏插入圖片描述

RDD緩存

緩存是一種RDD計算容錯的一種手段,程序在RDD數據丟失的時候,可以通過緩存快速計算當前RDD的值,而不需要反推出所有的RDD重新計算,因此Spark在需要對某個RDD多次使用的時候,爲了提高程序的執行效率用戶可以考慮使用RDD的cache。

//1.創建SparkContext
val sparkConf = new SparkConf()
.setAppName("wordcount")
.setMaster("local[6]")
val sc = new SparkContext(sparkConf)
//2.創建分佈式集合RDD -細化
val lines:RDD[String] = sc.textFile("file:///D:/demo/words")

val cacheRDD = lines.flatMap(_.split(" "))//緩存數據
.map((_, 1))
.persist(StorageLevel.MEMORY_ONLY)
//執行聚合
cacheRDD.reduceByKey(_ + _, 4).collect()

var start=System.currentTimeMillis()

for(i <- 0 to 100){//測試時間
    cacheRDD.reduceByKey(_ + _, 4).collect()
}
var end=System.currentTimeMillis()
//5.釋放資源
sc.stop()
println("總耗時:"+(end-start))

清除緩存

cacheRDD.unpersist()

思考,面對大規模數據集,直接將RDD數據緩存在內存中是否會導致內存溢出?
默認Spark的cache方法是用內存緩存的RDD中數據,這樣可以極大提升程序運行效率,但是面對大規模數據集合可能導致計算的節點產生OOM(Out of Memory),如果數據經過轉換後數據量級依然很大,這個時候不建議是用cache方法,Spark提供下一的存儲機制。

rdd#cache <==> rdd.persist(StorageLevel.MEMORY_ONLY)

默認情況下,用戶是用cache就等價於使用 rdd.persist(StorageLevel.MEMORY_ONLY),事實上Spark還提供其它的存儲策略,用於節省內存空間以及緩存數據的備份。

StorageLevel.MEMORY_ONLY       # 直接將RDD只存儲到內存,效率高,佔用空間大
StorageLevel.MEMORY_ONLY_2     # 直接將RDD只存儲到內存,效率高,佔用空間大,並且存儲兩份
StorageLevel.MEMORY_ONLY_SER   # 將RDD先進行序列化,效率相對較低,佔用空間稍微小
StorageLevel.MEMORY_ONLY_SER_2 # 將RDD先進行序列化,效率相對較低,佔用空間稍微小,並且存儲兩份

StorageLevel.MEMORY_AND_DISK
StorageLevel.MEMORY_AND_DISK_2
StorageLevel.MEMORY_AND_DISK_SER
StorageLevel.MEMORY_AND_DISK_SER_2  # 不確定情況下,一般使用該種緩存策略

StorageLevel.DISK_ONLY       # 基於磁盤存儲
StorageLevel.DISK_ONLY_2
StorageLevel.DISK_ONLY_SER
StorageLevel.DISK_ONLY_SER_2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章