Spark優化總結(三)——調參
前言
- 不要期待修改一個參數能夠像魔法一樣立馬得到神奇的好效果!(某些時候效果確實很棒^_^)你應當把參數看作一道菜中的調味品,能夠豐富味道,但主要還是得靠原材料的質量與炒菜的技藝。
- 開發Spark應用時,應當先優化好你的應用代碼,再來思考調參優化(必要的參數的除外)。
- 調參是一個比較複雜的主題,不同的環境、不同的代碼都會導致同樣的參數產生不同的效果。建議儘量在確定您的生產環境情況後、在優化好存在明顯問題的代碼後,再做調參測試。
- 下面會列出開發中常用的部分參數,並加以解釋,以作參考
簡單示例
-
一個Spark任務提交示例
spark-submit \ --queue test_queue --master yarn --deploy-mode cluster \ --num-executors 10 --executor-memory 8G --executor-cores 4 \ --driver-memory 4G --driver-cores 2 \ --conf spark.network.timeout=300 \ --conf spark.locality.wait=9s \ --class com.skey.spark.app.MyApp /home/jerry/spark-demo.jar
-
參數解釋
- –queue test_queue 將任務提交到YARN上面的test_queue隊列
- –master yarn 將任務運行在YARN上面
- –deploy-mode cluster 指定cluster模式運行
- –num-executors 10 指定啓動10個Executor進程
- –executor-memory 8G 每個Executor分配8G內存
- –executor-cores 4 每個Executor分配4個core,一個core對應一個線程
- –driver-memory 4G 爲Driver進程分配4G內存
- –driver-cores 2 爲Driver分配2個core,一個core對應一個線程
- –conf spark.network.timeout=300s 設置集羣內網絡通信延遲爲300s
- –conf spark.locality.wait=9s 設置數據本地化加載等待時間
- –class com.skey.spark.app.MyApp 指定需要運行的spark應用類
- /home/jerry/spark-demo.jar 指定需要運行的jar包
常用參數
- 下面列出的參數以Spark 2.4.3的configuration爲標準
- spark.memory.useLegacyMode
- 默認值: false
- 解釋: 該參數用於控制是否使用遺留的內存管理模式(也就是鎢絲計劃之前的內存模型,即1.6版本以前)。遺留模式下,內存由spark.storage.memoryFraction(0.6) + spark.shuffle.memoryFraction(0.2) + 系統默認(0.2) 組成。
- 建議: 剛開始開發應用時,可以不開啓,直接動態分配即可。一旦開發完成,如果能夠確定每次應用的shuffle、storage量時,可以嘗試改爲true,這樣的話不用動態分配內存(需要耗時)。另外動態分配模式下execution從storage借來的內存不管還需不需要用都不會還給storage,可能會導致部分問題。
- spark.storage.memoryFraction
- 默認值: 0.6
- 解釋: spark.memory.useLegacyMode爲true時有效,用於控制緩存部分的內存
- 建議: 如果緩存數據比較大,可以調大該參數
- spark.shuffle.memoryFraction
- 默認值: 0.2
- 解釋: spark.shuffle.memoryFraction爲true時有效,用於控制shuffle部分
- 建議: 如果頻繁發生spill(溢寫),可以調大該參數。如果你的應用緩存用的很少,請使勁調大該參數^_^
- spark.storage.unrollFraction
- 默認值: 0.2
- 解釋: spark.memory.useLegacyMode爲true時有效,用於緩存的數據的序列化/反序列化,佔spark.storage.memoryFraction的20%
- 建議: 可以不調
- spark.executor.memoryOverhead
- 默認值: executorMemory * 0.10,最低384M
- 解釋: 設置每個Executor的堆外內存,主要用於JVM自身,字符串, NIO Buffer等開銷。等於 Container內存 - Executor內存。(沒在官網找到該參數)
- 建議: Spark使用的是基於nio的,nio使用直接內存區效率非常高,可以適當的分配部分內存到堆外區。
- spark.locality.wait
- 默認值: 3s
- 解釋: 用於指定數據本地化等待時長,包括3個子參數(spark.locality.wait.node、spark.locality.wait.process、spark.locality.wait.rack)用於更細緻的調節。
- 建議: 通常統一調整spark.locality.wait即可。可以適當加長等待時間,特別是數據量比較大時,如果能夠本地化處理,效果更佳。
- spark.network.timeout
- 默認值: 120s
- 解釋: spark內存通信的網絡延時
- 建議: 如果spark應用處理比較耗時,那麼可以適當調大該參數(例如300s),防止延時導致的報錯
- spark.default.parallelism
- 默認值: 無
- 解釋: 用於指定RDD的shuffle操作的分區數,例如reduceByKey、join等。調用shuffle算子時,優先使用算子指定的分區數,否則使用spark.default.parallelism的值,如果還是沒值,則使用父RDD的分區數的值。對sql中的shuffle無效。
- 建議: 如果你想精細化控制,願意爲每個shuffle算子添加parallelism值,那麼不用設置^_^。建議可以先設置一個值(例如總core的2-3倍),然後代碼中有需要調整的就向shuffle算子傳參,覆蓋本次的默認值。(不要誤信網上瞎說的什麼並行度)
- spark.sql.shuffle.partitions
- 默認值: 200
- 解釋: 用於指定SparkSQL執行shuffle時的分區數。(沒在官網找到該參數)
- 建議: SQL在執行shuffle時,如果默認200小於你的總core數,就會浪費資源了。建議設置爲總core的2-3倍。
- spark.jars
- 默認值: 無
- 解釋: 指定jar包(用逗號分隔),會將其傳到Driver、Executor端
- 建議: 除了spark lib下的包,其他額外引用的庫建議都指定。(client模式下,只有Driver會用到的庫,可以不傳)
- spark.jars.excludes
- 默認值: 無
- 解釋: 排除不要的包,防止依賴衝突。格式 groupId:artifactId
- 建議: …
- spark.executor.userClassPathFirst與spark.driver.userClassPathFirst
- 默認值: false
- 解釋: 用於指定是否優先加載用戶指定的jar
- 建議: 因爲存在用戶指定的jar與spark默認庫下jar包可能衝突的問題,所以當衝突時,如果你能確定你的jar沒問題,那麼可以設置爲true。(之前遇到一個華爲的bug就是這樣)
- spark.driver.maxResultSize
- 默認值: 1g
- 解釋: 數據傳到Driver端的最大量,例如collect、take操作
- 建議: 有時候可能需要直接拉取大量的數據,可以根據數據量來調整該參數。
- spark.reducer.maxSizeInFlight
- 默認值: 48m
- 解釋: 每個reduce任務拉取數據的最大量
- 建議: 每個輸出端都需要創建一個buffer,消耗比較大。如果內存比較大,可以提高該值,拉取速度會加快。
- spark.shuffle.file.buffer
- 默認值: 32k
- 解釋: shuffle時,數據輸出到文件的buffer大小,寫滿buffer後,數據會溢寫到磁盤。
- 建議: 調大該值,可以降低溢寫到磁盤的次數(減少I/O次數),提高性能。內存充足的話,可以調大,例如64k。
- spark.shuffle.io.maxRetries
- 默認值: 3
- 解釋: shuffle時,read端從write端拉取數據的重試次數
- 建議: 因爲GC、網絡延遲等問題可能會導致拉取失敗,可以適當提高重試次數,防止意外。
- spark.shuffle.io.retryWait
- 默認值: 5s
- 解釋: spark.shuffle.io.maxRetries每次重試需要等多久
- 建議: 可以加大,提高穩定性
- spark.shuffle.manager
- 默認值: sort
- 解釋: 用於管理shuflle文件輸出到磁盤的方式。建議百度看看詳細流程。1.2版以前默認HashShuffleManager, 之後默認是SortShuffleManager。Spark 2後不再有該參數,直接是SortShuffleManager,默認會對數據進行排序。
- 建議: 可以通過spark.shuffle.sort.bypassMergeThreshold調整是否排序。
- spark.shuffle.sort.bypassMergeThreshold
- 默認值: 200
- 解釋: shffle時,如果read task數小於200,會啓用bypass機制:不會進行排序操作,最後會合並task產生的文件,並創建索引
- 建議: 不需要排序時,可以提高該參數,以大於你的shuffle read task數。(會有不錯的效率提升,我的一個應用降低了20%時間)
- spark.kryo.registrationRequired
- 默認值: false
- 解釋: 是否強制kryo序列化
- 建議: 如果爲false,kyro需要爲每個對象寫未註冊類的類名,會造成顯著的性能開銷。建議設置爲true。(某些朋友對於JVM報出的有的類不知道怎麼註冊,在這裏建議複製報錯的類,使用Class.forName(“類”))
- spark.rdd.compress
- 默認值: false
- 解釋: 是否壓縮序列化的RDD數據
- 建議: 開啓可以減少內存,但是解壓會增加CPU消耗時間
- spark.scheduler.mode
- 默認值: FIFO
- 解釋: 同一個SparkContext內的調度機制,包括FIFO、FAIR
- 建議: 一般使用較少。FIFO,先進先出,優先執行先提交的,有空閒的再給後面的job。FAIR,公平分配資源,爲每個可以並行執行的job平均分配計算資源。
- spark.streaming.backpressure.enabled
- 默認值: false
- 解釋: 是否啓用背壓機制,控制SparkStream接收數據的速率。
- 建議: 流式處理中,處理速度如果較慢,會導致來的數據不斷積壓。啓用後,Spark可以自己動態根據處理能力調整接收數據的量。如果存在積壓情況,建議啓用。另外還有幾個參數,用於細節控制,不建議調整。
- spark.streaming.blockInterval
- 默認值: 200ms
- 解釋: 每批處理的taske數 = 批處理間隔 / blockInterval
- 建議: blockInterval越大,taske則越少,會導致部分core沒有使用。可以根據你的core的量,適當降低該參數。(官方建議blockInterval最小值約爲50ms)
- spark.streaming.concurrentJobs
- 默認值: 1
- 解釋: 一個SparkStream應用內可以同時運行多少個job。(沒在官網找到該參數)
- 建議: 如果你分配的core比較多,每批的task數比較少(還可能處理時間比較長),空閒的core比較浪費,那麼可以調高該參數,同時運行後面的job(如果2個job之間沒有前後關聯的話)
- spark.driver.extraJavaOptions與spark.executor.extraJavaOptions
- 默認值: 無
- 解釋: 用於指定JVM參數
- 建議: 看JVM調參部分
JVM調參
- 一般不要先調JVM,開發中的大多數問題都是代碼質量不好導致的,先去看代碼、業務邏輯是否有問題,優化、優化、再優化……^_^
- 然後,確定運行環境,再來調整JVM參數
- 注意,運行時默認值會隨着系統環境改變,請用
java -XX:+PrintFlagsFinal -version
命令查看JVM最終參數 - 查看GC情況
- 查看每個節點的GC
- 添加-XX:PrintGCDetails,讓每個節點打印GC日誌,需要在每個節點分別查看。On YARN的話,可以點擊WebUI界面查看每個節點日誌。
- 想看Spark應用整體的吞吐量?
- 每個應用的Spark WebUI有展示,直接在這兒看就可以了。如果GC時間超過10%,那麼說明你的應用需要優化了!
- 查看每個節點的GC
- 關於GC的選擇(Java 8)
- 批處理,需要吞吐量較高?用 -XX:+UseParallelGC
- 流式處理,需要數據具有較高的一致性?用 -XX:+UseConcMarkSweepGC 或 G1
- 琢磨不定?選 ParallelGC
- 批處理時想用G1?可以試試,但是吞吐量沒有ParallelGC好(Java 8)
- 並行垃圾收集器參數(ParallelGC)
- 以吞吐量優先爲準則
- -XX:+UseParallelGC 設置年輕代使用ParallelGC,早期版本中老年代會默認使用SerialGC。
- -XX:+UseParallelOldGC 設置老年代也使用ParallelOldGC,Java1.6後開始支持(我的系統中是默認開啓的,你的不一定)
- -XX:ParallelGCThreads=8 設置並行收集垃圾的線程數爲8,一般同CPU核個數
- -XX:MaxGCPauseMillis=100 設置ParallelGC在年輕代單次回收的最長耗時爲100毫秒。如果耗時超過該值,JVM會自動調整年輕代內存大小,以適應該值。可以調大該值,例如500,以保證吞吐量。
- -XX:GCTimeRatio=99 設置垃圾回收時間佔總時間的百分比最高爲1%,公式爲1/(1+99),即吞吐量爲99%。默認爲99。
- -XX:+UseAdaptiveSizePolicy 啓用自適應策略。JVM會自動調整年輕代Eden區與Survivor區大小的比例,以適應GCTimeRatio的值。建議開啓。
- 併發垃圾收集器參數(ConcMarkSweepGC)
- 以響應時間優先爲準則
- -XX:+UseConcMarkSweepGC 設置老年代使用ConcMarkSweepGC,年輕代默認使用ParNewGC。
- -XX:+UseParNewGC 設置年輕代爲ParNewGC。JDK5以上使用CMS時,默認年輕代會採用ParNewGC。
- -XX:+UseCMSCompactAtFullCollection 開啓壓縮整理(默認),壓縮整理用於消除內存碎片化問題(因爲CMS採用的標記清除算法,不會進行壓縮整理,所以要單獨開啓)
- -XX:CMSFullGCsBeforeCompaction=2 設置進行2次FullGc後(默認0次),開始對內存進行壓縮整理。
- -XX:CMSInitiatingOccupancyFraction=70 設置老年代內存使用70%後開始進行併發垃圾收集
- 內存調整常用參數
- 常用比例
young | old eden | s0 | s1 | tenured ------------------------ 1 | 2 8 | 1 | 1 |
- -Xmx4G 設置JVM最大堆內存爲4G
- -Xms4G 設置JVM初始堆內存爲4G,一般設爲與-Xmx同樣大即可(避免重新分配內存)
- -Xmn1G 設置年輕代內存大小爲1G。堆內存 = 老年代 + 年輕代,因此年輕代與老年代之間要根據GC情況取得一個平衡。例如MinorGC較多,可以調大年輕代,MajorGC較多可以調大老年代(即調小年輕代)。
- -XX:NewSize=512M 設置年輕代初始大小爲512M,一般設爲與MaxNewSize同樣大即可。(不建議使用,直接用-Xmn)
- -XX:MaxNewSize=1024M 設置年輕代最大大小爲1024M。(不建議使用,直接用-Xmn)
- -XX:MetaspaceSize=128M 設置元信息區(永久代)初始大小爲256M,一般設爲與MaxMetaspaceSize同樣大即可。(Java8以前叫做-XX:PermSize)
- -XX:MaxMetaspaceSize=256M 設置元信息區(永久代)最大大小爲256M。(Java8以前叫做-XX:MaxPermSize)
- -XX:NewRatio=2 設置年輕代與老年代的大小比例爲 1 : 2 (Java8默認爲2)
- -XX:SurvivorRatio=8 設置年輕代中Eden區與Survivor的大小比例爲 8 : 1 : 1 (enden : survivor0 : survivor1)
- -Xss256K 設置每個線程私有棧的的大小爲256K(Java8默認爲1M)。線程越多內存佔用越大,需要啓用超多線程時,可以調低該參數,例如128k。
- -XX:MaxDirectMemory=100M 設置最大堆外內存
- 常用比例
- 其他常用參數
- -XX:+PrintGCDetails 打印GC詳細信息
- -XX:+PrintGCTimeStamps 打印GC時的時間戳
- -Xloggc:/home/jerry/logs/gc.log 設置GC日誌輸出目錄
- -XX:+HeapDumpOnOutOfMemoryError 當發生內存溢出異常時,dump出堆信息
- -XX:HeapDumpPath=./my_java.hprof 指定dump堆信息的輸出路徑
- -XX:MaxTenuringThreshold=8 設置對象在年輕代經歷垃圾回收仍然存活8次後進入老年代。最大值15(因爲JVM用4bit表示該值),默認值15(使用CMS時,默認爲6)
- -XX:+PrintFlagsFinal 打印JVM最終參數(例如 java -XX:+PrintFlagsFinal -version)
- -XX:+PrintFlagsInitial 打印JVM初始參數(最終值會隨環境改變)