Spark本質上是基於內存的,當內存自己比較緊張,其性能也會受到影響。所以須要對內存使用進行優化,減小其消耗。經常使用的性能優化點包括:數據結構、序列器、緩存持久化與Checkpoint、JVM參數調優(由於本質上依賴GC)、並行度、共享數據與本地化存儲、算子合理選擇等方面。接下來咱們結合各個優化點,依次看看如何有效利用內存。apache
減小內存開銷的重要手段之一就是優化數據結構。因爲JAVA對象的特性,會隱含額外的內存開銷,例如對象頭與集合等。先看對象頭,因爲每一個對象都包含對象頭(ObjectHeader),對象頭分爲兩部分:MarkWord與ClassPointer(類型指針)。MarkWord存儲了對象的hashCode、GC與鎖信息;ClassPointer存儲了指向類對象信息的指針。在32位JVM上對象頭佔用8字節,64位JVM則爲16字節,兩種類型的MarkWord和ClassPointer各佔一半空間。所以某些數據存儲時,可能內容自己會小於本身的對象頭(如整型數據)。數組
圖1:對象頭示例緩存
集合類型內部會經過自定義對象來構造特定的數據結構,例如HashMap,內部會經過自定義類型Entry進行封裝。一樣這類對象除對象頭還包含指針,也會額外佔用內存。而對於存儲基本數據類型的集合(例如int類型),內部會經過其對應的包裝器進行封裝(例如Integer),無形中也會擴大內存佔用。優化Spark應用程序的內存,本質上是要優化自定義的算子函數中使用的局部變量,減小其對內存的佔用。性能優化
優化的着手點能夠考慮如下幾個方面:數據結構
例如:List<Integer> list = new ArrayList<Integer>(),能夠替換爲:int[] array = new int[]。轉換後的數組相比集合類型,一方面減小了元信息的存儲開銷;其次因爲直接使用基本數據類型,下降了對象頭的內存開銷。app
因爲多層次嵌套的對象中可能包含大量的小對象,所以能夠對多層嵌套的對象結構進行拆解。例如:ide
public class Orders { private List<Items> items = newArrayList<Items>() }
能夠替換爲JSON:函數
{"orderId":1001,items:[{"itemId":1,"itemName":"cafe"},{"itemId":2, "itemName":"applewatch"}]}
一、設置RDD並行度(即partition數)。
二、調用RDD.cache()方法將RDD緩存到內存中。
三、觀察Driver的日誌能夠找到相似於:
「INFO ... Added rdd_0_1 in memory on mbk.local:50311 (size: 717.5KB, free: 332.3 MB)」的信息,顯示每一個partition佔用的內存。
四、內存數量*partition數量,即RDD內存總佔用量。性能
Spark算子一般會使用到外部的數據,當處理的數據比較大時,一樣會對內存形成巨大的開銷。所以引入了序列化技術,其實本質上就是對數據進行壓縮,減小對內存的佔用。Spark默認使用基於ObjectInputStream/ObjectOutputStream的原生序列化機制,其優勢在於便捷性,但問題是其性能不高,所以某些場景下並不是最佳選擇。優化
Kryo序列化機制——Spark支持使用Kryo類庫來進行序列化,相對而言Kryo更快並且序列化後的數據佔用更小。但缺點在於須要預先在Spark應用中對全部須要序列化的類型註冊。若是不註冊,Kryo必須保存類型的全限定名,反而會增大內存開銷。
配置參與以啓用Kryo序列化:
newSparkConf() .set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
註冊自定義類型以使用Kryo序列化:
SparkConf conf = newSparkConf().setMaster(...).setAppName(...) conf.registerKryoClasses(BigData.class) JavaSparkContext sc = newJavaSparkContext(conf)
此外值得注意的是,若是註冊的序列化類型比較大,此時須要對Kryo緩存進行調整,由於可能內部緩存沒法存放過大的對象。能夠調用SparkConf.set("spark.kryoserializer.buffer.mb", x)方法進行調整(緩存默認大小爲2M)。