Spark學習——效能調優(二)

Hiway發表於2019-04-01

其他更多java基礎文章:
java基礎學習(目錄)


繼續上一篇Spark學習——效能調優(一)的講解

降低cache操作的記憶體佔比

關於RDD記憶體的使用,後面會專門寫一篇,可以配合學習閱讀

spark中,堆記憶體又被劃分成了兩塊兒,一塊兒是專門用來給RDD的cache、persist操作進行RDD資料快取用的;另外一塊兒,就是我們剛才所說的,用來給spark運算元函式的執行使用的,存放函式中自己建立的物件。

預設情況下,給RDD cache操作的記憶體佔比,是0.6,60%的記憶體都給了cache操作了。但是問題是,如果某些情況下,cache不是那麼的緊張,問題在於task運算元函式中建立的物件過多,然後記憶體又不太大,導致了頻繁的minor gc,甚至頻繁full gc,導致spark頻繁的停止工作。效能影響會很大。

針對上述這種情況,大家可以在spark ui介面,如果通過yarn去執行的話,那麼就通過yarn的介面,去檢視你的spark作業的執行統計。可以看到每個stage的執行情況,包括每個task的執行時間、gc時間等等。如果發現gc太頻繁,時間太長。此時就可以適當調價這個比例。

降低cache操作的記憶體佔比,大不了用persist操作,選擇將一部分快取的RDD資料寫入磁碟,或者序列化方式,配合Kryo序列化類,減少RDD快取的記憶體佔用;降低cache操作記憶體佔比;對應的,運算元函式的記憶體佔比就提升了。這個時候,可能,就可以減少minor gc的頻率,同時減少full gc的頻率。對效能的提升是有一定的幫助的。

一句話,讓task執行運算元函式時,有更多的記憶體可以使用。

SparkConf conf = new SparkConf()
  .set("spark.storage.memoryFraction", "0.5")
複製程式碼

調節executor堆外記憶體

Spark學習——效能調優(二)
有時候,如果你的spark作業處理的資料量特別特別大,幾億資料量;然後spark作業一執行,時不時的報錯,shuffle file cannot find,executor、task lost,out of memory(記憶體溢位);

可能是說executor的堆外記憶體不太夠用,導致executor在執行的過程中,可能會記憶體溢位;然後可能導致後續的stage的task在執行的時候,可能要從一些executor中去拉取shuffle map output檔案,但是executor可能已經掛掉了,關聯的block manager也沒有了;所以可能會報shuffle output file not found;resubmitting task;executor lost;spark作業徹底崩潰。

上述情況下,就可以去考慮調節一下executor的堆外記憶體。也許就可以避免報錯;此外,有時,堆外記憶體調節的比較大的時候,對於效能來說,也會帶來一定的提升。

如何調節executor堆外記憶體

--conf spark.yarn.executor.memoryOverhead=2048
複製程式碼

spark-submit指令碼里面,去用--conf的方式,去新增配置;一定要注意!!!切記,不是在你的spark作業程式碼中,用new SparkConf().set()這種方式去設定,不要這樣去設定,是沒有用的!一定要在spark-submit指令碼中去設定。

spark.yarn.executor.memoryOverhead(看名字,顧名思義,針對的是基於yarn的提交模式)

預設情況下,這個堆外記憶體上限大概是300多M;我們通常專案中,真正處理大資料的時候,這裡都會出現問題,導致spark作業反覆崩潰,無法執行;此時就會去調節這個引數,到至少1G(1024M),甚至說2G、4G

通常這個引數調節上去以後,就會避免掉某些JVM OOM的異常問題,同時呢,會讓整體spark作業的效能,得到較大的提升。

調節連線等待時長

Spark學習——效能調優(二)
如果本地block manager沒有的話,那麼會通過TransferService,去遠端連線其他節點上executor的block manager去獲取。如果正好其他節點上的executor正在GC,此時呢,就會沒有響應,無法建立網路連線;會卡住;ok,spark預設的網路連線的超時時長,是60s;如果卡住60s都無法建立連線的話,那麼就宣告失敗了。

碰到一種情況,偶爾,偶爾,偶爾!!!沒有規律!!!某某file。一串file id。uuid(dsfsfd-2342vs--sdf--sdfsd)。not found。file lost。

這種情況下,很有可能是有那份資料的executor在jvm gc。所以拉取資料的時候,建立不了連線。然後超過預設60s以後,直接宣告失敗。

報錯幾次,幾次都拉取不到資料的話,可能會導致spark作業的崩潰。也可能會導致DAGScheduler,反覆提交幾次stage。TaskScheduler,反覆提交幾次task。大大延長我們的spark作業的執行時間。

