Spark性能調優

調優之前是將功能實現...然后算法優化,設計優化,再是spark調優!,需得一步一步來,不得直接越過,直接調優!

executor調優

對于exector的調優基于一個原則,那就是使用端口號界面看cpu的使用率.

在new SparkContext后,集群就會為在Worker上分配executor,如果executor對cpu使用率低的話,那就可以多增加幾個executor. 但是一套機器上的使用內存是不變的,executor越多,每個executor分配的內存就越少,從而會導致shuffle過程溢寫到磁盤上的文件過多,出現過多的數據spill over(溢出)甚至out of memory(內存不足)的情況.

每一次task只能處理一個partition的數據,這個值太小了會導致每片數據量太大,導致內存壓力,或者諸多executor的計算能力無法利用充分;但是如果太大了則會導致分片太多,執行效率降低。

調優方式1: 程序跑的過程中,發現Spark job有時候特別慢,查看cpu的利用率很低,可以嘗試減少每個executor占用cpu core的數量,增加并行executor的數量.配合增加切片,整體上提高cpu的利用率,從而提高性能.

調優方式2: 如果發現Spark job的時常發生內存溢出, 增加分片的數量,從而就可以減少每片數據的規模,能夠更多的task處理同樣的任務,減少executor的數量,這樣每個executor能夠分配到的內存就更大,相當于每個task的內存能夠更大的分配. 可能運行速度變慢了些,但是不會內存溢出了.

調優方式3: 如果數據量很少,有大量的小文件生成, 那就減少文件分片,使task整體數量減少,每個task能夠分到更大的內存.,處理更多的數據,就不會有大量的小文件生成.


頻繁GC或者OOM優化

頻繁GC(垃圾回收)或者OOM(內存溢出)在Driver端和Executor端分別有不同的處理方式

Driver端: 通常由于計算過大的結果集被回收到Driver端導致,需要調大Driver端的內存解決,或者進一步減少結果集的數量。

Executor端:

1. 以外部數據作為輸入的Stage,這類Stage中出現GC通常是因為在Map側進行map-side-combine時,由于group過多引起的。解決方法可以增加partition的數量(即task的數量)來減少每個task要處理的數據,來減少GC的可能性

2. 以shuffle作為輸入的Stage:這類Stage中出現GC的通常原因也是和shuffle有關,常見原因是某一個或多個group的數據過多,也就是所謂的數據傾斜,簡單的方法就是增加shuffle的數量.復雜點就在業務邏輯上調整,預先針對較大的group做單獨處理。


數據傾斜優化

注: 數據傾斜一般是代碼中的某些算子導致的

數據傾斜的物理本質

數據傾斜只會發生的shuffle階段,有時候會將各個節點相同的key拉取到某一個Task進行處理(比如join等操作),如果某個相同的key遠遠超過其他的key,那么這個Task所處理的數據量也就特別龐大,而spark作業的進度,也就是由最長的那個task決定的.

可能會誘發數據傾斜的算子

開發當中,不影響業務和效率的情況下,應當盡量避免以下算子:

distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition..

如何使用web ui看數據傾斜

通過Spark Web UI來查看當前stage各個task分配的數據量,進而確定是不是task的數據分配不均勻導致了數據傾斜

數據傾斜

如圖上就是典型的數據傾斜, 有的task處理完數據,只要5s,共1000多個key,而有的task要處理完數據需要4分鐘,一萬多個key..

如何處理數據傾斜

1. 預處理數據源

有些數據源的數據,本身分布就是不均勻的,那就可以實現預處理,如比如數據源是hive的話,而hive本身的數據剛好不均勻,那就對Hive數據源進行ETL,再進行spark作業. 也就是講spark的shuffle操作提前到了Hive ETL中.

這種方法數據傾斜還是存在的,只不過發生在了spark處理之前

2. 增加stage中task的數量

一個stage中封裝了一組具有相同邏輯的task,默認是200個.我們可以通過設置spark.sql.shuffle.partitions來修改task的數量,比如增加為1000,此時,stage分配的內存也會增加,stage里面更多的的task能夠處理相同的key,這樣的操作能夠有效的緩解和減輕數據傾斜問題.

