Spark RDD 緩存機(jī)制
Spark RDD 緩存是在內(nèi)存存儲(chǔ)RDD計(jì)算結(jié)果的一種優(yōu)化技術(shù)。把中間結(jié)果緩存起來(lái)以便在需要的時(shí)候重復(fù)使用,這樣才能有效減輕計(jì)算壓力,提升運(yùn)算性能。
當(dāng)對(duì)RDD執(zhí)行持久化操作時(shí),每個(gè)節(jié)點(diǎn)都會(huì)將自己操作的RDD的partition持久化到內(nèi)存中,并且在之后對(duì)該RDD的反復(fù)使用中,直接使用內(nèi)存緩存的partition。這樣的話,對(duì)于針對(duì)一個(gè)RDD反復(fù)執(zhí)行多個(gè)操作的場(chǎng)景,就只要對(duì)RDD計(jì)算一次即可,后面直接使用該RDD,而不需要反復(fù)計(jì)算多次該RDD。
巧妙使用RDD持久化,甚至在某些場(chǎng)景下,可以將spark應(yīng)用程序的性能提升10倍。對(duì)于迭代式算法和快速交互式應(yīng)用來(lái)說(shuō),RDD持久化,是非常重要的。
如何持久化
要持久化一個(gè)RDD,只要調(diào)用其cache()
或者persist()
方法即可。在該RDD第一次被計(jì)算出來(lái)時(shí),就會(huì)直接緩存在每個(gè)節(jié)點(diǎn)中。而且Spark的持久化機(jī)制還是自動(dòng)容錯(cuò)的,如果持久化的RDD的任何partition丟失了,那么Spark會(huì)自動(dòng)通過(guò)其源RDD,使用transformation操作重新計(jì)算該partition。
cache()
和persist()
的區(qū)別在于,cache()
是persist()
的一種簡(jiǎn)化方式,cache()
的底層就是調(diào)用的persist()
的無(wú)參版本,同時(shí)就是調(diào)用persist(MEMORY_ONLY)
,將數(shù)據(jù)持久化到內(nèi)存中。如果需要從內(nèi)存中去除緩存,那么可以使用unpersist()
方法。
RDD持久化存儲(chǔ)級(jí)別
RDD存儲(chǔ)級(jí)別主要有以下幾種。
級(jí)別 | 使用空間 | CPU時(shí)間 | 是否在內(nèi)存中 | 是否在磁盤(pán)上 | 備注 |
---|---|---|---|---|---|
MEMORY_ONLY | 高 | 低 | 是 | 否 | |
MEMORY_ONLY_2 | 高 | 低 | 是 | 否 | 數(shù)據(jù)存2份 |
MEMORY_ONLY_SER | 低 | 高 | 是 | 否 | 數(shù)據(jù)序列化 |
MEMORY_ONLY_SER_2 | 低 | 高 | 是 | 否 | 數(shù)據(jù)序列化,數(shù)據(jù)存2份 |
MEMORY_AND_DISK | 高 | 中等 | 部分 | 部分 | 如果數(shù)據(jù)在內(nèi)存中放不下,則溢寫(xiě)到磁盤(pán) |
MEMORY_AND_DISK_2 | 高 | 中等 | 部分 | 部分 | 數(shù)據(jù)存2份 |
MEMORY_AND_DISK_SER | 低 | 高 | 部分 | 部分 | |
MEMORY_AND_DISK_SER_2 | 低 | 高 | 部分 | 部分 | 數(shù)據(jù)存2份 |
DISK_ONLY | 低 | 高 | 否 | 是 | |
DISK_ONLY_2 | 低 | 高 | 否 | 是 | 數(shù)據(jù)存2份 |
OFF_HEAP |
注意:只能設(shè)置一種:不然會(huì)拋異常:Cannot change storage level of an RDD after it was already assigned a level
異常源碼如下:
private def persist(newLevel: StorageLevel, allowOverride: Boolean): this.type = { // TODO: Handle changes of StorageLevel if (storageLevel != StorageLevel.NONE && newLevel != storageLevel && !allowOverride) { throw new UnsupportedOperationException( "Cannot change storage level of an RDD after it was already assigned a level") } // If this is the first time this RDD is marked for persisting, register it // with the SparkContext for cleanups and accounting. Do this only once. if (storageLevel == StorageLevel.NONE) { sc.cleaner.foreach(_.registerRDDForCleanup(this)) sc.persistRDD(this) } storageLevel = newLevel this }
MEMORY_ONLY
使用未序列化的Java對(duì)象格式,將數(shù)據(jù)保存在內(nèi)存中。如果內(nèi)存不夠存放所有的數(shù)據(jù),則數(shù)據(jù)可能就不會(huì)進(jìn)行持久化。那么下次對(duì)這個(gè)RDD執(zhí)行算子操作時(shí),那些沒(méi)有被持久化的數(shù)據(jù),需要從源頭處重新計(jì)算一遍。這是默認(rèn)的持久化策略,使用cache()方法時(shí),實(shí)際就是使用的這種持久化策略。
MEMORY_ONLY_SER
基本含義同MEMORY_ONLY。唯一的區(qū)別是,會(huì)將RDD中的數(shù)據(jù)進(jìn)行序列化,RDD的每個(gè)partition會(huì)被序列化成一個(gè)字節(jié)數(shù)組。這種方式更加節(jié)省內(nèi)存,從而可以避免持久化的數(shù)據(jù)占用過(guò)多內(nèi)存導(dǎo)致頻繁GC。
MEMORY_AND_DISK
使用未序列化的Java對(duì)象格式,優(yōu)先嘗試將數(shù)據(jù)保存在內(nèi)存中。如果內(nèi)存不夠存放所有的數(shù)據(jù),會(huì)將數(shù)據(jù)寫(xiě)入磁盤(pán)文件中,下次對(duì)這個(gè)RDD執(zhí)行算子時(shí),持久化在磁盤(pán)文件中的數(shù)據(jù)會(huì)被讀取出來(lái)使用。
MEMORY_AND_DISK_SER
基本含義同MEMORY_AND_DISK。唯一的區(qū)別是,會(huì)將RDD中的數(shù)據(jù)進(jìn)行序列化,RDD的每個(gè)partition會(huì)被序列化成一個(gè)字節(jié)數(shù)組。這種方式更加節(jié)省內(nèi)存,從而可以避免持久化的數(shù)據(jù)占用過(guò)多內(nèi)存導(dǎo)致頻繁GC。
DISK_ONLY
使用未序列化的Java對(duì)象格式,將數(shù)據(jù)全部寫(xiě)入磁盤(pán)文件中。
OFF_HEAP
這個(gè)目前是試驗(yàn)型選項(xiàng),類(lèi)似MEMORY_ONLY_SER,但是數(shù)據(jù)是存儲(chǔ)在堆外內(nèi)存的。
后綴帶“_2”的存儲(chǔ)級(jí)別
對(duì)于上述任意一種持久化策略,如果加上后綴_2,代表的是將每個(gè)持久化的數(shù)據(jù),都復(fù)制一份副本,并將副本保存到其他節(jié)點(diǎn)上。這種基于副本的持久化機(jī)制主要用于進(jìn)行容錯(cuò)。假如某個(gè)節(jié)點(diǎn)掛掉了,節(jié)點(diǎn)的內(nèi)存或磁盤(pán)中的持久化數(shù)據(jù)丟失了,那么后續(xù)對(duì)RDD計(jì)算時(shí)還可以使用該數(shù)據(jù)在其他節(jié)點(diǎn)上的副本。如果沒(méi)有副本的話,就只能將這些數(shù)據(jù)從源頭處重新計(jì)算一遍了。
如何選擇一種最合適的持久化策略
- 默認(rèn)情況下,性能最高的當(dāng)然是MEMORY_ONLY,但前提是你的內(nèi)存必須足夠足夠大,可以綽綽有余地存放下整個(gè)RDD的所有數(shù)據(jù)。因?yàn)椴贿M(jìn)行序列化與反序列化操作,就避免了這部分的性能開(kāi)銷(xiāo);對(duì)這個(gè)RDD的后續(xù)算子操作,都是基于純內(nèi)存中的數(shù)據(jù)的操作,不需要從磁盤(pán)文件中讀取數(shù)據(jù),性能也很高;而且不需要復(fù)制一份數(shù)據(jù)副本,并遠(yuǎn)程傳送到其他節(jié)點(diǎn)上。但是這里必須要注意的是,在實(shí)際的生產(chǎn)環(huán)境中,恐怕能夠直接用這種策略的場(chǎng)景還是有限的,如果RDD中數(shù)據(jù)比較多時(shí)(比如幾十億),直接用這種持久化級(jí)別,會(huì)導(dǎo)致JVM的OOM內(nèi)存溢出異常。
- 如果使用MEMORY_ONLY級(jí)別時(shí)發(fā)生了內(nèi)存溢出,那么建議嘗試使用MEMORY_ONLY_SER級(jí)別。該級(jí)別會(huì)將RDD數(shù)據(jù)序列化后再保存在內(nèi)存中,此時(shí)每個(gè)partition僅僅是一個(gè)字節(jié)數(shù)組而已,大大減少了對(duì)象數(shù)量,并降低了內(nèi)存占用。這種級(jí)別比MEMORY_ONLY多出來(lái)的性能開(kāi)銷(xiāo),主要就是序列化與反序列化的開(kāi)銷(xiāo)。但是后續(xù)算子可以基于純內(nèi)存進(jìn)行操作,因此性能總體還是比較高的。此外,可能發(fā)生的問(wèn)題同上,如果RDD中的數(shù)據(jù)量過(guò)多的話,還是可能會(huì)導(dǎo)致OOM內(nèi)存溢出的異常。
- 如果純內(nèi)存的級(jí)別都無(wú)法使用,那么建議使用MEMORY_AND_DISK_SER策略,而不是MEMORY_AND_DISK策略。因?yàn)榧热坏搅诉@一步,就說(shuō)明RDD的數(shù)據(jù)量很大,內(nèi)存無(wú)法完全放下。序列化后的數(shù)據(jù)比較少,可以節(jié)省內(nèi)存和磁盤(pán)的空間開(kāi)銷(xiāo)。同時(shí)該策略會(huì)優(yōu)先盡量嘗試將數(shù)據(jù)緩存在內(nèi)存中,內(nèi)存緩存不下才會(huì)寫(xiě)入磁盤(pán)。
- 通常不建議使用DISK_ONLY和后綴為_(kāi)2的級(jí)別:因?yàn)橥耆诖疟P(pán)文件進(jìn)行數(shù)據(jù)的讀寫(xiě),會(huì)導(dǎo)致性能急劇降低,有時(shí)還不如重新計(jì)算一次所有RDD。后綴為_(kāi)2的級(jí)別,必須將所有數(shù)據(jù)都復(fù)制一份副本,并發(fā)送到其他節(jié)點(diǎn)上,數(shù)據(jù)復(fù)制以及網(wǎng)絡(luò)傳輸會(huì)導(dǎo)致較大的性能開(kāi)銷(xiāo),除非是要求作業(yè)的高可用性,否則不建議使用。
如何使用 Spark rdd 緩存
調(diào)用rdd.persist()
變量可以這樣設(shè)置,如:rdd.persist(StorageLevel.MEMORY_ONLY)
;這里使用了MEMORY_ONLY級(jí)別存儲(chǔ)。當(dāng)然也可以選擇其他的如:rdd.persist(StorageLevel.DISK_ONLY())
。
調(diào)用rdd.cache()
cache()
是rdd.persist(StorageLevel.MEMORY_ONLY)
的簡(jiǎn)寫(xiě),效果和他一模一樣的。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-843311.html
調(diào)用rdd.unpersist()清除緩存
rdd.unpersist()
把緩存起來(lái)的RDD清除,后續(xù)如果用到該RDD,則需要重新計(jì)算。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-843311.html
到了這里,關(guān)于Spark RDD 緩存機(jī)制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!