spark core源碼分析14 參數配置

博客地址: http://blog.csdn.net/yueqian_zhu/


spark 參數詳解

spark參數配置優先級:SparkConf > CLI > spark-defaults.conf > spak-env.sh

查看Spark屬性:在 http://<driver>:4040 上的應用程序Web UI在 Environment 標籤中列出了所有的Spark屬性。

spark-defaults.conf:

(1) spark.master:實際用於指定部署模式,以及akka actor的IP/port

(2) spark.eventLog.*:用於histroyServer的配置

(3)spark.driver.memory:driver端進程的內存,默認1G


一、Shuffle 相關

1、spark.shuffle.manager(默認 sort)

HashShuffleManager,故名思義也就是在Shuffle的過程中寫數據時不做排序操作,只是將數據根據Hash的結果,將各個Reduce分區的數據寫到各自的磁盤文件中。帶來的問題就是如果Reduce分區的數量比較大的話,將會產生大量的磁盤文件。如果文件數量特別巨大,對文件讀寫的性能會帶來比較大的影響,此外由於同時打開的文件句柄數量衆多,序列化,以及壓縮等操作需要分配的臨時內存空間也可能會迅速膨脹到無法接受的地步,對內存的使用和GC帶來很大的壓力,在Executor內存比較小的情況下尤爲突出,例如Spark on Yarn模式。

SortShuffleManager(目前是默認值),在寫入分區數據的時候,首先會根據實際情況對數據採用不同的方式進行排序操作,底線是至少按照Reduce分區Partition進行排序,這樣來至於同一個Map任務Shuffle到不同的Reduce分區中去的所有數據都可以寫入到同一個外部磁盤文件中去,用簡單的Offset標誌不同Reduce分區的數據在這個文件中的偏移量。這樣一個Map任務就只需要生成一個shuffle文件,從而避免了上述HashShuffleManager可能遇到的文件數量巨大的問題

兩者的性能比較,取決於內存,排序,文件操作等因素的綜合影響。

對於不需要進行排序的Shuffle操作來說,如repartition等,如果文件數量不是特別巨大,HashShuffleManager面臨的內存問題不大,而SortShuffleManager需要額外的根據Partition進行排序,顯然HashShuffleManager的效率會更高。

而對於本來就需要在Map端進行排序的Shuffle操作來說,如ReduceByKey等,使用HashShuffleManager雖然在寫數據時不排序,但在其它的步驟中仍然需要排序,而SortShuffleManager則可以將寫數據和排序兩個工作合併在一起執行,因此即使不考慮HashShuffleManager的內存使用問題,SortShuffleManager依舊可能更快。

2、spark.shuffle.sort.bypassMergeThreshold

這個參數僅適用於SortShuffleManager,如前所述,SortShuffleManager在處理不需要排序的Shuffle操作時,由於排序帶來性能的下降。這個參數決定了在這種情況下,當Reduce分區的數量小於多少的時候,在SortShuffleManager內部不使用Merge Sort的方式處理數據,而是與Hash Shuffle類似,直接將分區文件寫入單獨的文件,不同的是,在最後一步還是會將這些文件合併成一個單獨的文件。這樣通過去除Sort步驟來加快處理速度,代價是需要併發打開多個文件,所以內存消耗量增加,本質上是相對HashShuffleMananger一個折衷方案。 這個參數的默認值是200個分區,如果內存GC問題嚴重,可以降低這個值。

3、spark.shuffle.consolidateFiles(默認 false)

這個配置參數僅適用於HashShuffleMananger的實現,同樣是爲了解決生成過多文件的問題,採用的方式是在不同批次運行的Map任務之間重用Shuffle輸出文件,也就是說合並的是不同批次的Map任務的輸出數據,但是每個Map任務所需要的文件還是取決於Reduce分區的數量,因此,它並不減少同時打開的輸出文件的數量,因此對內存使用量的減少並沒有幫助。只是HashShuffleManager裏的一個折中的解決方案。

