Spark-SQL 面試準備 2

Spark Knowledge NO.2

11.RDD緩存:

Spark可以使用 persistcache 方法將任意 RDD 緩存到內存、磁盤文件系統中。緩存是容錯的,如果一個 RDD 分片丟失,可以通過構建它的 transformation自動重構。被緩存的 RDD 被使用的時,存取速度會被大大加速。一般的executor內存60%做 cache, 剩下的40%做task。

Spark中,RDD類可以使用cache() 和 persist() 方法來緩存。cache()是persist()的特例,將該RDD緩存到內存中。而persist可以指定一個StorageLevel。StorageLevel的列表可以在StorageLevel 伴生單例對象中找到。

Spark的不同StorageLevel ,目的滿足內存使用和CPU效率權衡上的不同需求。我們建議通過以下的步驟來進行選擇:

  • 如果你的RDDs可以很好的與默認的存儲級別(MEMORY_ONLY)契合,就不需要做任何修改了。這已經是CPU使用效率最高的選項,它使得RDDs的操作儘可能的快。
  • 如果不行,試着使用MEMORY_ONLY_SER並且選擇一個快速序列化的庫使得對象在有比較高的空間使用率的情況下,依然可以較快被訪問。
  • 儘可能不要存儲到硬盤上,除非計算數據集的函數,計算量特別大,或者它們過濾了大量的數據。否則,重新計算一個分區的速度,和與從硬盤中讀取基本差不多快。
  • 如果你想有快速故障恢復能力,使用複製存儲級別(例如:用Spark來響應web應用的請求)。所有的存儲級別都有通過重新計算丟失數據恢復錯誤的容錯機制,但是複製存儲級別可以讓你在RDD上持續的運行任務,而不需要等待丟失的分區被重新計算。
  • 如果你想要定義你自己的存儲級別(比如複製因子爲3而不是2),可以使用StorageLevel 單例對象的apply()方法。

在不會使用cached RDD的時候,及時使用unpersist方法來釋放它。

12.RDD共享變量:

在應用開發中,一個函數被傳遞給Spark操作(例如map和reduce),在一個遠程集羣上運行,它實際上操作的是這個函數用到的所有變量的獨立拷貝。這些變量會被拷貝到每一臺機器。通常看來,在任務之間中,讀寫共享變量顯然不夠高效。然而,Spark還是爲兩種常見的使用模式,提供了兩種有限的共享變量:廣播變量和累加器。

(1). 廣播變量(Broadcast Variables)

  • 廣播變量緩存到各個節點的內存中,而不是每個 Task

  • 廣播變量被創建後,能在集羣中運行的任何函數調用

  • 廣播變量是隻讀的,不能在被廣播後修改

  • 對於大數據集的廣播, Spark 嘗試使用高效的廣播算法來降低通信成本

val broadcastVar = sc.broadcast(Array(1, 2, 3))方法參數中是要廣播的變量
(2). 累加器

累加器只支持加法操作,可以高效地並行,用於實現計數器和變量求和。Spark 原生支持數值類型和標準可變集合的計數器,但用戶可以添加新的類型。只有Driver才能獲取累加器的值

13.spark-submit的時候如何引入外部jar包:

在通過spark-submit提交任務時,可以通過添加配置參數來指定

–driver-class-path 外部jar包
–jars 外部jar包