這種方法面對傾斜不是非常嚴重的情況,還是挺有用的,但是面對百萬,千萬key的話,就力不從心了

3.過濾一些導致傾斜的key

再某些場景下,某個key突然暴增,在對數據影響輕微的情況下,我們可以直接過濾這個key,讓他不參與計算.

這種方法完美規避數據傾斜,但是有可能是多個key導致的,那這個就不太好了.

4. 添加隨機前綴

對RDD執行reduceByKey等聚合類shuffle算子或者在Spark SQL中使用group by語句進行分組聚合時,比較適用這種方案

這個方法需要兩次聚合,第一次是局部聚合,先給每個key都打上一個隨機數,比如10以內的隨機數,此時原先一樣的key就變成不一樣的了,比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),就會變成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。接著對打上隨機數后的數據,執行reduceByKey等聚合操作,進行局部聚合,那么局部聚合結果,就會變成了(1_hello, 2) (2_hello, 2)。然后將各個key的前綴給去掉,就會變成(hello,2)(hello,2),再次進行全局聚合操作,就可以得到最終結果了,比如(hello, 4)。

將原本相同的key通過附加隨機前綴的方式,變成多個不同的key,就可以讓原本被一個task處理的數據分散到多個task上去做局部聚合,進而解決單個task處理數據量過多的問題。接著去除掉隨機前綴,再次進行全局聚合,就可以得到最終的結果.

這種方法很好,我在其他的博客上看到的,還沒實踐過,真心可以.不過僅限于聚合類

5.多種方案組合完成

就是將以上幾種方案一起組合起來使用,比如先預處理,在增加task,再增加前綴..當然,處理數據傾斜的方案肯定其他的,我也在不斷的學習!


程序開發調優

1. 避免創建重復的RDD

代碼如下: sc.textFile("hdfs://192.168.0.1:9000/hello.txt").map(...)

?????????????? ? sc.textFile("hdfs://192.168.0.1:9000/hello.txt").reduce(...)

對于同一份數據,我們只需要創建一份RDD來處理即可,如果對同一份數據創建了多個RDD,那就意味著spark作業會進行多次創建代表相同數據的RDD,并且對每一個RDD都重復計算,對spark作業的效率.內存.性能開銷都有明顯的影響

2. 盡可能復用同一個RDD

代碼如下: JavaPairRDD rdd1 = ...??????? JavaRDD rdd2 = rdd1.map(...)

???????????????? rdd1.reduceByKey(...)??????????? rdd1.map(tuple._2...)

針對不同的數據,但是不同的數據有重疊或者包含的關系,這樣的情景下,我們盡量復用同一個RDD,因為這樣可以盡可能地減少RDD的數量,從而盡可能減少算子執行的次數。

3. 對多次使用的RDD進行持久化

自動持久化: valrdd1= sc.textFile("hdfs://192.168.0.1:9000/666.txt").cache()

rdd1.map(...)???????? rdd1.reduce(...)???

//cache()使用非序列化的方式將RDD中的數據全部嘗試持久化到內存

手動持久化: valrdd1= sc.textFile("hdfs://192.168.0.1:9000/666.txt").persist(StorageLevel.MEMORY_AND_DISK_SER)

rdd1.map(...)??????? rdd1.reduce(...)

//prsist()使用指定的方式進行持久化,StorageLevel.MEMORY_AND_DISK_SER表示內存充足時有限持久化到內存中,不充足時持久化到磁盤中,后綴_SER表示使用序列化保存RDD數據

對多次使用的RDD進行持久化就是保證一個RDD執行多次算子操作時, 這個RDD本身僅僅被計算一次

對于多次使用的RDD進行持久化操作,Spark就會將數據保存到內存或磁盤中,下次使用此RDD時,就會從內存或磁盤中提取已持久化的rdd,而不需要將此RDD重新再計算一遍

4. 盡量避免使用shuffle算子

在上面開篇就提到過:在開發當中,不影響業務和效率的情況下,盡量避免使用shuffle類算子,這類算子包括distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition...