需要注意的是,這部分的代碼實現儘管原理上說很簡單,但是涉及到底層具體的文件系統的實現和限制等因素,例如在併發訪問等方面,需要處理的細節很多,因此一直存在着這樣那樣的bug或者問題,導致在例如EXT3上使用時,特定情況下性能反而可能下降,因此從Spark 0.8的代碼開始,一直到Spark 1.1的代碼爲止也還沒有被標誌爲Stable,不是默認採用的方式。此外因爲並不減少同時打開的輸出文件的數量,因此對性能具體能帶來多大的改善也取決於具體的文件數量的情況。所以即使你面臨着Shuffle文件數量巨大的問題,這個配置參數是否使用,在什麼版本中可以使用,也最好還是實際測試以後再決定。

如果爲true,在shuffle時就合併中間文件,對於有大量Reduce任務的shuffle來說,合併文件可 以提高文件系統性能,如果使用的是ext4 或 xfs 文件系統,建議設置爲true;對於ext3,由於文件系統的限制,設置爲true反而會使內核>8的機器降低性能

4、spark.shuffle.spill(默認 true)

shuffle的過程中,如果涉及到排序,聚合等操作,勢必會需要在內存中維護一些數據結構,進而佔用額外的內存。如果內存不夠用怎麼辦,那只有兩條路可以走,一就是out of memory 出錯了,二就是將部分數據臨時寫到外部存儲設備中去,最後再合併到最終的Shuffle輸出文件中去。

這裏spark.shuffle.spill 決定是否Spill到外部存儲設備(默認打開),如果你的內存足夠使用,或者數據集足夠小,當然也就不需要Spill,畢竟Spill帶來了額外的磁盤操作。

如果爲true,在shuffle期間通過溢出數據到磁盤來降低了內存使用總量,溢出閾值是由spark.shuffle.memoryFraction指定的

5、spark.shuffle.memoryFraction / spark.shuffle.safetyFraction

在啓用Spill的情況下,spark.shuffle.memoryFraction(1.1後默認爲0.2)決定了當Shuffle過程中使用的內存達到總內存多少比例的時候開始Spill。

通過spark.shuffle.memoryFraction可以調整Spill的觸發條件,即Shuffle佔用內存的大小,進而調整Spill的頻率和GC的行爲。總的來說,如果Spill太過頻繁,可以適當增加spark.shuffle.memoryFraction的大小,增加用於Shuffle的內存,減少Spill的次數。當然這樣一來爲了避免內存溢出,對應的可能需要減少RDD cache佔用的內存,即減小spark.storage.memoryFraction的值,這樣RDD cache的容量減少,有可能帶來性能影響,因此需要綜合考慮。

由於Shuffle數據的大小是估算出來的,一來爲了降低開銷,並不是每增加一個數據項都完整的估算一次,二來估算也會有誤差,所以實際暫用的內存可能比估算值要大,這裏spark.shuffle.safetyFraction(默認爲0.8)用來作爲一個保險係數,降低實際Shuffle使用的內存閥值,增加一定的緩衝,降低實際內存佔用超過用戶配置值的概率。

6、spark.shuffle.spill.compress / spark.shuffle.compress(true)

這兩個配置參數都是用來設置Shuffle過程中是否使用壓縮算法對Shuffle數據進行壓縮,前者針對Spill的中間數據,後者針對最終的shuffle輸出文件,默認都是True

理論上說,spark.shuffle.compress設置爲True通常都是合理的,因爲如果使用千兆以下的網卡,網絡帶寬往往最容易成爲瓶頸。此外,目前的Spark任務調度實現中,以Shuffle劃分Stage,下一個Stage的任務是要等待上一個Stage的任務全部完成以後才能開始執行,所以shuffle數據的傳輸和CPU計算任務之間通常不會重疊,這樣Shuffle數據傳輸量的大小和所需的時間就直接影響到了整個任務的完成速度。但是壓縮也是要消耗大量的CPU資源的,所以打開壓縮選項會增加Map任務的執行時間,因此如果在CPU負載的影響遠大於磁盤和網絡帶寬的影響的場合下,也可能將spark.shuffle.compress 設置爲False纔是最佳的方案.如果壓縮將使用spark.io.compression.codec。

