精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

高性能Spark作業基礎:你必須知道的調優原則及建議

大數據 Spark
筆者根據之前的Spark作業開發經驗以及實踐積累,總結出了一套Spark作業的性能優化方案。本文作為Spark性能優化指南的基礎篇,主要講解開發調優以及資源調優。

前言

在大數據計算領域,Spark已經成為了越來越流行、越來越受歡迎的計算平臺之一。Spark的功能涵蓋了大數據領域的離線批處理、SQL類處理、流式/實時計算、機器學習、圖計算等各種不同類型的計算操作,應用范圍與前景非常廣泛。在美團•大眾點評,已經有很多同學在各種項目中嘗試使用Spark。大多數同學(包括筆者在內),最初開始嘗試使用Spark的原因很簡單,主要就是為了讓大數據計算作業的執行速度更快、性能更高。

然而,通過Spark開發出高性能的大數據計算作業,并不是那么簡單的。如果沒有對Spark作業進行合理的調優,Spark作業的執行速度可能會很慢,這樣就完全體現不出Spark作為一種快速大數據計算引擎的優勢來。因此,想要用好Spark,就必須對其進行合理的性能優化。

[[241288]]

Spark的性能調優實際上是由很多部分組成的,不是調節幾個參數就可以立竿見影提升作業性能的。我們需要根據不同的業務場景以及數據情況,對Spark作業進行綜合性的分析,然后進行多個方面的調節和優化,才能獲得***性能。

筆者根據之前的Spark作業開發經驗以及實踐積累,總結出了一套Spark作業的性能優化方案。整套方案主要分為開發調優、資源調優、數據傾斜調優、shuffle調優幾個部分。開發調優和資源調優是所有Spark作業都需要注意和遵循的一些基本原則,是高性能Spark作業的基礎;數據傾斜調優,主要講解了一套完整的用來解決Spark作業數據傾斜的解決方案;shuffle調優,面向的是對Spark的原理有較深層次掌握和研究的同學,主要講解了如何對Spark作業的shuffle運行過程以及細節進行調優。

本文作為Spark性能優化指南的基礎篇,主要講解開發調優以及資源調優。

開發調優

調優概述

Spark性能優化的***步,就是要在開發Spark作業的過程中注意和應用一些性能優化的基本原則。開發調優,就是要讓大家了解以下一些Spark基本開發原則,包括:RDD lineage設計、算子的合理使用、特殊操作的優化等。在開發過程中,時時刻刻都應該注意以上原則,并將這些原則根據具體的業務以及實際的應用場景,靈活地運用到自己的Spark作業中。

原則一:避免創建重復的RDD

通常來說,我們在開發一個Spark作業時,首先是基于某個數據源(比如Hive表或HDFS文件)創建一個初始的RDD;接著對這個RDD執行某個算子操作,然后得到下一個RDD;以此類推,循環往復,直到計算出最終我們需要的結果。在這個過程中,多個RDD會通過不同的算子操作(比如map、reduce等)串起來,這個“RDD串”,就是RDD lineage,也就是“RDD的血緣關系鏈”。

我們在開發過程中要注意:對于同一份數據,只應該創建一個RDD,不能創建多個RDD來代表同一份數據。

一些Spark初學者在剛開始開發Spark作業時,或者是有經驗的工程師在開發RDD lineage極其冗長的Spark作業時,可能會忘了自己之前對于某一份數據已經創建過一個RDD了,從而導致對于同一份數據,創建了多個RDD。這就意味著,我們的Spark作業會進行多次重復計算來創建多個代表相同數據的RDD,進而增加了作業的性能開銷。

一個簡單的例子 

  1. // 需要對名為“hello.txt”的HDFS文件進行一次map操作,再進行一次reduce操作。也就是說,需要對一份數據執行兩次算子操作。 
  2.  
  3. // 錯誤的做法:對于同一份數據執行多次算子操作時,創建多個RDD。 
  4. // 這里執行了兩次textFile方法,針對同一個HDFS文件,創建了兩個RDD出來,然后分別對每個RDD都執行了一個算子操作。 
  5. // 這種情況下,Spark需要從HDFS上兩次加載hello.txt文件的內容,并創建兩個單獨的RDD;第二次加載HDFS文件以及創建RDD的性能開銷,很明顯是白白浪費掉的。 
  6. val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt"
  7. rdd1.map(...) 
  8. val rdd2 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt"
  9. rdd2.reduce(...) 
  10.  
  11. // 正確的用法:對于一份數據執行多次算子操作時,只使用一個RDD。 
  12. // 這種寫法很明顯比上一種寫法要好多了,因為我們對于同一份數據只創建了一個RDD,然后對這一個RDD執行了多次算子操作。 
  13. // 但是要注意到這里為止優化還沒有結束,由于rdd1被執行了兩次算子操作,第二次執行reduce操作的時候,還會再次從源頭處重新計算一次rdd1的數據,因此還是會有重復計算的性能開銷。 
  14. // 要徹底解決這個問題,必須結合“原則三:對多次使用的RDD進行持久化”,才能保證一個RDD被多次使用時只被計算一次。 
  15. val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt"
  16. rdd1.map(...) 
  17. rdd1.reduce(...) 

原則二:盡可能復用同一個RDD

除了要避免在開發過程中對一份完全相同的數據創建多個RDD之外,在對不同的數據執行算子操作時還要盡可能地復用一個RDD。比如說,有一個RDD的數據格式是key-value類型的,另一個是單value類型的,這兩個RDD的value數據是完全一樣的。那么此時我們可以只使用key-value類型的那個RDD,因為其中已經包含了另一個的數據。對于類似這種多個RDD的數據有重疊或者包含的情況,我們應該盡量復用一個RDD,這樣可以盡可能地減少RDD的數量,從而盡可能減少算子執行的次數。

