溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行spark內(nèi)存、GC及數(shù)據(jù)結(jié)構(gòu)調(diào)優(yōu)

發(fā)布時間:2021-12-16 21:01:42 來源:億速云 閱讀:325 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關如何進行spark內(nèi)存、GC及數(shù)據(jù)結(jié)構(gòu)調(diào)優(yōu),可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

一,基本概述

調(diào)優(yōu)內(nèi)存的使用主要有三個方面的考慮:對象的內(nèi)存占用量(你可能希望整個數(shù)據(jù)集都適合內(nèi)存),訪問這些數(shù)據(jù)的開銷,垃圾回收的負載。

默認情況下,java的對象是可以快速訪問的,但是相比于內(nèi)部的原始數(shù)據(jù)消耗估計2-5倍的空間。主要歸于下面三個原因:

1),每個不同的Java對象都有一個“對象頭”,它大約是16個字節(jié),包含一個指向它的類的指針。對于一個數(shù)據(jù)很少的對象(比如一個Int字段),這可以比數(shù)據(jù)大。

2),Java字符串在原始字符串數(shù)據(jù)上具有大約40字節(jié)的開銷(因為它們將它們存儲在一個Chars數(shù)組中,并保留額外的數(shù)據(jù),例如長度),并且由于String的內(nèi)部使用UTF-16編碼而將每個字符存儲為兩個字節(jié)。因此,一個10個字符的字符串可以容易地消耗60個字節(jié)。

3),常用集合類(如HashMap和LinkedList)使用鏈接的數(shù)據(jù)結(jié)構(gòu),其中每個條目都有一個“包裝器”對象(例如Map.Entry)。該對象不僅具有頭部,還包括指針(通常為8個字節(jié))到列表中的下一個對象。

4),原始類型的集合通常將它們存儲為“boxed”對象,如java.lang.Integer。

本節(jié)將從Spark的內(nèi)存管理概述開始,然后討論用戶可以采取的具體策略,以便在他/她的應用程序中更有效地使用內(nèi)存。具體來說,我們將描述如何確定對象的內(nèi)存使用情況,以及如何改進數(shù)據(jù)結(jié)構(gòu),或通過以序列化的格式存儲數(shù)據(jù)。然后我們將介紹調(diào)優(yōu)Spark的緩存大小和Java垃圾回收器。

二,spark的內(nèi)存管理概述

Spark中的內(nèi)存使用大部分屬于兩類:執(zhí)行和存儲。運行內(nèi)存指的是用于計算的,shuffle,joins,sorts 和aggregations,然后存儲內(nèi)存主要用于緩存和在集群中傳播的內(nèi)部數(shù)據(jù)。在spark內(nèi)部,存儲器和執(zhí)行器共享一個統(tǒng)一的區(qū)域(M)。當沒有使用執(zhí)行器內(nèi)存的時候,存儲器可以獲取所有可用的執(zhí)行器內(nèi)存,反之亦然。如果有需要執(zhí)行器可以驅(qū)逐存儲占用,但是僅僅當存儲內(nèi)存大于一個閾值(R)的時候才會發(fā)生。換句話說,R描述了M內(nèi)部的一個子區(qū)域,R中的緩存永遠不會被清除。由于實施的復雜性,存儲內(nèi)存不得驅(qū)逐執(zhí)行內(nèi)存。該設計保證了幾個理想的性能。

首先,不使用緩存的應用程序可以將整個空間用于執(zhí)行,從而避免不必要的磁盤溢寫。

其次,使用緩存的應用程序可以保留最小的存儲空間(R),其中數(shù)據(jù)塊不受驅(qū)逐。

最后,這種方法為各種工作負載提供了合理的開箱即用性能,而不需要用戶掌握內(nèi)部如何分配內(nèi)存的專業(yè)知識。

雖然有兩個相關配置,但典型用戶不需要調(diào)整它們,因為默認值適用于大多數(shù)工作負載:

1),spark.memory.fraction將M的大小表示為(JVM堆空間 - 300MB)的一部分(默認為0.75,新版本如spark2.2改為0.6)。剩余的空間(25%,對應的新版本是0.4)用于用戶數(shù)據(jù)結(jié)構(gòu),Spark中的內(nèi)部元數(shù)據(jù),并且在稀疏和異常大的記錄的情況下保護OOM錯誤。

2),spark.memory.storageFraction表示R的大小作為M的一部分(默認為0.5)。R是M內(nèi)的存儲空間,其中緩存的塊免于被執(zhí)行器驅(qū)逐。

三,確定內(nèi)存的消耗

最好的方式去計算一個數(shù)據(jù)的的內(nèi)存消耗,就是創(chuàng)建一個RDD,然后加入cache,這樣就可以在web ui中Storage頁面看到了。頁面會告訴你,這個RDD消耗了多少內(nèi)存。

要估計特定對象的內(nèi)存消耗,請使用SizeEstimator的估計方法。這對于嘗試使用不同的數(shù)據(jù)布局來修剪內(nèi)存使用情況以及確定廣播變量在每個執(zhí)行程序堆中占用的空間量非常有用。

四,調(diào)優(yōu)數(shù)據(jù)結(jié)構(gòu)

減少內(nèi)存消耗的第一種方法是避免使用增加負擔的java特性,例如基于指針的數(shù)據(jù)結(jié)構(gòu)和包裝對象。下面幾種方法可以來避免這個。

1,將數(shù)據(jù)結(jié)構(gòu)設計為偏好對象數(shù)組和原始類型,而不是標準的Java或Scala集合類(例如HashMap)。fastutil庫(http://fastutil.di.unimi.it/)為與Java標準庫兼容的原始類型提供方便的集合類。

2,盡可能避免使用有很多小對象和指針的嵌套結(jié)構(gòu)。

3,針對關鍵詞,考慮使用數(shù)字ID或者枚舉對象而不是字符串。

4,如果您的RAM少于32 GB,請設置JVM標志-XX:+ UseCompressedOops使指針為四個字節(jié)而不是八個字節(jié)。您可以在spark-env.sh中添加這些選項。

五,序列化RDD

盡管進行了調(diào)優(yōu),當您的對象仍然太大而無法有效存儲時,一個簡單的方法來減少內(nèi)存使用是使用RDD持久性API中的序列化StorageLevel(如MEMORY_ONLY_SER)以序列化形式存儲它們。Spark將會將每個RDD分區(qū)存儲為一個大字節(jié)數(shù)組。以序列化形式存儲數(shù)據(jù)的唯一缺點是數(shù)據(jù)訪問變慢,因為必須對每個對象進行反序列化。如果您想以序列化形式緩存數(shù)據(jù),我們強烈建議使用Kryo,因為它會使數(shù)據(jù)比java序列化后的大小更?。ǘ铱隙ū仍璊ava對象更?。?/p>

六,垃圾回收調(diào)優(yōu)

1,基本介紹

當你程序的RDD頻繁的變動的時候,垃圾回收將會是一個問題。RDD的一次讀入,然后有很多種基于它的計算,這種情況下垃圾回收沒啥問題。當JAVA需要驅(qū)逐舊的對象,為新對象騰出空間的時候,需要跟蹤所有Java對象并找到無用的對象。要記住的要點是,垃圾收集的成本與Java對象的數(shù)量成正比,因此使用較少對象的數(shù)據(jù)結(jié)構(gòu)(例如,Ints數(shù)組,代替LinkedList)將大大降低了成本。一個更好的方法是以序列化形式持久化對象,如上所述:每個RDD分區(qū)將只有一個對象(一個字節(jié)數(shù)組)。在嘗試其他技術之前,如果GC是一個問題,首先要嘗試的是使用序列化緩存。

由于任務的運行內(nèi)存和RDD的緩存內(nèi)存的干擾,GC也會是一個問題。

2,測量GC的影響

GC調(diào)優(yōu)的第一步是收集關于垃圾收集發(fā)生頻率和GC花費的時間的統(tǒng)計信息。通過將-verbose:gc -XX:+ PrintGCDetails -XX:+ PrintGCTimeStamps添加到Java選項來完成。下次運行Spark作業(yè)時,每當垃圾收集發(fā)生時,都會看到在工作日志中打印的消息。請注意,這些日志將在您的群集的Executor節(jié)點上(在其工作目錄中的stdout文件中),而不是您的driver功能中。

3,高級GC調(diào)優(yōu)

為了進一步調(diào)整垃圾收集,我們首先需要了解一些關于JVM內(nèi)存管理的基本信息<詳細的請看:JVM的垃圾回收算法>:

1),java的堆內(nèi)存分為兩個區(qū)域新生代和老年代。新生代保存的是生命周期比較短的對象,老年代保存生命周期比較長的對象。

