Apache Spark Day3
Apache Spark Day3
RDD進階(面試)
分析WordCount
sc.textFile("hdfs:///words/t_word") //RDD0
.flatMap(_.split(" ")) //RDD1
.map((_,1)) //RDD2
.reduceByKey(_+_) //RDD3 finalRDD
.collect //Array 任務提交
RDD都有哪些特性?
* Internally, each RDD is characterized by five main properties: * * - A list of partitions * - A function for computing each split * - A list of dependencies on other RDDs * - Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned) * - Optionally, a list of preferred locations to compute each split on (e.g. block locations for * an HDFS file) *
- RDD只讀的具有分割槽分散式資料集-分割槽數等於該RDD並行度
- 每個分割槽獨立運算,儘可能實現分割槽本地性計算
- 只讀的資料集且RDD與RDD之間存在著相互依賴關係
- 針對於 key-value RDD,可以指定分割槽策略【可選】
- 基於資料所屬的位置,選擇最優位置實現本地性計算【可選】
RDD容錯
在理解DAGSchedule如何做狀態劃分的前提是需要大家瞭解一個專業術語lineage
通常被人們稱為RDD的血統。在瞭解什麼是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快取
快取是一種RDD計算容錯的一種手段,程式在RDD資料丟失的時候,可以通過快取快速計算當前RDD的值,而不需要反推出所有的RDD重新計算,因此Spark在需要對某個RDD多次使用的時候,為了提高程式的執行效率使用者可以考慮使用RDD的cache。
scala> var finalRDD=sc.textFile("hdfs:///words/src").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
finalRDD: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[25] at reduceByKey at <console>:24
scala> finalRDD.cache
res7: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[25] at reduceByKey at <console>:24
scala> finalRDD.collect
res8: Array[(String, Int)] = Array((this,1), (is,1), (day,2), (come,1), (hello,1), (baby,1), (up,1), (spark,1), (a,1), (on,1), (demo,1), (good,2), (study,1))
scala> finalRDD.collect
res9: Array[(String, Int)] = Array((this,1), (is,1), (day,2), (come,1), (hello,1), (baby,1), (up,1), (spark,1), (a,1), (on,1), (demo,1), (good,2), (study,1))
使用者可以呼叫upersist方法清空快取
scala> finalRDD.unpersist()
res11: org.apache.spark.rdd.RDD[(String, Int)] @scala.reflect.internal.annotations.uncheckedBounds = ShuffledRDD[25] at reduceByKey at <console>:24
除了呼叫cache之外,Spark提供了更細粒度的RDD快取方案,使用者可以根據叢集的記憶體狀態選擇合適的快取策略。使用者可以使用persist方法指定快取級別。
RDD#persist(StorageLevel.MEMORY_ONLY)
目前Spark支援的快取方案如下:
object StorageLevel {
val NONE = new StorageLevel(false, false, false, false)
val DISK_ONLY = new StorageLevel(true, false, false, false)# 僅僅儲存磁碟
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2) # 僅僅儲存磁碟 儲存兩份
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false) # 先序列化再 儲存記憶體,費CPU節省記憶體
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true) # 選擇這個!
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)
...
那如何選擇呢?
預設情況下,效能最高的當然是MEMORY_ONLY,但前提是你的記憶體必須足夠足夠大,可以綽綽有餘地存放下整個RDD的所有資料。因為不進行序列化與反序列化操作,就避免了這部分的效能開銷;對這個RDD的後續運算元操作,都是基於純記憶體中的資料的操作,不需要從磁碟檔案中讀取資料,效能也很高;而且不需要複製一份資料副本,並遠端傳送到其他節點上。但是這裡必須要注意的是,在實際的生產環境中,恐怕能夠直接用這種策略的場景還是有限的,如果RDD中資料比較多時(比如幾十億),直接用這種持久化級別,會導致JVM的OOM記憶體溢位異常。
如果使用MEMORY_ONLY級別時發生了記憶體溢位,那麼建議嘗試使用MEMORY_ONLY_SER級別。該級別會將RDD資料序列化後再儲存在記憶體中,此時每個partition僅僅是一個位元組陣列而已,大大減少了物件數量,並降低了記憶體佔用。這種級別比MEMORY_ONLY多出來的效能開銷,主要就是序列化與反序列化的開銷。但是後續運算元可以基於純記憶體進行操作,因此效能總體還是比較高的。此外,可能發生的問題同上,如果RDD中的資料量過多的話,還是可能會導致OOM記憶體溢位的異常。
不要洩漏到磁碟,除非你在記憶體中計算需要很大的花費,或者可以過濾大量資料,儲存部分相對重要的在記憶體中。否則儲存在磁碟中計算速度會很慢,效能急劇降低。
字尾為_2的級別,必須將所有資料都複製一份副本,併傳送到其他節點上,資料複製以及網路傳輸會導致較大的效能開銷,除非是要求作業的高可用性,否則不建議使用。
CheckPoint 機制
除了使用快取機制可以有效的保證RDD的故障恢復,但是如果快取失效還是會在導致系統重新計算RDD的結果,所以對於一些RDD的lineage較長的場景,計算比較耗時,使用者可以嘗試使用checkpoint機制儲存RDD的計算結果,該種機制和快取最大的不同在於,使用checkpoint之後被checkpoint的RDD資料直接持久化在檔案系統中,一般推薦將結果寫在hdfs中,這種checpoint並不會自動清空。注意checkpoint在計算的過程中先是對RDD做mark,在任務執行結束後,再對mark的RDD實行checkpoint,也就是要重新計算被Mark之後的rdd的依賴和結果。
sc.setCheckpointDir("hdfs://CentOS:9000/checkpoints")
val rdd1 = sc.textFile("hdfs://CentOS:9000/demo/words/")
.map(line => {
println(line)
})
//對當前RDD做標記
rdd1.checkpoint()
rdd1.collect()
因此在checkpoint一般需要和cache連用,這樣就可以保證計算一次。
sc.setCheckpointDir("hdfs://CentOS:9000/checkpoints")
val rdd1 = sc.textFile("hdfs://CentOS:9000/demo/words/")
.map(line => {
println(line)
})
rdd1.persist(StorageLevel.MEMORY_AND_DISK)//先cache
//對當前RDD做標記
rdd1.checkpoint()
rdd1.collect()
rdd1.unpersist()//刪除快取
任務計算原始碼剖析
理論指導
sc.textFile("hdfs:///demo/words/t_word") //RDD0
.flatMap(_.split(" ")) //RDD1
.map((_,1)) //RDD2
.reduceByKey(_+_) //RDD3 finalRDD
.collect //Array 任務提交
通過分析以上的程式碼,我們不難發現Spark在執行任務前期,會根據RDD的轉換關係形成一個任務執行DAG。將任務劃分成若干個stage。Spark底層在劃分stage的依據是根據RDD間的依賴關係劃分。Spark將RDD與RDD間的轉換分類:ShuffleDependency-寬依賴
| NarrowDependency-窄依賴
,Spark如果發現RDD與RDD之間存在窄依賴關係,系統會自動將存在窄依賴關係的RDD的計算運算元歸納為一個stage,如果遇到寬依賴系統開啟一個新的stage.
Spark 寬窄依賴判斷
寬依賴:父RDD的一個分割槽對應了子RDD的多個分割槽,出現分叉就認定為寬依賴。ShuffleDependency
窄依賴:父RDD的1個分割槽(多個父RDD)僅僅只對應子RDD的一個分割槽認定為窄依賴。OneToOneDependency
|RangeDependency
|PruneDependency
Spark在任務提交前期,首先根據finalRDD逆推出所有依賴RDD,以及RDD間依賴關係,如果遇到窄依賴合併在當前的stage中,如果是寬依賴開啟新的stage。
getMissingParentStages
private def getMissingParentStages(stage: Stage): List[Stage] = {
val missing = new HashSet[Stage]
val visited = new HashSet[RDD[_]]
// We are manually maintaining a stack here to prevent StackOverflowError
// caused by recursively visiting
val waitingForVisit = new ArrayStack[RDD[_]]
def visit(rdd: RDD[_]) {
if (!visited(rdd)) {
visited += rdd
val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
if (rddHasUncachedPartitions) {
for (dep <- rdd.dependencies) {
dep match {
case shufDep: ShuffleDependency[_, _, _] =>
val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)
if (!mapStage.isAvailable) {
missing += mapStage
}
case narrowDep: NarrowDependency[_] =>
waitingForVisit.push(narrowDep.rdd)
}
}
}
}
}
waitingForVisit.push(stage.rdd)
while (waitingForVisit.nonEmpty) {
visit(waitingForVisit.pop())
}
missing.toList
}
遇到寬依賴,系統會自動的建立一個
ShuffleMapStage
submitMissingTasks
private def submitMissingTasks(stage: Stage, jobId: Int) {
//計算分割槽
val partitionsToCompute: Seq[Int] = stage.findMissingPartitions()
...
//計算最佳位置
val taskIdToLocations: Map[Int, Seq[TaskLocation]] = try {
stage match {
case s: ShuffleMapStage =>
partitionsToCompute.map { id => (id, getPreferredLocs(stage.rdd, id))}.toMap
case s: ResultStage =>
partitionsToCompute.map { id =>
val p = s.partitions(id)
(id, getPreferredLocs(stage.rdd, p))
}.toMap
}
} catch {
case NonFatal(e) =>
stage.makeNewStageAttempt(partitionsToCompute.size)
listenerBus.post(SparkListenerStageSubmitted(stage.latestInfo, properties))
abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
runningStages -= stage
return
}
//將分割槽對映TaskSet
val tasks: Seq[Task[_]] = try {
val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
stage match {
case stage: ShuffleMapStage =>
stage.pendingPartitions.clear()
partitionsToCompute.map { id =>
val locs = taskIdToLocations(id)
val part = partitions(id)
stage.pendingPartitions += id
new ShuffleMapTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
Option(sc.applicationId), sc.applicationAttemptId, stage.rdd.isBarrier())
}
case stage: ResultStage =>
partitionsToCompute.map { id =>
val p: Int = stage.partitions(id)
val part = partitions(p)
val locs = taskIdToLocations(id)
new ResultTask(stage.id, stage.latestInfo.attemptNumber,
taskBinary, part, locs, id, properties, serializedTaskMetrics,
Option(jobId), Option(sc.applicationId), sc.applicationAttemptId,
stage.rdd.isBarrier())
}
}
} catch {
case NonFatal(e) =>
abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
runningStages -= stage
return
}
//呼叫taskScheduler#submitTasks TaskSet
if (tasks.size > 0) {
logInfo(s"Submitting ${tasks.size} missing tasks from $stage (${stage.rdd}) (first 15 " +
s"tasks are for partitions ${tasks.take(15).map(_.partitionId)})")
taskScheduler.submitTasks(new TaskSet(
tasks.toArray, stage.id, stage.latestInfo.attemptNumber, jobId, properties))
}
...
}
總結關鍵字:逆推、finalRDD、ResultStage 、ShuffleMapStage、ShuffleMapTask、ResultTask、ShuffleDependency、NarrowDependency、DAGScheduler、TaskScheduler、SchedulerBackend、DAGSchedulerEventProcessLoop
Jars依賴問題
1、可以使用–packages或者–jars解決依賴問題
[root@CentOS ~]# spark-submit --master spark://CentOS:7077 --deploy-mode client --class com.baizhi.outputs.SparkWordCountApplication --name RedisSinkDemo --total-executor-cores 6 --packages redis.clients:jedis:2.9.2 /root/original-spark-rdd-1.0-SNAPSHOT.jar
2、可以使用fat jar外掛將需要的依賴打包
[root@CentOS ~]# spark-submit --master spark://CentOS:7077 --deploy-mode client --class com.baizhi.outputs.SparkWordCountApplication --name RedisSinkDemo --total-executor-cores 6 /root/spark-rdd-1.0-SNAPSHOT.jar
3、注意當整合MySQL的時候,需要額外注意
- 將MySQL新增到HADOOP_CLASSPATH類路徑下
- 使用spark.executor.extraClassPath和spark.driver.extraClassPath能夠解決MySQL依賴問題
[root@CentOS ~]# spark-submit --master spark://CentOS:7077 --deploy-mode client --class com.baizhi.inputs.SparkMySQLUserQueryApplication --name MysqLReadDemo --total-executor-cores 6 --conf spark.driver.extraClassPath=/root/mysql-connector-java-5.1.49.jar --conf spark.executor.extraClassPath=/root/mysql-connector-java-5.1.49.jar /root/original-spark-rdd-1.0-SNAPSHOT.jar
如果大家覺得麻煩,還可以在 spark-defaut.conf 配置改引數:
spark.executor.extraClassPath=/root/.ivy2/jars/*
spark.driver.extraClassPath=/root/.ivy2/jars/*
[root@CentOS ~]# spark-submit --master spark://CentOS:7077 --deploy-mode client --class com.baizhi.inputs.SparkMySQLUserQueryApplication --name MysqLReadDemo --total-executor-cores 6 --packages mysql:mysql-connector-java:5.1.38 /root/original-spark-rdd-1.0-SNAPSHOT.jar
/.ivy2/jars/*
```shell
[root@CentOS ~]# spark-submit --master spark://CentOS:7077 --deploy-mode client --class com.baizhi.inputs.SparkMySQLUserQueryApplication --name MysqLReadDemo --total-executor-cores 6 --packages mysql:mysql-connector-java:5.1.38 /root/original-spark-rdd-1.0-SNAPSHOT.jar
相關文章
- Apache Ignite 與 Apache Spark比較ApacheSpark
- Apache Spark原始碼剖析ApacheSpark原始碼
- Apache Spark有哪些侷限性ApacheSpark
- Apache Spark 入門簡介ApacheSpark
- Spark流教程 :使用 Apache Spark 的Twitter情緒分析SparkApache
- Apache Spark和Hive有用的功能ApacheSparkHive
- Apache Spark Dataframe Join語法教程ApacheSpark
- 使用Apache Spark和Apache Hudi構建分析資料湖ApacheSpark
- Apache Spark 2一些使用案例ApacheSpark
- 快取Apache Spark RDD - 效能調優快取ApacheSpark
- Apache Kyuubi 助力 CDH 解鎖 Spark SQLApacheSparkSQL
- Apache Spark:分割槽和分桶 - NiveditaApacheSpark
- 帶有Apache Spark的Lambda架構ApacheSpark架構
- Apache Spark 記憶體管理詳解ApacheSpark記憶體
- [翻譯]Apache Spark入門簡介ApacheSpark
- Day3
- 【譯】Using .NET for Apache Spark to Analyze Log DataApacheSpark
- Apache Spark常見的三大誤解ApacheSpark
- Apache Spark有何用途?有何特點?ApacheSpark
- Apache Kyuubi & Celeborn,助力 Spark 擁抱雲原生ApacheSpark
- 為Apache Spark準備的深度學習ApacheSpark深度學習
- Apache Spark 2.0正式版釋出下載ApacheSpark
- 教程:Apache Spark SQL入門及實踐指南!ApacheSparkSQL
- Apache Spark技術實戰之3 -- Spark Cassandra Connector的安裝和使用ApacheSpark
- 在 Apache Spark 中使用機器學習進行客戶細分ApacheSpark機器學習
- Apache Spark SQL的高階Join連線技術ApacheSparkSQL
- 面向Apache Spark的Kotlin預覽版簡介ApacheSparkKotlin
- Building a Movie Recommendation Service with Apache Spark & Flask - Part 1UIApacheSparkFlask
- Building a Movie Recommendation Service with Apache Spark & Flask - Part 2UIApacheSparkFlask
- tomaztk/Spark-for-data-engineers:面向資料工程師的Apache Spark學習教程Spark工程師Apache
- Apache Spark技術實戰之6 -- spark-submit常見問題及其解決ApacheSparkMIT
- Apache Spark技術實戰之4 -- 利用Spark將json檔案匯入CassandraApacheSparkJSON
- Laravel 框架 day3Laravel框架
- 機器學習專案 - 使用 Apache Spark 建立電影推薦引擎機器學習ApacheSpark
- Apache Spark:大資料處理統一引擎ApacheSpark大資料
- 三種大資料流處理框架選擇比較:Apache Kafka流、Apache Spark流和Apache Flink - quora大資料框架ApacheKafkaSpark
- Day3晚筆記筆記
- day3連結串列