一個簡單的例子 

  1. // 錯誤的做法。 
  2.  
  3. // 有一個<Long, String>格式的RDD,即rdd1。 
  4. // 接著由于業務需要,對rdd1執行了一個map操作,創建了一個rdd2,而rdd2中的數據僅僅是rdd1中的value值而已,也就是說,rdd2是rdd1的子集。 
  5. JavaPairRDD<Long, String> rdd1 = ... 
  6. JavaRDD<String> rdd2 = rdd1.map(...) 
  7.  
  8. // 分別對rdd1和rdd2執行了不同的算子操作。 
  9. rdd1.reduceByKey(...) 
  10. rdd2.map(...) 
  11.  
  12. // 正確的做法。 
  13.  
  14. // 上面這個case中,其實rdd1和rdd2的區別無非就是數據格式不同而已,rdd2的數據完全就是rdd1的子集而已,卻創建了兩個rdd,并對兩個rdd都執行了一次算子操作。 
  15. // 此時會因為對rdd1執行map算子來創建rdd2,而多執行一次算子操作,進而增加性能開銷。 
  16.  
  17. // 其實在這種情況下完全可以復用同一個RDD。 
  18. // 我們可以使用rdd1,既做reduceByKey操作,也做map操作。 
  19. // 在進行第二個map操作時,只使用每個數據的tuple._2,也就是rdd1中的value值,即可。 
  20. JavaPairRDD<Long, String> rdd1 = ... 
  21. rdd1.reduceByKey(...) 
  22. rdd1.map(tuple._2...) 
  23.  
  24. // 第二種方式相較于***種方式而言,很明顯減少了一次rdd2的計算開銷。 
  25. // 但是到這里為止,優化還沒有結束,對rdd1我們還是執行了兩次算子操作,rdd1實際上還是會被計算兩次。 
  26. // 因此還需要配合“原則三:對多次使用的RDD進行持久化”進行使用,才能保證一個RDD被多次使用時只被計算一次。 

原則三:對多次使用的RDD進行持久化

當你在Spark代碼中多次對一個RDD做了算子操作后,恭喜,你已經實現Spark作業***步的優化了,也就是盡可能復用RDD。此時就該在這個基礎之上,進行第二步優化了,也就是要保證對一個RDD執行多次算子操作時,這個RDD本身僅僅被計算一次。

Spark中對于一個RDD執行多次算子的默認原理是這樣的:每次你對一個RDD執行一個算子操作時,都會重新從源頭處計算一遍,計算出那個RDD來,然后再對這個RDD執行你的算子操作。這種方式的性能是很差的。

因此對于這種情況,我們的建議是:對多次使用的RDD進行持久化。此時Spark就會根據你的持久化策略,將RDD中的數據保存到內存或者磁盤中。以后每次對這個RDD進行算子操作時,都會直接從內存或磁盤中提取持久化的RDD數據,然后執行算子,而不會從源頭處重新計算一遍這個RDD,再執行算子操作。

對多次使用的RDD進行持久化的代碼示例 

  1. // 如果要對一個RDD進行持久化,只要對這個RDD調用cache()和persist()即可。 
  2.  
  3. // 正確的做法。 
  4. // cache()方法表示:使用非序列化的方式將RDD中的數據全部嘗試持久化到內存中。 
  5. // 此時再對rdd1執行兩次算子操作時,只有在***次執行map算子時,才會將這個rdd1從源頭處計算一次。 
  6. // 第二次執行reduce算子時,就會直接從內存中提取數據進行計算,不會重復計算一個rdd。 
  7. val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt").cache() 
  8. rdd1.map(...) 
  9. rdd1.reduce(...) 
  10.  
  11. // persist()方法表示:手動選擇持久化級別,并使用指定的方式進行持久化。 
  12. // 比如說,StorageLevel.MEMORY_AND_DISK_SER表示,內存充足時優先持久化到內存中,內存不充足時持久化到磁盤文件中。 
  13. // 而且其中的_SER后綴表示,使用序列化的方式來保存RDD數據,此時RDD中的每個partition都會序列化成一個大的字節數組,然后再持久化到內存或磁盤中。 
  14. // 序列化的方式可以減少持久化的數據對內存/磁盤的占用量,進而避免內存被持久化數據占用過多,從而發生頻繁GC。 
  15. val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt").persist(StorageLevel.MEMORY_AND_DISK_SER) 
  16. rdd1.map(...) 
  17. rdd1.reduce(...) 

對于persist()方法而言,我們可以根據不同的業務場景選擇不同的持久化級別。

Spark的持久化級別

高性能Spark作業基礎:你必須知道的調優原則及建議

如何選擇一種最合適的持久化策略

默認情況下,性能***的當然是MEMORY_ONLY,但前提是你的內存必須足夠足夠大,可以綽綽有余地存放下整個RDD的所有數據。因為不進行序列化與反序列化操作,就避免了這部分的性能開銷;對這個RDD的后續算子操作,都是基于純內存中的數據的操作,不需要從磁盤文件中讀取數據,性能也很高;而且不需要復制一份數據副本,并遠程傳送到其他節點上。但是這里必須要注意的是,在實際的生產環境中,恐怕能夠直接用這種策略的場景還是有限的,如果RDD中數據比較多時(比如幾十億),直接用這種持久化級別,會導致JVM的OOM內存溢出異常。

如果使用MEMORY_ONLY級別時發生了內存溢出,那么建議嘗試使用MEMORY_ONLY_SER級別。該級別會將RDD數據序列化后再保存在內存中,此時每個partition僅僅是一個字節數組而已,大大減少了對象數量,并降低了內存占用。這種級別比MEMORY_ONLY多出來的性能開銷,主要就是序列化與反序列化的開銷。但是后續算子可以基于純內存進行操作,因此性能總體還是比較高的。此外,可能發生的問題同上,如果RDD中的數據量過多的話,還是可能會導致OOM內存溢出的異常。

如果純內存的級別都無法使用,那么建議使用MEMORY_AND_DISK_SER策略,而不是MEMORY_AND_DISK策略。因為既然到了這一步,就說明RDD的數據量很大,內存無法完全放下。序列化后的數據比較少,可以節省內存和磁盤的空間開銷。同時該策略會優先盡量嘗試將數據緩存在內存中,內存緩存不下才會寫入磁盤。