可以考慮調節連線的超時時長。

--conf spark.core.connection.ack.wait.timeout=300
複製程式碼

spark-submit指令碼,切記,不是在new SparkConf().set()這種方式來設定的。

合併map端輸出檔案

Spark學習——效能調優(二)
實際生產環境的條件:
100個節點(每個節點一個executor):100個executor
每個executor:2個cpu core
總共1000個task:每個executor平均10個task
每個節點,10個task,每個節點會輸出多少份map端檔案?10 * 1000=1萬個檔案
總共有多少份map端輸出檔案?100 * 10000 = 100萬。

  • shuffle中的寫磁碟的操作,基本上就是shuffle中效能消耗最為嚴重的部分。 通過上面的分析,一個普通的生產環境的spark job的一個shuffle環節,會寫入磁碟100萬個檔案。 磁碟IO對效能和spark作業執行速度的影響,是極其驚人和嚇人的。 基本上,spark作業的效能,都消耗在shuffle中了,雖然不只是shuffle的map端輸出檔案這一個部分,但是這裡也是非常大的一個效能消耗點。

開啟map端輸出檔案的合併機制

Spark學習——效能調優(二)
通過一下命令可以開啟map端輸出檔案的合併機制

new SparkConf().set("spark.shuffle.consolidateFiles", "true")
複製程式碼

如上圖:

  • 第一個stage,同時就執行cpu core個task,比如cpu core是2個,並行執行2個task;每個task都建立下一個stage的task數量個檔案;
  • 第一個stage,並行執行的2個task執行完以後;就會執行另外兩個task;另外2個task不會再重新建立輸出檔案;而是複用之前的task建立的map端輸出檔案,將資料寫入上一批task的輸出檔案中。
  • 第二個stage,task在拉取資料的時候,就不會去拉取上一個stage每一個task為自己建立的那份輸出檔案了;而是拉取少量的輸出檔案,每個輸出檔案中,可能包含了多個task給自己的map端輸出。

合併map端輸出檔案後,上面的例子會有什麼改變呢?

  1. map task寫入磁碟檔案的IO,減少:100萬檔案 -> 20萬檔案
  2. 第二個stage,原本要拉取第一個stage的task數量份檔案,1000個task,第二個stage的每個task,都要拉取1000份檔案,走網路傳輸;合併以後,100個節點,每個節點2個cpu core,第二個stage的每個task,主要拉取100 * 2 = 200個檔案即可;網路傳輸的效能消耗是不是也大大減少

提醒一下(map端輸出檔案合併):
只有並行執行的task會去建立新的輸出檔案;下一批並行執行的task,就會去複用之前已有的輸出檔案;但是有一個例外,比如2個task並行在執行,但是此時又啟動要執行2個task;那麼這個時候的話,就無法去複用剛才的2個task建立的輸出檔案了;而是還是隻能去建立新的輸出檔案。

要實現輸出檔案的合併的效果,必須是一批task先執行,然後下一批task再執行,才能複用之前的輸出檔案;負責多批task同時起來執行,還是做不到複用的。

調節map端記憶體快取和reduce端記憶體佔比

Spark學習——效能調優(二)
預設map端記憶體緩衝是每個task,32kb。reduce端聚合記憶體比例,是0.2,也就是20%。

如果map端的task,處理的資料量比較大,但是呢,你的記憶體緩衝大小是固定的。可能會出現什麼樣的情況?

每個task就處理320kb,32kb,總共會向磁碟溢寫320 / 32 = 10次。
每個task處理32000kb,32kb,總共會向磁碟溢寫32000 / 32 = 1000次。

在map task處理的資料量比較大的情況下,而你的task的記憶體緩衝預設是比較小的,32kb。可能會造成多次的map端往磁碟檔案的spill溢寫操作,發生大量的磁碟IO,從而降低效能。

reduce端聚合記憶體,佔比。預設是0.2。如果資料量比較大,reduce task拉取過來的資料很多,那麼就會頻繁發生reduce端聚合記憶體不夠用,頻繁發生spill操作,溢寫到磁碟上去。而且最要命的是,磁碟上溢寫的資料量越大,後面在進行聚合操作的時候,很可能會多次讀取磁碟中的資料,進行聚合。預設不調優,在資料量比較大的情況下,可能頻繁地發生reduce端的磁碟檔案的讀寫。

這兩個點之所以放在一起講,是因為他們倆是有關聯的。資料量變大,map端肯定會出點問題;reduce端肯定也會出點問題;出的問題是一樣的,都是磁碟IO頻繁,變多,影響效能。

如何調優