對於spark.shuffle.spill.compress而言,情況類似,但是spill數據不會被髮送到網絡中,僅僅是臨時寫入本地磁盤,而且在一個任務中同時需要執行壓縮和解壓縮兩個步驟,所以對CPU負載的影響會更大一些,而磁盤帶寬(如果標配12HDD的話)可能往往不會成爲Spark應用的主要問題,所以這個參數相對而言,或許更有機會需要設置爲False。如果壓縮將使用spark.io.compression.codec。

總之,Shuffle過程中數據是否應該壓縮,取決於CPU/DISK/NETWORK的實際能力和負載,應該綜合考慮。

7、spark.shuffle.file.buffer.kb(默認 100)

每個shuffle的文件輸出流內存緩衝區的大小,以KB爲單位。這些緩衝區可以減少磁盤尋道的次數,也減少創建shuffle中間文件時的系統調用

8、spark.reducer.maxMbInFlight(默認 48)

每個reduce任務同時獲取map輸出的最大大小 (以兆字節爲單位)。由於每個map輸出都需要一個緩衝區來接收它,這代表着每個 reduce 任務有固定的內存開銷,所以要設置小點,除非有很大內存


二、storage 相關

1、spark.local.dir(默認 /tmp)

這個看起來很簡單,就是Spark用於寫中間數據,如RDD Cache,Shuffle,Spill等數據的位置,那麼有什麼可以注意的?

首先,最基本的當然是我們可以配置多個路徑(用逗號分隔)到多個磁盤上增加整體IO帶寬,這個大家都知道

其次,目前的實現中,Spark是通過對文件名採用hash算法分佈到多個路徑下的目錄中去,如果你的存儲設備有快有慢,比如SSD+HDD混合使用,那麼你可以通過在SSD上配置更多的目錄路徑來增大它被Spark使用的比例,從而更好地利用SSD的IO帶寬能力。當然這只是一種變通的方法,終極解決方案還是應該像目前HDFS的實現方向一樣,讓Spark能夠感知具體的存儲設備類型,針對性的使用

用於保存map輸出文件或者轉儲RDD。在Spark 1.0 及更高版本此屬性會被環境變量 SPARK_LOCAL_DIRS (Standalone、Mesos) 或 LOCAL_DIRS (YARN) 代替

2、spark.executor.memory(默認 512 m)

每個executor使用的內存大小,和性能本身當然並沒有直接的關係,但是幾乎所有運行時性能相關的內容都或多或少間接和內存大小相關。這個參數最終會被設置到Executor的JVM的heap尺寸上,對應的就是Xmx和Xms的值

理論上Executor 內存當然是多多益善,但是實際受機器配置,以及運行環境,資源共享,JVM GC效率等因素的影響,還是有可能需要爲它設置一個合理的大小。 多大算合理,要看實際情況Executor的內存基本上是Executor內部所有任務共享的,而每個Executor上可以支持的任務的數量取決於Executor所管理的CPU Core資源的多少,因此你需要了解每個任務的數據規模的大小,從而推算出每個Executor大致需要多少內存即可滿足基本的需求。

如何知道每個任務所需內存的大小呢,這個很難統一的衡量,因爲除了數據集本身的開銷,還包括算法所需各種臨時內存空間的使用,而根據具體的代碼算法等不同,臨時內存空間的開銷也不同。但是數據集本身的大小,對最終所需內存的大小還是有一定的參考意義的。

通常來說每個分區的數據集在內存中的大小,可能是其在磁盤上源數據大小的若干倍(不考慮源數據壓縮,Java對象相對於原始裸數據也還要算上用於管理數據的數據結構的額外開銷),需要準確的知道大小的話,可以將RDD cache在內存中,從BlockManager的Log輸出可以看到每個Cache分區的大小(其實也是估算出來的,並不完全準確)

如: BlockManagerInfo: Added rdd_0_1 on disk on sr438:41134 (size: 495.3 MB)

反過來說,如果你的Executor的數量和內存大小受機器物理配置影響相對固定,那麼你就需要合理規劃每個分區任務的數據規模,例如採用更多的分區,用增加任務數量(進而需要更多的批次來運算所有的任務)的方式來減小每個任務所需處理的數據大小。