通常不建議使用DISK_ONLY和后綴為_2的級別:因為完全基于磁盤文件進行數據的讀寫,會導致性能急劇降低,有時還不如重新計算一次所有RDD。后綴為_2的級別,必須將所有數據都復制一份副本,并發送到其他節點上,數據復制以及網絡傳輸會導致較大的性能開銷,除非是要求作業的高可用性,否則不建議使用。

原則四:盡量避免使用shuffle類算子

如果有可能的話,要盡量避免使用shuffle類算子。因為Spark作業運行過程中,最消耗性能的地方就是shuffle過程。shuffle過程,簡單來說,就是將分布在集群中多個節點上的同一個key,拉取到同一個節點上,進行聚合或join等操作。比如reduceByKey、join等算子,都會觸發shuffle操作。

shuffle過程中,各個節點上的相同key都會先寫入本地磁盤文件中,然后其他節點需要通過網絡傳輸拉取各個節點上的磁盤文件中的相同key。而且相同key都拉取到同一個節點進行聚合操作時,還有可能會因為一個節點上處理的key過多,導致內存不夠存放,進而溢寫到磁盤文件中。因此在shuffle過程中,可能會發生大量的磁盤文件讀寫的IO操作,以及數據的網絡傳輸操作。磁盤IO和網絡數據傳輸也是shuffle性能較差的主要原因。

因此在我們的開發過程中,能避免則盡可能避免使用reduceByKey、join、distinct、repartition等會進行shuffle的算子,盡量使用map類的非shuffle算子。這樣的話,沒有shuffle操作或者僅有較少shuffle操作的Spark作業,可以大大減少性能開銷。

Broadcast與map進行join代碼示例 

  1. // 傳統的join操作會導致shuffle操作。 
  2. // 因為兩個RDD中,相同的key都需要通過網絡拉取到一個節點上,由一個task進行join操作。 
  3. val rdd3 = rdd1.join(rdd2) 
  4.  
  5. // Broadcast+map的join操作,不會導致shuffle操作。 
  6. // 使用Broadcast將一個數據量較小的RDD作為廣播變量。 
  7. val rdd2Data = rdd2.collect() 
  8. val rdd2DataBroadcast = sc.broadcast(rdd2Data) 
  9.  
  10. // 在rdd1.map算子中,可以從rdd2DataBroadcast中,獲取rdd2的所有數據。 
  11. // 然后進行遍歷,如果發現rdd2中某條數據的key與rdd1的當前數據的key是相同的,那么就判定可以進行join。 
  12. // 此時就可以根據自己需要的方式,將rdd1當前數據與rdd2中可以連接的數據,拼接在一起(String或Tuple)。 
  13. val rdd3 = rdd1.map(rdd2DataBroadcast...) 
  14.  
  15. // 注意,以上操作,建議僅僅在rdd2的數據量比較少(比如幾百M,或者一兩G)的情況下使用。 
  16. // 因為每個Executor的內存中,都會駐留一份rdd2的全量數據。 

原則五:使用map-side預聚合的shuffle操作

如果因為業務需要,一定要使用shuffle操作,無法用map類的算子來替代,那么盡量使用可以map-side預聚合的算子。

所謂的map-side預聚合,說的是在每個節點本地對相同的key進行一次聚合操作,類似于MapReduce中的本地combiner。map-side預聚合之后,每個節點本地就只會有一條相同的key,因為多條相同的key都被聚合起來了。其他節點在拉取所有節點上的相同key時,就會大大減少需要拉取的數據數量,從而也就減少了磁盤IO以及網絡傳輸開銷。通常來說,在可能的情況下,建議使用reduceByKey或者aggregateByKey算子來替代掉groupByKey算子。因為reduceByKey和aggregateByKey算子都會使用用戶自定義的函數對每個節點本地的相同key進行預聚合。而groupByKey算子是不會進行預聚合的,全量的數據會在集群的各個節點之間分發和傳輸,性能相對來說比較差。

比如如下兩幅圖,就是典型的例子,分別基于reduceByKey和groupByKey進行單詞計數。其中***張圖是groupByKey的原理圖,可以看到,沒有進行任何本地聚合時,所有數據都會在集群節點之間傳輸;第二張圖是reduceByKey的原理圖,可以看到,每個節點本地的相同key數據,都進行了預聚合,然后才傳輸到其他節點上進行全局聚合。 

groupByKey實現wordcount原理

 reduceByKey實現wordcount原理

原則六:使用高性能的算子

除了shuffle相關的算子有優化原則之外,其他的算子也都有著相應的優化原則。

使用reduceByKey/aggregateByKey替代groupByKey

詳情見“原則五:使用map-side預聚合的shuffle操作”。

使用mapPartitions替代普通map

mapPartitions類的算子,一次函數調用會處理一個partition所有的數據,而不是一次函數調用處理一條,性能相對來說會高一些。但是有的時候,使用mapPartitions會出現OOM(內存溢出)的問題。因為單次函數調用就要處理掉一個partition所有的數據,如果內存不夠,垃圾回收時是無法回收掉太多對象的,很可能出現OOM異常。所以使用這類操作時要慎重!

使用foreachPartitions替代foreach

原理類似于“使用mapPartitions替代map”,也是一次函數調用處理一個partition的所有數據,而不是一次函數調用處理一條數據。在實踐中發現,foreachPartitions類的算子,對性能的提升還是很有幫助的。比如在foreach函數中,將RDD中所有數據寫MySQL,那么如果是普通的foreach算子,就會一條數據一條數據地寫,每次函數調用可能就會創建一個數據庫連接,此時就勢必會頻繁地創建和銷毀數據庫連接,性能是非常低下;但是如果用foreachPartitions算子一次性處理一個partition的數據,那么對于每個partition,只要創建一個數據庫連接即可,然后執行批量插入操作,此時性能是比較高的。實踐中發現,對于1萬條左右的數據量寫MySQL,性能可以提升30%以上。

使用filter之后進行coalesce操作

通常對一個RDD執行filter算子過濾掉RDD中較多數據后(比如30%以上的數據),建議使用coalesce算子,手動減少RDD的partition數量,將RDD中的數據壓縮到更少的partition中去。因為filter之后,RDD的每個partition中都會有很多數據被過濾掉,此時如果照常進行后續的計算,其實每個task處理的partition中的數據量并不是很多,有一點資源浪費,而且此時處理的task越多,可能速度反而越慢。因此用coalesce減少partition數量,將RDD中的數據壓縮到更少的partition之后,只要使用更少的task即可處理完所有的partition。在某些場景下,對于性能的提升會有一定的幫助。