14.spark如何防止內存溢出:

  1. driver端的內存溢出
    可以增大driver的內存參數:spark.driver.memory (default 1g)
    這個參數用來設置Driver的內存。在Spark程序中,SparkContext,DAGScheduler都是運行在Driver端的。對應rdd的Stage切分也是在Driver端運行,如果用戶自己寫的程序有過多的步驟,切分出過多的Stage,這部分信息消耗的是Driver的內存,這個時候就需要調大Driver的內存。
  2. map過程產生大量對象導致內存溢出
    這種溢出的原因是在單個map中產生了大量的對象導致的,例如:rdd.map(x=>for(i <- 1 to 10000) yield i.toString),這個操作在rdd中,每個對象都產生了10000個對象,這肯定很容易產生內存溢出的問題。針對這種問題,在不增加內存的情況下,可以通過減少每個Task的大小,以便達到每個Task即使產生大量的對象Executor的內存也能夠裝得下。具體做法可以在會產生大量對象的map操作之前調用repartition方法,分區成更小的塊傳入map。例如:rdd.repartition(10000).map(x=>for(i <- 1 to 10000) yield i.toString)。
    面對這種問題注意,不能使用rdd.coalesce方法,這個方法只能減少分區,不能增加分區, 不會有shuffle的過程。
  3. 數據不平衡導致內存溢出
    數據不平衡除了有可能導致內存溢出外,也有可能導致性能的問題,解決方法和上面說的類似,就是調用repartition重新分區。這裏就不再累贅了。
  4. shuffle後內存溢出
    shuffle內存溢出的情況可以說都是shuffle後,單個文件過大導致的。在Spark中,join,reduceByKey這一類型的過程,都會有shuffle的過程,在shuffle的使用,需要傳入一個partitioner,大部分Spark中的shuffle操作,默認的partitioner都是HashPatitioner,默認值是父RDD中最大的分區數,這個參數通過spark.default.parallelism控制(在spark-sql中用spark.sql.shuffle.partitions) , spark.default.parallelism參數只對HashPartitioner有效,所以如果是別的Partitioner或者自己實現的Partitioner就不能使用spark.default.parallelism這個參數來控制shuffle的併發量了。如果是別的partitioner導致的shuffle內存溢出,就需要從partitioner的代碼增加partitions的數量。
  5. standalone模式下資源分配不均勻導致內存溢出
    在standalone的模式下如果配置了–total-executor-cores 和 –executor-memory 這兩個參數,但是沒有配置–executor-cores這個參數的話,就有可能導致,每個Executor的memory是一樣的,但是cores的數量不同,那麼在cores數量多的Executor中,由於能夠同時執行多個Task,就容易導致內存溢出的情況。這種情況的解決方法就是同時配置–executor-cores或者spark.executor.cores參數,確保Executor資源分配均勻。
    使用rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)代替rdd.cache()
    rdd.cache()和rdd.persist(Storage.MEMORY_ONLY)是等價的,在內存不足的時候rdd.cache()的數據會丟失,再次使用的時候會重算,而rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)在內存不足的時候會存儲在磁盤,避免重算,只是消耗點IO時間。

15.spark中的數據傾斜的現象,原因,後果:

(1) 數據傾斜的現象
多數task執行速度較快,少數task執行時間非常長,或者等待很長時間後提示你內存不足,執行失敗。
(2) 數據傾斜的原因

  • 數據問題
    1.key本身分佈不均衡(包括大量的key爲空)
    2.key的設置不合理
  • spark使用問題
    1. shuffle時的併發度不夠
    2. 計算方式有誤
  • 數據傾斜的後果
    1. spark中的stage的執行時間受限於最後那個執行完成的task,因此運行緩慢的任務會拖垮整個程序的運行速度(分佈式程序運行的速度是由最慢的那個task決定的)。
    2. 過多的數據在同一個task中運行,將會把executor撐爆。

1.數據問題造成的數據傾斜
找出異常的key
如果任務長時間卡在最後最後1個(幾個)任務,首先要對key進行抽樣分析,判斷是哪些key造成的。 選取key,對數據進行抽樣,統計出現的次數,根據出現次數大小排序取出前幾個。
比如: df.select(“key”).sample(false,0.1).(k=>(k,1)).reduceBykey(+).map(k=>(k._2,k._1)).sortByKey(false).take(10)
如果發現多數數據分佈都較爲平均,而個別數據比其他數據大上若干個數量級,則說明發生了數據傾斜。