3、spark.storage.memoryFraction(默認 0.6)

指Java堆用於cache的比例

如前面所說spark.executor.memory決定了每個Executor可用內存的大小,而spark.storage.memoryFraction則決定了在這部分內存中有多少可以用於Memory Store管理RDD Cache數據,剩下的內存用來保證任務運行時各種其它內存空間的需要。

spark.executor.memory默認值爲0.6,官方文檔建議這個比值不要超過JVM Old Gen區域的比值。這也很容易理解,因爲RDD Cache數據通常都是長期駐留內存的,理論上也就是說最終會被轉移到Old Gen區域(如果該RDD還沒有被刪除的話),如果這部分數據允許的尺寸太大,勢必把Old Gen區域佔滿,造成頻繁的FULL GC。

如何調整這個比值,取決於你的應用對數據的使用模式和數據的規模,粗略的來說,如果頻繁發生Full GC,可以考慮降低這個比值,這樣RDD Cache可用的內存空間減少(剩下的部分Cache數據就需要通過Disk Store寫到磁盤上了),會帶來一定的性能損失,但是騰出更多的內存空間用於執行任務,減少Full GC發生的次數,反而可能改善程序運行的整體性能

4、spark.streaming.blockInterval(默認 200)

這個參數用來設置Spark Streaming裏Stream Receiver生成Block的時間間隔,默認爲200ms。具體的行爲表現是具體的Receiver所接收的數據,每隔這裏設定的時間間隔,就從Buffer中生成一個StreamBlock放進隊列,等待進一步被存儲到BlockManager中供後續計算過程使用。理論上來說,爲了每個Streaming Batch 間隔裏的數據是均勻的,這個時間間隔當然應該能被Batch的間隔時間長度所整除。總體來說,如果內存大小夠用,Streaming的數據來得及處理,這個blockInterval時間間隔的影響不大,當然,如果數據Cache Level是Memory+Ser,即做了序列化處理,那麼BlockInterval的大小會影響序列化後數據塊的大小,對於Java 的GC的行爲會有一些影響。

此外spark.streaming.blockQueueSize決定了在StreamBlock被存儲到BlockMananger之前,隊列中最多可以容納多少個StreamBlock。默認爲10,因爲這個隊列Poll的時間間隔是100ms,所以如果CPU不是特別繁忙的話,基本上應該沒有問題。

5、spark.storage.memoryMapThreshold(默認 2m)

以字節爲單位的塊大小,用於磁盤讀取一個塊大小時進行內存映射。這可以防止Spark在內存映射時使用很小塊,一般情況下,對塊進行內存映射的開銷接近或低於操作系統的頁大小


三、壓縮及序列化相關

1、spark.serializer(默認JavaSerializer)

默認爲org.apache.spark.serializer.JavaSerializer,官方建議使用org.apache.spark.serializer.KryoSerializer,當然也可以任意是定義爲org.apache.spark.Serializer子類的序化器,實際上只要是org.apache.spark.serializer的子類就可以了,不過如果只是應用,大概你不會自己去實現一個的。

序列化對於spark應用的性能來說,還是有很大影響的,在特定的數據格式的情況下,KryoSerializer的性能可以達到JavaSerializer的10倍以上,當然放到整個Spark程序中來考量,比重就沒有那麼大了,但是以Wordcount爲例,通常也很容易達到30%以上的性能提升。而對於一些Int之類的基本類型數據,性能的提升就幾乎可以忽略了。KryoSerializer依賴Twitter的Chill庫來實現,相對於JavaSerializer,主要的問題在於不是所有的Java Serializable對象都能支持。

需要注意的是,這裏可配的Serializer針對的對象是Shuffle數據,以及RDD Cache等場合,而Spark Task的序列化是通過spark.closure.serializer來配置,但是目前只支持JavaSerializer,所以等於沒法配置啦。

2、spark.rdd.compress(默認 false)

這個參數決定了RDD Cache的過程中,RDD數據在序列化之後是否進一步進行壓縮再儲存到內存或磁盤上。當然是爲了進一步減小Cache數據的尺寸,對於Cache在磁盤上而言,絕對大小大概沒有太大關係,主要是考慮Disk的IO帶寬。而對於Cache在內存中,那主要就是考慮尺寸的影響,是否能夠Cache更多的數據,是否能減小Cache數據對GC造成的壓力等。