使用repartitionAndSortWithinPartitions替代repartition與sort類操作

repartitionAndSortWithinPartitions是Spark官網推薦的一個算子,官方建議,如果需要在repartition重分區之后,還要進行排序,建議直接使用repartitionAndSortWithinPartitions算子。因為該算子可以一邊進行重分區的shuffle操作,一邊進行排序。shuffle與sort兩個操作同時進行,比先shuffle再sort來說,性能可能是要高的。

原則七:廣播大變量

有時在開發過程中,會遇到需要在算子函數中使用外部變量的場景(尤其是大變量,比如100M以上的大集合),那么此時就應該使用Spark的廣播(Broadcast)功能來提升性能。

在算子函數中使用到外部變量時,默認情況下,Spark會將該變量復制多個副本,通過網絡傳輸到task中,此時每個task都有一個變量副本。如果變量本身比較大的話(比如100M,甚至1G),那么大量的變量副本在網絡中傳輸的性能開銷,以及在各個節點的Executor中占用過多內存導致的頻繁GC,都會極大地影響性能。

因此對于上述情況,如果使用的外部變量比較大,建議使用Spark的廣播功能,對該變量進行廣播。廣播后的變量,會保證每個Executor的內存中,只駐留一份變量副本,而Executor中的task執行時共享該Executor中的那份變量副本。這樣的話,可以大大減少變量副本的數量,從而減少網絡傳輸的性能開銷,并減少對Executor內存的占用開銷,降低GC的頻率。

廣播大變量的代碼示例 

  1. // 以下代碼在算子函數中,使用了外部的變量。 
  2. // 此時沒有做任何特殊操作,每個task都會有一份list1的副本。 
  3. val list1 = ... 
  4. rdd1.map(list1...) 
  5.  
  6. // 以下代碼將list1封裝成了Broadcast類型的廣播變量。 
  7. // 在算子函數中,使用廣播變量時,首先會判斷當前task所在Executor內存中,是否有變量副本。 
  8. // 如果有則直接使用;如果沒有則從Driver或者其他Executor節點上遠程拉取一份放到本地Executor內存中。 
  9. // 每個Executor內存中,就只會駐留一份廣播變量副本。 
  10. val list1 = ... 
  11. val list1Broadcast = sc.broadcast(list1) 
  12. rdd1.map(list1Broadcast...) 

原則八:使用Kryo優化序列化性能

在Spark中,主要有三個地方涉及到了序列化:

  • 在算子函數中使用到外部變量時,該變量會被序列化后進行網絡傳輸(見“原則七:廣播大變量”中的講解)。
  • 將自定義的類型作為RDD的泛型類型時(比如JavaRDD,Student是自定義類型),所有自定義類型對象,都會進行序列化。因此這種情況下,也要求自定義的類必須實現Serializable接口。
  • 使用可序列化的持久化策略時(比如MEMORY_ONLY_SER),Spark會將RDD中的每個partition都序列化成一個大的字節數組。

對于這三種出現序列化的地方,我們都可以通過使用Kryo序列化類庫,來優化序列化和反序列化的性能。Spark默認使用的是Java的序列化機制,也就是ObjectOutputStream/ObjectInputStream API來進行序列化和反序列化。但是Spark同時支持使用Kryo序列化庫,Kryo序列化類庫的性能比Java序列化類庫的性能要高很多。官方介紹,Kryo序列化機制比Java序列化機制,性能高10倍左右。Spark之所以默認沒有使用Kryo作為序列化類庫,是因為Kryo要求***要注冊所有需要進行序列化的自定義類型,因此對于開發者來說,這種方式比較麻煩。

以下是使用Kryo的代碼示例,我們只要設置序列化類,再注冊要序列化的自定義類型即可(比如算子函數中使用到的外部變量類型、作為RDD泛型類型的自定義類型等): 

  1. // 創建SparkConf對象。 
  2. val conf = new SparkConf().setMaster(...).setAppName(...) 
  3. // 設置序列化器為KryoSerializer。 
  4. conf.set("spark.serializer""org.apache.spark.serializer.KryoSerializer"
  5. // 注冊要序列化的自定義類型。 
  6. conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2])) 

原則九:優化數據結構

  • Java中,有三種類型比較耗費內存:
  • 對象,每個Java對象都有對象頭、引用等額外的信息,因此比較占用內存空間。
  • 字符串,每個字符串內部都有一個字符數組以及長度等額外信息。

集合類型,比如HashMap、LinkedList等,因為集合類型內部通常會使用一些內部類來封裝集合元素,比如Map.Entry。

因此Spark官方建議,在Spark編碼實現中,特別是對于算子函數中的代碼,盡量不要使用上述三種數據結構,盡量使用字符串替代對象,使用原始類型(比如Int、Long)替代字符串,使用數組替代集合類型,這樣盡可能地減少內存占用,從而降低GC頻率,提升性能。

但是在筆者的編碼實踐中發現,要做到該原則其實并不容易。因為我們同時要考慮到代碼的可維護性,如果一個代碼中,完全沒有任何對象抽象,全部是字符串拼接的方式,那么對于后續的代碼維護和修改,無疑是一場巨大的災難。同理,如果所有操作都基于數組實現,而不使用HashMap、LinkedList等集合類型,那么對于我們的編碼難度以及代碼可維護性,也是一個極大的挑戰。因此筆者建議,在可能以及合適的情況下,使用占用內存較少的數據結構,但是前提是要保證代碼的可維護性。

資源調優

調優概述

在開發完Spark作業之后,就該為作業配置合適的資源了。Spark的資源參數,基本都可以在spark-submit命令中作為參數設置。很多Spark初學者,通常不知道該設置哪些必要的參數,以及如何設置這些參數,***就只能胡亂設置,甚至壓根兒不設置。資源參數設置的不合理,可能會導致沒有充分利用集群資源,作業運行會極其緩慢;或者設置的資源過大,隊列沒有足夠的資源來提供,進而導致各種異常。總之,無論是哪種情況,都會導致Spark作業的運行效率低下,甚至根本無法運行。因此我們必須對Spark作業的資源使用原理有一個清晰的認識,并知道在Spark作業運行過程中,有哪些資源參數是可以設置的,以及如何設置合適的參數值。

Spark作業基本運行原理 

Spark基本運行原理

詳細原理見上圖。我們使用spark-submit提交一個Spark作業之后,這個作業就會啟動一個對應的Driver進程。根據你使用的部署模式(deploy-mode)不同,Driver進程可能在本地啟動,也可能在集群中某個工作節點上啟動。Driver進程本身會根據我們設置的參數,占有一定數量的內存和CPU core。而Driver進程要做的***件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是其他的資源管理集群,美團•大眾點評使用的是YARN作為資源管理集群)申請運行Spark作業需要使用的資源,這里的資源指的就是Executor進程。YARN集群管理器會根據我們為Spark作業設置的資源參數,在各個工作節點上,啟動一定數量的Executor進程,每個Executor進程都占有一定數量的內存和CPU core。

在申請到了作業執行所需的資源之后,Driver進程就會開始調度和執行我們編寫的作業代碼了。Driver進程會將我們編寫的Spark作業代碼分拆為多個stage,每個stage執行一部分代碼片段,并為每個stage創建一批task,然后將這些task分配到各個Executor進程中執行。task是最小的計算單元,負責執行一模一樣的計算邏輯(也就是我們自己編寫的某個代碼片段),只是每個task處理的數據不同而已。一個stage的所有task都執行完畢之后,會在各個節點本地的磁盤文件中寫入計算中間結果,然后Driver就會調度運行下一個stage。下一個stage的task的輸入數據就是上一個stage輸出的中間結果。如此循環往復,直到將我們自己編寫的代碼邏輯全部執行完,并且計算完所有的數據,得到我們想要的結果為止。

Spark是根據shuffle類算子來進行stage的劃分。如果我們的代碼中執行了某個shuffle類算子(比如reduceByKey、join等),那么就會在該算子處,劃分出一個stage界限來。可以大致理解為,shuffle算子執行之前的代碼會被劃分為一個stage,shuffle算子執行以及之后的代碼會被劃分為下一個stage。因此一個stage剛開始執行的時候,它的每個task可能都會從上一個stage的task所在的節點,去通過網絡傳輸拉取需要自己處理的所有key,然后對拉取到的所有相同的key使用我們自己編寫的算子函數執行聚合操作(比如reduceByKey()算子接收的函數)。這個過程就是shuffle。

當我們在代碼中執行了cache/persist等持久化操作時,根據我們選擇的持久化級別的不同,每個task計算出來的數據也會保存到Executor進程的內存或者所在節點的磁盤文件中。

因此Executor的內存主要分為三塊:***塊是讓task執行我們自己編寫的代碼時使用,默認是占Executor總內存的20%;第二塊是讓task通過shuffle過程拉取了上一個stage的task的輸出后,進行聚合等操作時使用,默認也是占Executor總內存的20%;第三塊是讓RDD持久化時使用,默認占Executor總內存的60%。

task的執行速度是跟每個Executor進程的CPU core數量有直接關系的。一個CPU core同一時間只能執行一個線程。而每個Executor進程上分配到的多個task,都是以每個task一條線程的方式,多線程并發運行的。如果CPU core數量比較充足,而且分配到的task數量比較合理,那么通常來說,可以比較快速和高效地執行完這些task線程。

以上就是Spark作業的基本運行原理的說明,大家可以結合上圖來理解。理解作業基本原理,是我們進行資源參數調優的基本前提。

資源參數調優

了解完了Spark作業運行的基本原理之后,對資源相關的參數就容易理解了。所謂的Spark資源參數調優,其實主要就是對Spark運行過程中各個使用資源的地方,通過調節各種參數,來優化資源使用的效率,從而提升Spark作業的執行性能。以下參數就是Spark中主要的資源參數,每個參數都對應著作業運行原理中的某個部分,我們同時也給出了一個調優的參考值。

num-executors

參數說明:該參數用于設置Spark作業總共要用多少個Executor進程來執行。Driver在向YARN集群管理器申請資源時,YARN集群管理器會盡可能按照你的設置來在集群的各個工作節點上,啟動相應數量的Executor進程。這個參數非常之重要,如果不設置的話,默認只會給你啟動少量的Executor進程,此時你的Spark作業的運行速度是非常慢的。

參數調優建議:每個Spark作業的運行一般設置50~100個左右的Executor進程比較合適,設置太少或太多的Executor進程都不好。設置的太少,無法充分利用集群資源;設置的太多的話,大部分隊列可能無法給予充分的資源。

executor-memory

參數說明:該參數用于設置每個Executor進程的內存。Executor內存的大小,很多時候直接決定了Spark作業的性能,而且跟常見的JVM OOM異常,也有直接的關聯。

參數調優建議:每個Executor進程的內存設置4G~8G較為合適。但是這只是一個參考值,具體的設置還是得根據不同部門的資源隊列來定。可以看看自己團隊的資源隊列的***內存限制是多少,num-executors乘以executor-memory,是不能超過隊列的***內存量的。此外,如果你是跟團隊里其他人共享這個資源隊列,那么申請的內存量***不要超過資源隊列***總內存的1/3~1/2,避免你自己的Spark作業占用了隊列所有的資源,導致別的同學的作業無法運行。

executor-cores

參數說明:該參數用于設置每個Executor進程的CPU core數量。這個參數決定了每個Executor進程并行執行task線程的能力。因為每個CPU core同一時間只能執行一個task線程,因此每個Executor進程的CPU core數量越多,越能夠快速地執行完分配給自己的所有task線程。

參數調優建議:Executor的CPU core數量設置為2~4個較為合適。同樣得根據不同部門的資源隊列來定,可以看看自己的資源隊列的***CPU core限制是多少,再依據設置的Executor數量,來決定每個Executor進程可以分配到幾個CPU core。同樣建議,如果是跟他人共享這個隊列,那么num-executors * executor-cores不要超過隊列總CPU core的1/3~1/2左右比較合適,也是避免影響其他同學的作業運行。