經過分析,傾斜的數據主要有以下三種情況:
1、null(空值)或是一些無意義的信息()之類的,大多是這個原因引起。
2、無效數據,大量重複的測試數據或是對結果影響不大的有效數據。
3、有效數據,業務導致的正常數據分佈。
解決辦法
第1,2種情況,直接對數據進行過濾即可(因爲該數據對當前業務不會產生影響)。
第3種情況則需要進行一些特殊操作,常見的有以下幾種做法
(1) 隔離執行,將異常的key過濾出來單獨處理,最後與正常數據的處理結果進行union操作。
(2) 對key先添加隨機值,進行操作後,去掉隨機值,再進行一次操作。
(3) 使用reduceByKey 代替 groupByKey(reduceByKey用於對每個key對應的多個value進行merge操作,最重要的是它能夠在本地先進行merge操作,並且merge操作可以通過函數自定義.)
(4) 使用map join。
案例
如果使用reduceByKey因爲數據傾斜造成運行失敗的問題。具體操作流程如下:
(1) 將原始的 key 轉化爲 key + 隨機值(例如Random.nextInt)
(2) 對數據進行 reduceByKey(func)
(3) 將 key + 隨機值 轉成 key
(4) 再對數據進行 reduceByKey(func)
案例操作流程分析:
假設說有傾斜的Key,我們給所有的Key加上一個隨機數,然後進行reduceByKey操作;此時同一個Key會有不同的隨機數前綴,在進行reduceByKey操作的時候原來的一個非常大的傾斜的Key就分而治之變成若干個更小的Key,不過此時結果和原來不一樣,怎麼破?進行map操作,目的是把隨機數前綴去掉,然後再次進行reduceByKey操作。(當然,如果你很無聊,可以再次做隨機數前綴),這樣我們就可以把原本傾斜的Key通過分而治之方案分散開來,最後又進行了全局聚合
注意1: 如果此時依舊存在問題,建議篩選出傾斜的數據單獨處理。最後將這份數據與正常的數據進行union即可。
注意2: 單獨處理異常數據時,可以配合使用Map Join解決。

2.spark使用不當造成的數據傾斜
提高shuffle並行度
dataFrame和sparkSql可以設置spark.sql.shuffle.partitions參數控制shuffle的併發度,默認爲200。
rdd操作可以設置spark.default.parallelism控制併發度,默認參數由不同的Cluster Manager控制。
侷限性: 只是讓每個task執行更少的不同的key。無法解決個別key特別大的情況造成的傾斜,如果某些key的大小非常大,即使一個task單獨執行它,也會受到數據傾斜的困擾。
使用map join 代替reduce join
在小表不是特別大(取決於你的executor大小)的情況下使用,可以使程序避免shuffle的過程,自然也就沒有數據傾斜的困擾了.(詳細見http://blog.csdn.net/lsshlsw/article/details/50834858、http://blog.csdn.net/lsshlsw/article/details/48694893)
侷限性: 因爲是先將小數據發送到每個executor上,所以數據量不能太大。

16.spark中map-side-join關聯優化:

將多份數據進行關聯是數據處理過程中非常普遍的用法,不過在分佈式計算系統中,這個問題往往會變的非常麻煩,因爲框架提供的 join 操作一般會將所有數據根據 key 發送到所有的 reduce 分區中去,也就是 shuffle 的過程。造成大量的網絡以及磁盤IO消耗,運行效率極其低下,這個過程一般被稱爲 reduce-side-join。

如果其中有張表較小的話,我們則可以自己實現在 map 端實現數據關聯,跳過大量數據進行 shuffle 的過程,運行時間得到大量縮短,根據不同數據可能會有幾倍到數十倍的性能提升。

何時使用:在海量數據中匹配少量特定數據

原理:reduce-side-join 的缺陷在於會將key相同的數據發送到同一個partition中進行運算,大數據集的傳輸需要長時間的IO,同時任務併發度收到限制,還可能造成數據傾斜。

reduce-side-join 運行圖如下

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Kx0FESdz-1579501752609)(C:\Users\sqian\Documents\Others\images\20180909162441742.png)]