這兩者,前者通常不會是主要問題,尤其是在RDD Cache本身的目的就是追求速度,減少重算步驟,用IO換CPU的情況下。而後者,GC問題當然是需要考量的,數據量小,佔用空間少,GC的問題大概會減輕,但是是否真的需要走到RDD Cache壓縮這一步,或許用其它方式來解決可能更加有效。

所以這個值默認是關閉的,但是如果在磁盤IO的確成爲問題或者GC問題真的沒有其它更好的解決辦法的時候,可以考慮啓用RDD壓縮。

3、spark.broadcast.compress(默認 true)

是否在發送之前壓縮廣播變量,默認值爲True。

Broadcast機制是用來減少運行每個Task時,所需要發送給TASK的RDD所使用到的相關數據的尺寸,一個Executor只需要在第一個Task啓動時,獲得一份Broadcast數據,之後的Task都從本地的BlockManager中獲取相關數據。在1.1最新版本的代碼中,RDD本身也改爲以Broadcast的形式發送給Executor(之前的實現RDD本身是隨每個任務發送的),因此基本上不太需要顯式的決定哪些數據需要broadcast了。

因爲Broadcast的數據需要通過網絡發送,而在Executor端又需要存儲在本地BlockMananger中,加上最新的實現,默認RDD通過Boradcast機制發送,因此大大增加了Broadcast變量的比重,所以通過壓縮減小尺寸,來減少網絡傳輸開銷和內存佔用,通常都是有利於提高整體性能的。

什麼情況可能不壓縮更好呢,大致上個人覺得同樣還是在網絡帶寬和內存不是問題的時候,如果Driver端CPU資源很成問題(畢竟壓縮的動作基本都在Driver端執行),那或許有調整的必要。

4、spark.io.compression.codec(默認LZFCompressionCodec)

RDD Cache和Shuffle數據壓縮所採用的算法Codec,默認值曾經是使用LZF作爲默認Codec,最近因爲LZF的內存開銷的問題,默認的Codec已經改爲Snappy。

LZF和Snappy相比較,前者壓縮率比較高(當然要看具體數據內容了,通常要高20%左右),但是除了內存問題以外,CPU代價也大一些(大概也差20%~50%?)

在用於Shuffle數據的場合下,內存方面,應該主要是在使用HashShuffleManager的時候有可能成爲問題,因爲如果Reduce分區數量巨大,需要同時打開大量的壓縮數據流用於寫文件,進而在Codec方面需要大量的buffer。但是如果使用SortShuffleManager,由於shuffle文件數量大大減少,不會產生大量的壓縮數據流,所以內存開銷大概不會成爲主要問題。

剩下的就是CPU和壓縮率的權衡取捨,和前面一樣,取決於CPU/網絡/磁盤的能力和負載,個人認爲CPU通常更容易成爲瓶頸。所以要調整性能,要不不壓縮,要不使用Snappy可能性大一些?

對於RDD Cache的場合來說,絕大多數場合都是內存操作或者本地IO,所以CPU負載的問題可能比IO的問題更加突出,這也是爲什麼 spark.rdd.compress 本身默認爲不壓縮,如果要壓縮,大概也是Snappy合適一些?


四、運行時相關

1、spark.default.parallelism

(本地模式:機器核數,其他:max(executor的core,2))

2、spark.broadcast.factory(默認 HttpBroadcastFactory)

3、spark.broadcast.blockSize(默認 4096)

TorrentBroadcastFactory塊大小(以kb爲單位)。過大會降低廣播速度;過小會使印象BlockManager性能

4、spark.task.cpus(默認 1)

爲每個任務分配的內核數

5、spark.task.maxFailures(默認 4)

Task的最大重試次數

6、spark.cores.max

當應用程序運行在Standalone集羣或者粗粒度共享模式Mesos集羣時,應用程序向集羣請求的最大CPU內 核總數(不是指每臺機器,而是整個集羣)。如果不設置,對於Standalone集羣將使用spark.deploy.defaultCores中數值, 而Mesos將使用集羣中可用的內核