driver-memory

參數說明:該參數用于設置Driver進程的內存。

參數調優建議:Driver的內存通常來說不設置,或者設置1G左右應該就夠了。唯一需要注意的一點是,如果需要使用collect算子將RDD的數據全部拉取到Driver上進行處理,那么必須確保Driver的內存足夠大,否則會出現OOM內存溢出的問題。

spark.default.parallelism

參數說明:該參數用于設置每個stage的默認task數量。這個參數極為重要,如果不設置可能會直接影響你的Spark作業性能。

參數調優建議:Spark作業的默認task數量為500~1000個較為合適。很多同學常犯的一個錯誤就是不去設置這個參數,那么此時就會導致Spark自己根據底層HDFS的block數量來設置task的數量,默認是一個HDFS block對應一個task。通常來說,Spark默認設置的數量是偏少的(比如就幾十個task),如果task數量偏少的話,就會導致你前面設置好的Executor的參數都前功盡棄。試想一下,無論你的Executor進程有多少個,內存和CPU有多大,但是task只有1個或者10個,那么90%的Executor進程可能根本就沒有task執行,也就是白白浪費了資源!因此Spark官網建議的設置原則是,設置該參數為num-executors * executor-cores的2~3倍較為合適,比如Executor的總CPU core數量為300個,那么設置1000個task是可以的,此時可以充分地利用Spark集群的資源。

spark.storage.memoryFraction

參數說明:該參數用于設置RDD持久化數據在Executor內存中能占的比例,默認是0.6。也就是說,默認Executor 60%的內存,可以用來保存持久化的RDD數據。根據你選擇的不同的持久化策略,如果內存不夠時,可能數據就不會持久化,或者數據會寫入磁盤。

參數調優建議:如果Spark作業中,有較多的RDD持久化操作,該參數的值可以適當提高一些,保證持久化的數據能夠容納在內存中。避免內存不夠緩存所有的數據,導致數據只能寫入磁盤中,降低了性能。但是如果Spark作業中的shuffle類操作比較多,而持久化操作比較少,那么這個參數的值適當降低一些比較合適。此外,如果發現作業由于頻繁的gc導致運行緩慢(通過spark web ui可以觀察到作業的gc耗時),意味著task執行用戶代碼的內存不夠用,那么同樣建議調低這個參數的值。

spark.shuffle.memoryFraction

參數說明:該參數用于設置shuffle過程中一個task拉取到上個stage的task的輸出后,進行聚合操作時能夠使用的Executor內存的比例,默認是0.2。也就是說,Executor默認只有20%的內存用來進行該操作。shuffle操作在進行聚合時,如果發現使用的內存超出了這個20%的限制,那么多余的數據就會溢寫到磁盤文件中去,此時就會極大地降低性能。

參數調優建議:如果Spark作業中的RDD持久化操作較少,shuffle操作較多時,建議降低持久化操作的內存占比,提高shuffle操作的內存占比比例,避免shuffle過程中數據過多時內存不夠用,必須溢寫到磁盤上,降低了性能。此外,如果發現作業由于頻繁的gc導致運行緩慢,意味著task執行用戶代碼的內存不夠用,那么同樣建議調低這個參數的值。

資源參數的調優,沒有一個固定的值,需要同學們根據自己的實際情況(包括Spark作業中的shuffle操作數量、RDD持久化操作數量以及spark web ui中顯示的作業gc情況),同時參考本篇文章中給出的原理以及調優建議,合理地設置上述參數。

資源參數參考示例

以下是一份spark-submit命令的示例,大家可以參考一下,并根據自己的實際情況進行調節: 

  1. ./bin/spark-submit \ 
  2.   --master yarn-cluster \ 
  3.   --num-executors 100 \ 
  4.   --executor-memory 6G \ 
  5.   --executor-cores 4 \ 
  6.   --driver-memory 1G \ 
  7.   --conf spark.default.parallelism=1000 \ 
  8.   --conf spark.storage.memoryFraction=0.5 \ 
  9.   --conf spark.shuffle.memoryFraction=0.3 \ 

寫在***的話

根據實踐經驗來看,大部分Spark作業經過本次基礎篇所講解的開發調優與資源調優之后,一般都能以較高的性能運行了,足以滿足我們的需求。但是在不同的生產環境和項目背景下,可能會遇到其他更加棘手的問題(比如各種數據傾斜),也可能會遇到更高的性能要求。為了應對這些挑戰,需要使用更高級的技巧來處理這類問題。在后續的《Spark性能優化指南——高級篇》中,我們會詳細講解數據傾斜調優以及Shuffle調優。

責任編輯:未麗燕 來源: 美團技術團隊
相關推薦

2017-10-11 15:50:18

光纖通信傳輸

2011-05-20 15:02:01

Oracle性能調優

2017-12-07 15:28:36

2012-09-29 10:29:56

.Net內存分配繼承

2012-09-29 09:22:24

.NETGC內存分配

2020-02-28 14:05:00

Linuxshell命令

2017-12-07 15:47:25

2012-06-27 09:36:37

軟件開發軟件開發

2023-12-27 18:10:05

PyTorch工具框架

2021-12-26 00:03:25

Spark性能調優

2021-03-04 08:39:21

SparkRDD調優

2021-10-29 08:44:22

推拉機制面試broker

2024-03-29 13:17:03

Docker數據卷Volume

2021-03-01 07:34:42

Java泛型ArrayList

2015-07-23 10:37:13

Linux命令

2011-11-30 09:09:13

王濤Windows Pho移動開發

2015-06-29 09:40:10

Rails新特性

2022-09-14 22:58:58

Push 推薦Java 開發vivo

2019-05-30 08:25:50

5G4G網絡

2012-11-05 09:19:37

點贊
收藏

51CTO技術棧公眾號