2),新生代又分為了三個區(qū)域(Eden, Survivor1, Survivor2)。

3),垃圾收集過程的簡化說明:當Eden已滿時,Eden上運行了一個minor GC,并將Eden和Survivor1中存在的對象復制到Survivor2。Survivor 將進行交換。如果一個對象足夠老,或者Survivor2已滿,則會移動到老年代。最后當老年代接近滿的時候,會觸發(fā)full GC。

Spark應用程序GC調(diào)優(yōu)的目標是,確保生命周期比較長的RDD保存在老年代,新生代有足夠的空間保存生命周期比較短的對象。這有助于避免觸發(fā)Full GC去收集task運行期間產(chǎn)生的臨時變量。下面列舉幾個有用的步驟:

1),通過收集垃圾回收信息,判斷是否有太多的垃圾回收過程。假如full gc在一個task完成之前觸發(fā)了好幾次,那說明運行task的內(nèi)存空間不足,需要加內(nèi)存。

2),在gc的統(tǒng)計信息中,如果老年代接近滿了,減少用于緩存的內(nèi)存(通過減小spark.memory.Fraction)。緩存較少的對象比降低運行速度對我們來說更有好處。另外,可以考慮減少年輕代。可以通過減小-Xmn參數(shù)設置的值,假如使用的話。假如沒有設置可以修改JVM的NewRation參數(shù)。大多數(shù)JVMs默認值是2,意思是老年代占用了三分之二的總內(nèi)存。這個值要足夠大,相當于擴展了spark.memory.fraction.

3),如果有太多的minor gc,較少的major gc,增加Eden區(qū)內(nèi)存會有幫助。將Eden區(qū)內(nèi)存設置的比task運行估計內(nèi)存稍微大一些。如果Eden區(qū)大小確定為E,那就將新生代的內(nèi)存設置為-Xmn=4/3E,按比例增加內(nèi)存是考慮到survivor區(qū)所占用的內(nèi)存。

4),嘗試通過設置-XX:+UseG1GC垃圾回收器為G1。在垃圾回收器是瓶頸的一些情況下,它可以提高性能。請注意,對于大的Executor堆,通過使用-XX:G!HeapRegionSize去增大G1的堆大小,顯得尤為重要。

5),例如,如果您的任務是從HDFS讀取數(shù)據(jù),則可以使用從HDFS讀取的數(shù)據(jù)塊的大小來估計任務使用的內(nèi)存量。請注意,解壓縮塊的大小通常是塊大小的2或3倍。所以如果我們希望有3或4個任務的工作空間,HDFS塊的大小是64 MB,我們可以估計Eden的大小是4 * 3 * 64MB。

5),監(jiān)控垃圾收集的頻率和時間如何隨著新設置的變化而變化。

經(jīng)驗表明,GC調(diào)整的效果取決于您的應用程序和可用的內(nèi)存量。下面的鏈接里有更多的在線描述的調(diào)優(yōu)的選項,但在高層次上,管理GC的全面發(fā)生頻率有助于減少開銷。

看完上述內(nèi)容,你們對如何進行spark內(nèi)存、GC及數(shù)據(jù)結(jié)構(gòu)調(diào)優(yōu)有進一步的了解嗎?如果還想了解更多知識或者相關內(nèi)容,請關注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI