參考:https://www.iteblog.com/archives/2342.html
spark新的內(nèi)存管理框架(1.6以上)上使用兩個參數(shù)來控制spark.memory.fraction和spark.memory.storageFraction。
spark.storage.memoryFraction spark.shuffle.memoryFraction不再使用
在spark1.6之前采用的是靜態(tài)內(nèi)存模型,1.6之后采用的是統(tǒng)一內(nèi)存模型
概述
統(tǒng)一內(nèi)存管理模塊包括了堆內(nèi)內(nèi)存(On-heap Memory)和堆外內(nèi)存(Off-heap Memory)兩大區(qū)域
對于堆內(nèi)內(nèi)存
- System Memory = executor-memory = (Eden + FROM +TO) = execution-memory + storage-memory + user-memory + reserver-memory
- spark-memory = execution-memory + storage-memory
- reserver-memory固定大小為200M
- user-meomry = (SystemMemory-200m)* (1-spark.memory.fraction)
- spark-meomry = (SystemMemory-200m)*spark.memory.fraction
- storage-memory = spark-memory * spark.memory.storageFraction
-execution-memory = spark-memory * (1-spark.memory.storageFraction)
On-heap Memory 堆內(nèi)內(nèi)存
默認(rèn)情況下笆豁,Spark 僅僅使用了堆內(nèi)內(nèi)存。Executor 端的堆內(nèi)內(nèi)存區(qū)域大致可以分為以下四大塊:
- Execution 內(nèi)存:主要用于存放 Shuffle锅移、Join、Sort淌喻、Aggregation 等計(jì)算過程中的臨時數(shù)據(jù)
- Storage 內(nèi)存:主要用于存儲 spark 的 cache 數(shù)據(jù)跃赚,例如RDD的緩存、unroll數(shù)據(jù)膏蚓;
- 用戶內(nèi)存(User Memory):主要用于存儲 RDD 轉(zhuǎn)換操作所需要的數(shù)據(jù),例如 RDD 依賴等信息畸写。
- 預(yù)留內(nèi)存(Reserved Memory):系統(tǒng)預(yù)留內(nèi)存驮瞧,會用來存儲Spark內(nèi)部對象。
systemMemory = Runtime.getRuntime.maxMemory枯芬,其實(shí)就是通過參數(shù) spark.executor.memory 或 --executor-memory 配置的论笔。
reservedMemory 在 Spark 2.2.1 中是寫死的,其值等于 300MB千所,這個值是不能修改的(如果在測試環(huán)境下狂魔,我們可以通過 spark.testing.reservedMemory 參數(shù)進(jìn)行修改);
usableMemory = systemMemory - reservedMemory淫痰,這個就是 Spark 可用內(nèi)存
Off-heap Memory 堆外內(nèi)存
java的unsafe相關(guān)的api使用的是堆外內(nèi)存
這種模式不在 JVM 內(nèi)申請內(nèi)存最楷,而是調(diào)用 Java 的 unsafe 相關(guān) API 進(jìn)行諸如 C 語言里面的 malloc() 直接向操作系統(tǒng)申請內(nèi)存,由于這種方式不進(jìn)過 JVM 內(nèi)存管理待错,所以可以避免頻繁的 GC籽孙,這種內(nèi)存申請的缺點(diǎn)是必須自己編寫內(nèi)存申請和釋放的邏輯
默認(rèn)情況下,堆外內(nèi)存是關(guān)閉的朗鸠,我們可以通過 spark.memory.offHeap.enabled 參數(shù)啟用,并且通過 spark.memory.offHeap.size 設(shè)置堆外內(nèi)存大小础倍,單位為字節(jié)烛占。如果堆外內(nèi)存被啟用,那么 Executor 內(nèi)將同時存在堆內(nèi)和堆外內(nèi)存沟启,兩者的使用互補(bǔ)影響忆家,這個時候 Executor 中的 Execution 內(nèi)存是堆內(nèi)的 Execution 內(nèi)存和堆外的 Execution 內(nèi)存之和,同理德迹,Storage 內(nèi)存也一樣芽卿。相比堆內(nèi)內(nèi)存,堆外內(nèi)存只區(qū)分 Execution 內(nèi)存和 Storage 內(nèi)存
Execution 內(nèi)存和 Storage 內(nèi)存動態(tài)調(diào)整
具體的實(shí)現(xiàn)邏輯如下:
程序提交的時候我們都會設(shè)定基本的 Execution 內(nèi)存和 Storage 內(nèi)存區(qū)域(通過 spark.memory.storageFraction 參數(shù)設(shè)置)胳搞;
在程序運(yùn)行時卸例,如果雙方的空間都不足時称杨,則存儲到硬盤;將內(nèi)存中的塊存儲到磁盤的策略是按照 LRU 規(guī)則進(jìn)行的筷转。若己方空間不足而對方空余時姑原,可借用對方的空間;(存儲空間不足是指不足以放下一個完整的 Block)
Execution 內(nèi)存的空間被對方占用后,可讓對方將占用的部分轉(zhuǎn)存到硬盤呜舒,然后"歸還"借用的空間
Storage 內(nèi)存的空間被對方占用后锭汛,目前的實(shí)現(xiàn)是無法讓對方"歸還",因?yàn)樾枰紤] Shuffle 過程中的很多因素袭蝗,實(shí)現(xiàn)起來較為復(fù)雜唤殴;而且 Shuffle 過程產(chǎn)生的文件在后面一定會被使用到,而 Cache 在內(nèi)存的數(shù)據(jù)不一定在后面使用到腥。
注意朵逝,上面說的借用對方的內(nèi)存需要借用方和被借用方的內(nèi)存類型都一樣,都是堆內(nèi)內(nèi)存或者都是堆外內(nèi)存左电,不存在堆內(nèi)內(nèi)存不夠去借用堆外內(nèi)存的空間廉侧。
一個示例
現(xiàn)在我們提交的 Spark 作業(yè)關(guān)于內(nèi)存的配置如下:
--executor-memory 18g
由于沒有設(shè)置 spark.memory.fraction 和 spark.memory.storageFraction 參數(shù)
上圖很清楚地看到 Storage Memory 的可用內(nèi)存是 10.1GB,這個數(shù)是咋來的呢篓足?根據(jù)前面的規(guī)則段誊,我們可以得出以下的計(jì)算:
systemMemory = spark.executor.memory
reservedMemory = 300MB
usableMemory = systemMemory - reservedMemory
StorageMemory= usableMemory * spark.memory.fraction * spark.memory.storageFraction
如果我們把數(shù)據(jù)代進(jìn)去,得出以下的結(jié)果:
systemMemory = 18Gb = 19327352832 字節(jié)
reservedMemory = 300MB = 300 * 1024 * 1024 = 314572800
usableMemory = systemMemory - reservedMemory = 19327352832 - 314572800 = 19012780032
StorageMemory= usableMemory * spark.memory.fraction * spark.memory.storageFraction
= 19012780032 * 0.6 * 0.5 = 5703834009.6 = 5.312109375GB
不對啊栈拖,和上面的 10.1GB 對不上啊连舍。為什么呢?這是因?yàn)?Spark UI 上面顯示的 Storage Memory 可用內(nèi)存其實(shí)等于 Execution 內(nèi)存和 Storage 內(nèi)存之和涩哟,也就是 usableMemory * spark.memory.fraction:
StorageMemory= usableMemory * spark.memory.fraction
= 19012780032 * 0.6 = 11407668019.2 = 10.62421GB
還是不對索赏,這是因?yàn)槲覀冸m然設(shè)置了 --executor-memory 18g,但是 Spark 的 Executor 端通過 Runtime.getRuntime.maxMemory 拿到的內(nèi)存其實(shí)沒這么大贴彼,只有 17179869184 字節(jié)潜腻,所以 systemMemory = 17179869184,然后計(jì)算的數(shù)據(jù)如下:
systemMemory = 17179869184 字節(jié)
reservedMemory = 300MB = 300 * 1024 * 1024 = 314572800
usableMemory = systemMemory - reservedMemory = 17179869184 - 314572800 = 16865296384
StorageMemory= usableMemory * spark.memory.fraction
= 16865296384 * 0.6 = 9.42421875 GB
我們通過將上面的 16865296384 * 0.6 字節(jié)除于 1024 * 1024 * 1024 轉(zhuǎn)換成 9.42421875 GB器仗,和 UI 上顯示的還是對不上融涣,這是因?yàn)?Spark UI 是通過除于 1000 * 1000 * 1000 將字節(jié)轉(zhuǎn)換成 GB,如下:
systemMemory = 17179869184 字節(jié)
reservedMemory = 300MB = 300 * 1024 * 1024 = 314572800
usableMemory = systemMemory - reservedMemory = 17179869184 - 314572800 = 16865296384
StorageMemory= usableMemory * spark.memory.fraction
= 16865296384 * 0.6 字節(jié) = 16865296384 * 0.6 / (1000 * 1000 * 1000) = 10.1GB
我們設(shè)置了 --executor-memory 18g精钮,但是 Spark 的 Executor 端通過 Runtime.getRuntime.maxMemory 拿到的內(nèi)存其實(shí)沒這么大威鹿,只有 17179869184 字節(jié),這個數(shù)據(jù)是怎么計(jì)算的轨香?
Runtime.getRuntime.maxMemory 是程序能夠使用的最大內(nèi)存忽你,其值會比實(shí)際配置的執(zhí)行器內(nèi)存的值小。這是因?yàn)閮?nèi)存分配池的堆部分分為 Eden臂容,Survivor 和 Tenured 三部分空間科雳,而這里面一共包含了兩個 Survivor 區(qū)域根蟹,而這兩個 Survivor 區(qū)域在任何時候我們只能用到其中一個,所以我們可以使用下面的公式進(jìn)行描述:
ExecutorMemory = Eden + 2 * Survivor + Tenured
Runtime.getRuntime.maxMemory = Eden + Survivor + Tenured
上面的 17179869184 字節(jié)可能因?yàn)槟愕?GC 配置不一樣得到的數(shù)據(jù)不一樣炸渡,但是上面的計(jì)算公式是一樣的
使用了堆外內(nèi)存
用了堆內(nèi)和堆外內(nèi)存
現(xiàn)在如果我們啟用了堆外內(nèi)存娜亿,情況咋樣呢?我們的內(nèi)存相關(guān)配置如下:
spark.executor.memory 18g
spark.memory.offHeap.enabled true
spark.memory.offHeap.size 10737418240
其實(shí) Spark UI 上面顯示的 Storage Memory 可用內(nèi)存等于堆內(nèi)內(nèi)存和堆外內(nèi)存之和蚌堵,計(jì)算公式如下:
堆內(nèi)
systemMemory = 17179869184 字節(jié)
reservedMemory = 300MB = 300 * 1024 * 1024 = 314572800
usableMemory = systemMemory - reservedMemory = 17179869184 - 314572800 = 16865296384
totalOnHeapStorageMemory = usableMemory * spark.memory.fraction
= 16865296384 * 0.6 = 10119177830
堆外
totalOffHeapStorageMemory = spark.memory.offHeap.size = 10737418240
StorageMemory = totalOnHeapStorageMemory + totalOffHeapStorageMemory
= (10119177830 + 10737418240) 字節(jié)
= (20856596070 / (1000 * 1000 * 1000)) GB
= 20.9 GB