spark作業中最消耗性能的是shuffle,shuffle就是將key相同的數據,拉取到同一個task上進行join.reduceByKey等算子操作.這個過程中會有大量的磁盤IO,數據傳輸,內存消耗,對性能有很大的影響.

5. groupByKey算子替代

在要使用groupByKey算子的時候,盡量用reduceByKey或者aggregateByKey算子替代.因為調用groupByKey時候,按照相同的key進行分組,形成RDD[key,Iterable[value]]的形式,此時所有的鍵值對都將被重新洗牌,移動,對網絡數據傳輸造成理論上的最大影響.

GroupByKey的方式

而reduceByKey的話,他會先將本地的數據預聚合一次(通過reduceByKey的lambda函數,),就只有一條key了, 而進行shuffle操作,將所有相同的key拉取到同一個task上時,就可以減少極大的IO以及數據傳輸. 所以用reduceByKey或者aggregateByKey算子來替代掉groupByKey算子,這樣可以提前預聚合,從而提高性能.

ReduceByKey的方式

6. 使用高性能的算子

像上面的reduceByKey或者aggregateByKey算子來替代掉groupByKey算子,這就是使用高性能算子,與此類似的還有

1) 使用mapPartitions替代普通map; mapPartitions算子,一次函數調用會處理一個partition的數據,而不是一次函數調用處理一條,性能也有大的提升. 但是一次處理一個partition的數據,就要一次處理一個partition的數據,如果內存不夠,就會出現OOM操作.

2) 使用foreachPartitions替代foreach; foreachPartitions也是一次調用處理一個partition的數據,而非foreach的一條,對性能也有大的提升,但是和上面一樣,也要注意內存的充足與否

3) filter之后進行coalesce操作; filter之后,RDD的每個partition中都會有很多數據被過濾掉,此時如果照常進行后續的計算,其實每個task處理的partition中的數據量并不是很多,有一點資源浪費,而且此時處理的task越多,可能速度反而越慢。因此用coalesce減少partition數量,將RDD中的數據壓縮到更少的partition之后,只要使用更少的task即可處理完所有的partition。這對spark的性能也有一定的穩定與幫助

4) repartitionAndSortWithinPartitions替代repartition與sort類操作; 如果需要在repartition重分區之后,還要進行排序, 建議直接使用repartitionAndSortWithinPartitions算子。因為該算子可以一邊進行重分區的shuffle操作,一邊進行排序。shuffle與sort兩個操作同時進行,比先shuffle再sort來說,性能也能優化不少.

7. 使用廣播大變量

代碼如下: val list1= ...

??????????????? rdd1.map(list1...)

??????????????? val list1= ...

??????????????? val list1 Broadcast=sc.broadcast(list1)

?????????????? rdd1.map(list1Broadcast...)

使用一般的外部變量,如果外部變量內存較大,會導致頻繁GC,這時候可以使用spark的廣播功能. 廣播后的變量,會保證每個Executor的內存中會有一份變量副本,此時task會共享它所在的Executor中的那個廣播變量. 從而減少網絡傳輸的性能開銷,并減少了副本的數量,減少對Executor內存的占用開銷,降低了GC的頻率。

8. 優化數據結構

Spark官方建議,在Spark編碼實現中,特別是對于算子函數中的代碼,盡量不要使用字符串.集合.對象三種數據結構.

盡量使用字符串代替對象,盡量用基本數據類型(Int,Long..)代替字符串,盡量舒勇數組代替集合,? 這樣可以盡可能減少內存占用,降低GC頻率,提高性能


運行資源調優

這一段我摘抄的...

前面的調優一直都提到了一個點,那就是內存的分配,資源的分配.不然會導致OOM,頻繁GC..? ?

1) num-executors

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

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

2) executor-memory

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

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

3) 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左右比較合適,也是避免影響其他同學的作業運行。

4) driver-memory

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

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

5) 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集群的資源。

6) spark.storage.memoryFraction

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

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

web ui可以觀察到作業的gc耗時),意味著task執行用戶代碼的內存不夠用,那么同樣建議調低這個參數的值。

7) spark.shuffle.memoryFraction

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容