一区二区在线视频观看| 成人区精品一区二区不卡| 亚洲欧美日韩综合国产aⅴ| 亚洲欧洲xxxx| 五月天婷婷影视| 肉肉视频在线观看| 2020国产精品久久精品美国| 国产精品入口福利| 国产亚洲精品成人| 国产一区二区三区天码| 91精品麻豆日日躁夜夜躁| 精品久久久久久久久久中文字幕| 国产亚洲依依| 成人晚上爱看视频| 国产美女久久久| 91久久国产视频| 91精品国产91久久综合| 日韩精品免费电影| 手机在线观看日韩av| 性感女国产在线| 亚洲欧美日韩系列| 神马影院一区二区三区| 深夜福利视频网站| 国产精品一区二区在线播放| 国产不卡av在线免费观看| 欧美黄色一级网站| 欧美gayvideo| 国产亚洲精品va在线观看| 亚洲熟女一区二区三区| 亚洲精品乱码日韩| 在线一区二区三区四区五区| 国产精品12345| 黄色成人在线观看| 国产精品色噜噜| 欧美精品一区二区三区在线看午夜 | 综合亚洲深深色噜噜狠狠网站| 国产亚洲欧美一区二区三区| 国产精品免费无遮挡| 视频一区二区欧美| 91av在线国产| 国产真人真事毛片| 欧美一区精品| 久久精品一区中文字幕| 摸摸摸bbb毛毛毛片| 日韩av中文字幕一区| 欧美一级欧美三级在线观看| 五月婷婷之婷婷| 日本综合视频| 在线精品视频免费播放| 黄在线观看网站| 中文字幕高清在线播放| 精品国产乱码久久久久久虫虫漫画| 国产一二三四五| 国产一区久久精品| 亚洲欧美一区二区三区久本道91| 亚洲午夜精品久久| 毛片免费不卡| 综合久久久久久| 欧美另类videos| 亚洲淫性视频| 一区二区三区不卡在线观看| 四虎4hu永久免费入口| 国产在线观看a视频| 亚洲欧美色一区| 996这里只有精品| 牛牛精品在线视频| 亚洲高清中文字幕| 成人综合视频在线| 国产精品高清乱码在线观看| 色素色在线综合| 亚洲色图久久久| 亚洲男人在线| 日韩一区二区在线观看视频播放| 国产裸体视频网站| 牛牛视频精品一区二区不卡| 日韩精品免费视频| 99精品全国免费观看| 久久亚洲在线| 欧美另类在线观看| 国产精品suv一区二区三区| 久久久精品五月天| 国产欧美日韩精品丝袜高跟鞋| 99久久精品日本一区二区免费| 粉嫩一区二区三区性色av| 国产综合色一区二区三区| 国产在线观看黄| 亚洲三级理论片| 很污的网站在线观看| 日韩欧美看国产| 欧美麻豆精品久久久久久| 在线成人免费av| 欧美人妖视频| 色七七影院综合| 欧美成欧美va| 水野朝阳av一区二区三区| 成人av在线天堂| 欧美一级一区二区三区| 国产欧美精品一区二区色综合| gogogo免费高清日本写真| 成人福利电影| 欧美日韩一区二区不卡| 911亚洲精选| 欧美一区二区三区高清视频| 欧美日本高清一区| 波多野结衣日韩| 成人在线综合网站| 小说区图片区图片区另类灬| 免费毛片在线看片免费丝瓜视频 | 欧美激情一级二级| 日韩精品在线一区二区三区| 国产精品综合在线视频| 欧美日韩在线高清| heyzo高清国产精品| 精品视频1区2区3区| 国产视频久久久久久| 亚洲成人精品| 国产精品狠色婷| 日韩有码第一页| 亚洲免费成人av| 最近中文字幕一区二区| 欧美午夜18电影| 久精品免费视频| 一区二区美女视频| 久久久精品日韩欧美| 国产九色porny| 精品国产第一国产综合精品| 亚洲一级片在线看| 亚洲日本韩国在线| 懂色av一区二区夜夜嗨| 中文字幕99| 成人天堂yy6080亚洲高清| 欧美精品一区二区在线播放| 少妇视频在线播放| 久久九九精品| 欧美一级爽aaaaa大片| yellow在线观看网址| 精品少妇一区二区三区日产乱码| 亚洲伦理一区二区三区| 蜜臀精品久久久久久蜜臀| 欧美日韩最好看的视频| 自拍网站在线观看| 亚洲精品99久久久久中文字幕| 精品无码久久久久成人漫画| 开心九九激情九九欧美日韩精美视频电影 | 麻豆成人入口| 亚洲**2019国产| 神宫寺奈绪一区二区三区| 亚洲国产视频a| 欧美xxxx日本和非洲| 欧美日韩国产欧| 成人综合av网| 黄污视频在线观看| 精品国产91洋老外米糕| 久久精品性爱视频| 97精品超碰一区二区三区| 欧美亚洲另类色图| 深爱激情综合网| 国产精品成人va在线观看| 奇米影视888狠狠狠777不卡| 色琪琪一区二区三区亚洲区| 日韩黄色中文字幕| 激情深爱一区二区| 97超碰在线视| 精品国产导航| 日本一本a高清免费不卡| 九九九伊在人线综合| 欧美在线一二三四区| 亚洲一级理论片| 国模大尺度一区二区三区| 日本福利视频在线观看| 国产成人精品亚洲线观看| 2020久久国产精品| 久草福利在线视频| 91精品中文字幕一区二区三区| 欧美极品aaaaabbbbb| 99精品久久只有精品| www黄色在线| 在线看片不卡| 激情久久av| jizzjizz少妇亚洲水多| 欧美精品情趣视频| 欧美xxx.com| 777久久久精品| 超碰中文字幕在线| 国产精品久久久久久久午夜片| 在线观看一区二区三区视频| 在线播放日韩| 亚洲综合第一| 精品精品国产毛片在线看| 日本久久精品视频| caoporn97在线视频| 日韩激情视频在线| 国产在成人精品线拍偷自揄拍| 亚洲国产成人porn| 亚洲色图100p| 99re热视频这里只精品| 久久久久久久久久一区| 亚洲激情av| 影音先锋欧美资源| 天海翼亚洲一区二区三区| 国产自摸综合网| 在线天堂资源| 九九热这里只有在线精品视| 成人在线视频成人| 欧美www视频| 亚洲天堂av片| 亚洲色图视频网站| 中文字幕在线观看免费高清| 成人免费毛片高清视频| 国产精品视频中文字幕| 久久国产精品亚洲77777| www.99riav| re久久精品视频| 久久精品日产第一区二区三区| 成人黄色理论片| 国产成+人+综合+亚洲欧美丁香花| 影音先锋在线视频| 日韩性生活视频| 蜜桃视频在线观看视频| 精品av久久707| 国产伦子伦对白视频| 91国偷自产一区二区三区观看| 久久精品欧美一区二区| 亚洲丝袜另类动漫二区| 无码人妻精品一区二区中文| 成人黄色在线视频| 爱情岛论坛亚洲自拍| 蜜臀av国产精品久久久久| 女人扒开屁股爽桶30分钟| 欧美日韩99| 亚洲色婷婷久久精品av蜜桃| 手机在线电影一区| 日韩欧美在线观看强乱免费| 婷婷综合电影| 精品久久久久久综合日本| 综合激情网...| 亚洲在线观看视频网站| 亚洲精品乱码日韩| 国产精品综合不卡av| 国产成人免费| 国产欧美日韩精品专区| 精品自拍视频| 国产精品亚洲欧美导航| 国产成人午夜性a一级毛片| 国产成人精品视频在线| 春暖花开亚洲一区二区三区| 欧洲成人免费视频| 激情开心成人网| 国产精品看片资源| 欧美亚洲人成在线| 91精品久久久久久久久久另类 | 国产高清在线一区二区| 视频二区欧美| 成人三级视频在线观看一区二区| 视频精品二区| 精品一区二区三区日本| 欧美人体视频| 日本免费高清不卡| 日韩国产专区| 日本一二三区视频在线| 亚洲婷婷免费| 日本a级片免费观看| 久久性天堂网| 亚洲精品综合在线观看| 国产精品亚洲午夜一区二区三区| 岛国av免费观看| 91女神在线视频| 午夜在线观看一区| 综合色天天鬼久久鬼色| 国产无遮挡aaa片爽爽| 欧美日韩国产影院| 最新中文字幕第一页| 这里只有精品免费| 成人乱码一区二区三区 | 99热免费精品在线观看| 男人天堂1024| 麻豆视频观看网址久久| 亚洲制服在线观看| 91在线观看高清| 91麻豆制片厂| 亚洲午夜久久久| 无码人妻丰满熟妇区五十路| 欧美麻豆精品久久久久久| 黄色小视频免费观看| 一区二区欧美久久| 91麻豆一二三四在线| 国产91精品久久久久久久| 欧美成人app| av蓝导航精品导航| 视频精品在线观看| 国产精品一区在线免费观看| 在线一区欧美| 爱豆国产剧免费观看大全剧苏畅| 成人精品在线视频观看| 男人天堂资源网| 午夜精品福利一区二区三区av | 国产在线视频你懂的| 一本一道波多野结衣一区二区| 国产美女免费视频| 亚洲欧美综合图区| 美足av综合网| 国产日韩在线看片| 亚洲另类春色校园小说| 一级特黄妇女高潮| 首页国产欧美久久| www.四虎在线| 亚洲欧洲精品成人久久奇米网| 99精品视频99| 欧美一区二区成人| 97人人在线| 青青草国产精品一区二区| 日韩三级网址| 亚洲自拍偷拍二区| 日韩精品每日更新| 人妻在线日韩免费视频| 亚洲猫色日本管| 中文字幕观看视频| 亚洲男人天堂手机在线| 男女羞羞视频在线观看| 国产精品网红福利| 久久成人福利| 国产精品无码免费专区午夜| 久久精品国产一区二区三 | 中文字幕永久有效| 久久久久久久久免费| 尤物视频在线观看国产| 日韩精品专区在线| 在线视频国产区| 91九色综合久久| 色综合天天爱| 欧美精品久久久久久久久25p| 2欧美一区二区三区在线观看视频 337p粉嫩大胆噜噜噜噜噜91av | 国产成人一二片| 日本免费黄色小视频| 精品一区二区三区久久| 黑人と日本人の交わりビデオ| 色94色欧美sute亚洲线路一久| 免费a级片在线观看| 欧美精品18videosex性欧美| 最新精品在线| 成人性生活视频免费看| 福利一区二区在线观看| 久久精品www| 精品国产三级电影在线观看| 污污的视频在线观看| 99高清视频有精品视频| 欧美69wwwcom| 亚洲精品无码一区二区| 亚洲aaa精品| 日韩欧美在线番号| 日本精品视频网站| 日韩毛片视频| 四虎成人在线播放| 亚洲综合成人在线| 手机av免费在线观看| 久久人人爽人人| 美女久久99| 高清一区在线观看| 亚洲免费观看高清完整| 亚洲免费不卡视频| 91高潮在线观看| 精品一区二区三区的国产在线观看| 爆乳熟妇一区二区三区霸乳| 国产人久久人人人人爽| 97精品人妻一区二区三区香蕉| 欧美精品在线网站| 久久97精品| www.色就是色| 亚洲欧洲中文日韩久久av乱码| 好吊色一区二区三区| 欧美中文字幕视频| 91亚洲国产成人久久精品| 特黄特色免费视频| 欧美视频在线免费看| 日韩在线资源| 国产精品久久精品国产 | 久久中文字幕av| 日本黄色www| 欧美性极品xxxx做受| 香蕉视频免费在线播放| 国产精品日韩欧美一区二区三区 | 精品视频二区| 国产欧美日韩综合精品| 激情欧美日韩| 妺妺窝人体色WWW精品| 蜜臀久久久久久久| 精品美女久久久久久免费| 中文字幕+乱码+中文| 久久福利视频导航| 美女毛片一区二区三区四区| 一级黄色录像在线观看| 亚洲成人av在线电影| 成人好色电影| 国产精品免费看一区二区三区| 日韩和欧美一区二区| 国产一级一片免费播放放a| 亚洲午夜小视频| 91精品啪在线观看国产爱臀| 青青青在线视频免费观看| 亚洲午夜成aⅴ人片|