調節map task記憶體緩衝:
new SparkConf().set("spark.shuffle.file.buffer", "64")
預設32k(spark 1.3.x不是這個引數,後面還有一個字尾,kb;spark 1.5.x以後,變了,就是現在這個引數)

調節reduce端聚合記憶體佔比:
new SparkConf().set("spark.shuffle.memoryFraction", "0.3")
預設0.2
複製程式碼

在實際生產環境中,我們在什麼時候來調節兩個引數?

看Spark UI,如果你的公司是決定採用standalone模式,那麼很簡單,你的spark跑起來,會顯示一個Spark UI的地址,4040的埠,進去看,依次點選進去,可以看到,你的每個stage的詳情,有哪些executor,有哪些task,每個task的shuffle write和shuffle read的量,shuffle的磁碟和記憶體,讀寫的資料量;如果是用的yarn模式來提交,從yarn的介面進去,點選對應的application,進入Spark UI,檢視詳情。

如果發現shuffle 磁碟的write和read很大。這個時候,就意味著最好調節一些shuffle的引數。進行調優。首先當然是考慮開啟map端輸出檔案合併機制。

調節上面說的那兩個引數。調節的時候的原則。spark.shuffle.file.buffer,每次擴大一倍,然後看看效果,64,128;spark.shuffle.memoryFraction,每次提高0.1,看看效果。

不能調節的太大,太大了以後過猶不及,因為記憶體資源是有限的,你這裡調節的太大了,其他環節的記憶體使用就會有問題了。

SortShuffleManager調優

Spark學習——效能調優(二)

Spark學習——效能調優(二)

//閾值設定
new SparkConf().set("spark.shuffle.sort.bypassMergeThreshold", "550")
複製程式碼
  • 在spark 1.5.x以後,對於shuffle manager又出來了一種新的manager,tungsten-sort(鎢絲),鎢絲sort shuffle manager。官網上一般說,鎢絲sort shuffle manager,效果跟sort shuffle manager是差不多的。 但是,唯一的不同之處在於,鎢絲manager,是使用了自己實現的一套記憶體管理機制,效能上有很大的提升, 而且可以避免shuffle過程中產生的大量的OOM,GC,等等記憶體相關的異常。

來一個總結,現在相當於把spark的shuffle的東西又多講了一些。大家理解的更加深入了。hash、sort、tungsten-sort。如何來選擇?

  1. 需不需要資料預設就讓spark給你進行排序?就好像mapreduce,預設就是有按照key的排序。如果不需要的話,其實還是建議搭建就使用最基本的HashShuffleManager,因為最開始就是考慮的是不排序,換取高效能;

  2. 什麼時候需要用sort shuffle manager?如果你需要你的那些資料按key排序了,那麼就選擇這種吧,而且要注意,reduce task的數量應該是超過200的,這樣sort、merge(多個檔案合併成一個)的機制,才能生效把。但是這裡要注意,你一定要自己考量一下,有沒有必要在shuffle的過程中,就做這個事情,畢竟對效能是有影響的。

  3. 如果你不需要排序,而且你希望你的每個task輸出的檔案最終是會合併成一份的,你自己認為可以減少效能開銷;可以去調節·bypassMergeThreshold·這個閾值,比如你的reduce task數量是500,預設閾值是200,所以預設還是會進行sort和直接merge的;可以將閾值調節成550,不會進行sort,按照hash的做法,每個reduce task建立一份輸出檔案,最後合併成一份檔案。(一定要提醒大家,這個引數,其實我們通常不會在生產環境裡去使用,也沒有經過驗證說,這樣的方式,到底有多少效能的提升)

  4. 如果你想選用sort based shuffle manager,而且你們公司的spark版本比較高,是1.5.x版本的,那麼可以考慮去嘗試使用tungsten-sort shuffle manager。看看效能的提升與穩定性怎麼樣。

總結:

  1. 在生產環境中,不建議大家貿然使用第三點和第四點:
  2. 如果你不想要你的資料在shuffle時排序,那麼就自己設定一下,用hash shuffle manager。
  3. 如果你的確是需要你的資料在shuffle時進行排序的,那麼就預設不用動,預設就是sort shuffle manager;或者是什麼?如果你壓根兒不care是否排序這個事兒,那麼就預設讓他就是sort的。調節一些其他的引數(consolidation機制)。(80%,都是用這種)
spark.shuffle.manager:hash、sort、tungsten-sort

new SparkConf().set("spark.shuffle.manager", "hash")
new SparkConf().set("spark.shuffle.manager", "tungsten-sort")
// 預設就是,new SparkConf().set("spark.shuffle.manager", "sort")

複製程式碼

相關文章