map-side-join 運行圖如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UAkbMMsL-1579501752612)(C:\Users\sqian\Documents\Others\images\20180909162409221.png)]

將少量的數據轉化爲Map進行廣播,廣播會將此 Map 發送到每個節點中,如果不進行廣播,每個task執行時都會去獲取該Map數據,造成了性能浪費。對大數據進行遍歷,使用mapPartition而不是map,因爲mapPartition是在每個partition中進行操作,因此可以減少遍歷時新建broadCastMap.value對象的空間消耗,同時匹配不到的數據也不會返回。

17.kafka整合sparkStreaming問題:

(1)、如何實現sparkStreaming讀取kafka中的數據
可以這樣說:在kafka0.10版本之前有二種方式與sparkStreaming整合,一種是基於receiver,一種是direct,然後分別闡述這2種方式分別是什麼
receiver:是採用了kafka高級api,利用receiver接收器來接受kafka topic中的數據,從kafka接收來的數據會存儲在spark的executor中,之後spark streaming提交的job會處理這些數據,kafka中topic的偏移量是保存在zk中的。
基本使用:

還有幾個需要注意的點:
在Receiver的方式中,Spark中的partition和kafka中的partition並不是相關的,所以如果我們加大每個topic的partition數量,僅僅是增加線程來處理由單一Receiver消費的主題。但是這並沒有增加Spark在處理數據上的並行度.
對於不同的Group和topic我們可以使用多個Receiver創建不同的Dstream來並行接收數據,之後可以利用union來統一成一個Dstream。
在默認配置下,這種方式可能會因爲底層的失敗而丟失數據. 因爲receiver一直在接收數據,在其已經通知zookeeper數據接收完成但是還沒有處理的時候,executor突然掛掉(或是driver掛掉通知executor關閉),緩存在其中的數據就會丟失. 如果希望做到高可靠, 讓數據零丟失,如果我們啓用了Write Ahead Logs(spark.streaming.receiver.writeAheadLog.enable=true)該機制會同步地將接收到的Kafka數據寫入分佈式文件系統(比如HDFS)上的預寫日誌中. 所以, 即使底層節點出現了失敗, 也可以使用預寫日誌中的數據進行恢復. 複製到文件系統如HDFS,那麼storage level需要設置成 StorageLevel.MEMORY_AND_DISK_SER,也就是KafkaUtils.createStream(…, StorageLevel.MEMORY_AND_DISK_SER)
direct:在spark1.3之後,引入了Direct方式。不同於Receiver的方式,Direct方式沒有receiver這一層,其會週期性的獲取Kafka中每個topic的每個partition中的最新offsets,之後根據設定的maxRatePerPartition來處理每個batch。(設置spark.streaming.kafka.maxRatePerPartition=10000。限制每秒鐘從topic的每個partition最多消費的消息條數)。
(2) 對比這2中方式的優缺點:
採用receiver方式:這種方式可以保證數據不丟失,但是無法保證數據只被處理一次,WAL實現的是At-least-once語義(至少被處理一次),如果在寫入到外部存儲的數據還沒有將offset更新到zookeeper就掛掉,這些數據將會被反覆消費. 同時,降低了程序的吞吐量。
採用direct方式:相比Receiver模式而言能夠確保機制更加健壯. 區別於使用Receiver來被動接收數據, Direct模式會週期性地主動查詢Kafka, 來獲得每個topic+partition的最新的offset, 從而定義每個batch的offset的範圍. 當處理數據的job啓動時, 就會使用Kafka的簡單consumer api來獲取Kafka指定offset範圍的數據。
優點:
1、簡化並行讀取
如果要讀取多個partition, 不需要創建多個輸入DStream然後對它們進行union操作. Spark會創建跟Kafka partition一樣多的RDD partition, 並且會並行從Kafka中讀取數據. 所以在Kafka partition和RDD partition之間, 有一個一對一的映射關係.
2、高性能
如果要保證零數據丟失, 在基於receiver的方式中, 需要開啓WAL機制. 這種方式其實效率低下, 因爲數據實際上被複制了兩份, Kafka自己本身就有高可靠的機制, 會對數據複製一份, 而這裏又會複製一份到WAL中. 而基於direct的方式, 不依賴Receiver, 不需要開啓WAL機制, 只要Kafka中作了數據的複製, 那麼就可以通過Kafka的副本進行恢復.
3、一次且僅一次的事務機制
基於receiver的方式, 是使用Kafka的高階API來在ZooKeeper中保存消費過的offset的. 這是消費Kafka數據的傳統方式. 這種方式配合着WAL機制可以保證數據零丟失的高可靠性, 但是卻無法保證數據被處理一次且僅一次, 可能會處理兩次. 因爲Spark和ZooKeeper之間可能是不同步的. 基於direct的方式, 使用kafka的簡單api, Spark Streaming自己就負責追蹤消費的offset, 並保存在checkpoint中. Spark自己一定是同步的, 因此可以保證數據是消費一次且僅消費一次。不過需要自己完成將offset寫入zk的過程,在官方文檔中都有相應介紹.
*簡單代碼實例:

  • messages.foreachRDD(rdd=>{

val message = rdd.map(_._2)//對數據進行一些操作

message.map(method)//更新zk上的offset (自己實現)

updateZKOffsets(rdd)

})

  • sparkStreaming程序自己消費完成後,自己主動去更新zk上面的偏移量。也可以將zk中的偏移量保存在mysql或者redis數據庫中,下次重啓的時候,直接讀取mysql或者redis中的偏移量,獲取到上次消費的偏移量,接着讀取數據。

18.spark master在使用zookeeper進行HA時,有哪些元數據保存在zookeeper?

答:spark通過這個參數spark.deploy.zookeeper.dir指定master元數據在zookeeper中保存的位置,包括worker,master,application,executors.standby節點要從zk中獲得元數據信息,恢復集羣運行狀態,才能對外繼續提供服務,作業提交資源申請等,在恢復前是不能接受請求的,另外,master切換需要注意兩點:

1.在master切換的過程中,所有的已經在運行的程序皆正常運行,因爲spark application在運行前就已經通過cluster manager獲得了計算資源,所以在運行時job本身的調度和處理master是沒有任何關係的;

2.在master的切換過程中唯一的影響是不能提交新的job,一方面不能提交新的應用程序給集羣,因爲只有Active master才能接受新的程序的提交請求,另外一方面,已經運行的程序也不能action操作觸發新的job提交請求。

19.spark master HA主從切換過程不會影響集羣已有的作業運行,爲什麼?

答:因爲程序在運行之前,已經向集羣申請過資源,這些資源已經提交給driver了,也就是說已經分配好資源了,這是粗粒度分配,一次性分配好資源後不需要再關心資源分配,在運行時讓driver和executor自動交互,弊端是如果資源分配太多,任務運行完不會很快釋放,造成資源浪費,這裏不適用細粒度分配的原因是因爲任務提交太慢。

20.什麼是粗粒度,什麼是細粒度,各自的優缺點是什麼?

答:1.粗粒度:啓動時就分配好資源,程序啓動,後續具體使用就使用分配好的資源,不需要再分配資源。好處:作業特別多時,資源複用率較高,使用粗粒度。缺點:容易資源浪費,如果一個job有1000個task,完成了999個,還有一個沒完成,那麼使用粗粒度。如果有999個資源閒置在那裏,會造成資源大量浪費。

2.細粒度:用資源的時候分配,用完了就立即回收資源,啓動會麻煩一點,啓動一次分配一次,會比較麻煩。

發佈了79 篇原創文章 · 獲贊 45 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章