7、spark.speculation(默認 false)

此參數設定是否使用推測執行機制,如果設置爲true則spark使用推測執行機制,對於Stage中拖後腿的Task在其他節點中重新啓動,並將最先完成的Task的計算結果最爲最終結果

8、spark.speculation.interval(默認 100)

Spark多長時間進行檢查task運行狀態用以推測,以毫秒爲單位

9、spark.speculation.quantile(默認 0.75)

推測啓動前,Stage必須要完成總Task的百分比

10、spark.speculation.multiplier(默認 1.5)

比已完成Task的運行速度中位數慢多少倍才啓用推測

11、spark.scheduler.revive.interval(默認 1000)

復活重新獲取資源的Task的最長時間間隔(毫秒),發生在Task因爲本地資源不足而將資源分配給其他Task運行後進入等待時間,如果這個等待時間內重新獲取足夠的資源就繼續計算

12、spark.deploy.spreadOut(默認 true)

Standalone集羣管理器是否自由選擇節點還是固定到儘可能少的節點,前者會有更好的數據本地性,後者對於計算密集型工作負載更有效

13、spark.deploy.defaultCores

如果沒有設置spark.cores.max,該參數設置Standalone集羣分配給應用程序的最大內核數,如果不設置,應用程序獲取所有的有效內核。注意在一個共享的集羣中,設置一個低值防止攫取了所有的內核,影響他人的使用


五、HistoryServer相關

1、spark.history.updateInterval(默認 10)

以秒爲單位,更新日誌相關信息的時間間隔

2、spark.history.retainedApplications(250)

保存Application歷史記錄的個數,如果超過這個值,舊的應用程序信息將被刪除

3、spark.history.ui.port(默認 18080)

HistoryServer的web端口

4、spark.eventLog.enabled(默認 false)

是否記錄Spark事件

5、spark.eventLog.dir

保存日誌相關信息的路徑,可以是hdfs://開頭的HDFS路徑,也可以是file://開頭的本地路徑,都需要提前創建

6、spark.yarn.historyServer.address

Server端的URL:Ip:port 或者host:port


六、spark-submit工具相關

其位於$SPARK_HOME/bin目錄中

Usage: spark-submit [options] <app jar | python file> [app options]

參數名稱

含義

--master MASTER_URL

可以是spark://host:port, mesos://host:port, yarn, yarn-cluster,yarn-client, local

--deploy-mode DEPLOY_MODE

Driver程序運行的地方,client或者cluster

--class CLASS_NAME

主類名稱,含包名

--name NAME

Application名稱

--jars JARS

Driver依賴的第三方jar包

--py-files PY_FILES

用逗號隔開的放置在Python應用程序PYTHONPATH上的.zip, .egg, .py文件列表

--files FILES

 用逗號隔開的要放置在每個executor工作目錄的文件列表

--properties-file FILE

設置應用程序屬性的文件路徑,默認是conf/spark-defaults.conf

--driver-memory MEM

Driver程序使用內存大小

--driver-java-options


--driver-library-path

Driver程序的庫路徑

--driver-class-path

Driver程序的類路徑

--executor-memory MEM

executor內存大小,默認1G

--driver-cores NUM

Driver程序的使用CPU個數,僅限於Spark Alone模式

--supervise

失敗後是否重啓Driver,僅限於Spark Alone模式

--total-executor-cores NUM

executor使用的總核數,僅限於Spark Alone、Spark on Mesos模式

--executor-cores NUM

每個executor使用的內核數,默認爲1,僅限於Spark on Yarn模式

--queue QUEUE_NAME

提交應用程序給哪個YARN的隊列,默認是default隊列,僅限於Spark on Yarn模式

--num-executors NUM

啓動的executor數量,默認是2個,僅限於Spark on Yarn模式,standalone下默認在當前可用的worker上都開啓一個Executor

--archives ARCHIVES

僅限於Spark on Yarn模式


引用:

http://spark-config.readthedocs.org/en/latest/

http://my.oschina.net/mkh/blog/300389#OSC_h2_13


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