廖旺堅(jiān),黃永峰,包從開
(1.清華大學(xué)電子工程系,北京 100084;2.清華大學(xué)信息科學(xué)與技術(shù)國(guó)家實(shí)驗(yàn)室(籌),北京 100084)
集群并行計(jì)算是指在大量性能較低、可靠性較差的通用計(jì)算機(jī)上并行完成巨大的計(jì)算任務(wù),是大數(shù)據(jù)時(shí)代滿足低成本/高計(jì)算能力需求的最佳選擇。Hadoop是一個(gè)開源的集群并行計(jì)算框架,包含MapReduce計(jì)算模型和Hadoop分布式文件系統(tǒng)HDFS(Hadoop Distributed File System)存儲(chǔ)模型,具有硬件要求低、吞吐量大、容錯(cuò)性好的特點(diǎn),滿足了大數(shù)據(jù)計(jì)算的需要,成為當(dāng)前工業(yè)界最流行的計(jì)算框架,也是學(xué)術(shù)界研究熱點(diǎn)。但是,隨著數(shù)據(jù)量迅猛增長(zhǎng),計(jì)算復(fù)雜度不斷增加,計(jì)算實(shí)時(shí)性越來越高,Hadoop計(jì)算速度較慢的弱點(diǎn)逐漸顯現(xiàn)。于是,Zaharia等[1 - 3]基于Hadoop,在前人不斷改進(jìn)的基礎(chǔ)上重新實(shí)現(xiàn)其調(diào)度模型,形成了Spark計(jì)算框架。
Spark是一個(gè)快速、通用的大規(guī)模數(shù)據(jù)處理引擎[4],具有快速、易用、通用、兼容性好的特點(diǎn)。它使用有向無環(huán)圖DAG(Directed Acyclic Graph)進(jìn)行作業(yè)調(diào)度,根據(jù)依賴關(guān)系將不同作業(yè)分解成大量子任務(wù)組成的DAG,減少了不同作業(yè)之間的數(shù)據(jù)傳遞過程,并且將前后作業(yè)間的中間數(shù)據(jù)使用內(nèi)存緩存?zhèn)鬟f,使用線程池運(yùn)行計(jì)算任務(wù),使得計(jì)算速度相比傳統(tǒng)的Hadoop MapReduce有100倍量級(jí)的提升[3]。
目前對(duì)Spark性能的研究主要有三類:第一類是在Spark模型層面對(duì)作業(yè)調(diào)度、底層資源管理等方面做改進(jìn)和優(yōu)化;第二類是在提交Spark作業(yè)時(shí)改變不同的參數(shù),找到最優(yōu)值;第三類是在應(yīng)用層面,編程時(shí)使用不同的實(shí)現(xiàn)方法,提高作業(yè)運(yùn)行性能。Ousterhout等[5]對(duì)Spark性能做了詳盡測(cè)試和分析,認(rèn)為CPU可以優(yōu)化的地方較少,而網(wǎng)絡(luò)使用遠(yuǎn)未達(dá)到飽和狀態(tài),其優(yōu)化對(duì)性能的影響很小,但該文獻(xiàn)關(guān)于內(nèi)存對(duì)性能的影響很少提及。陳僑安等[6]對(duì)大量舊任務(wù)提交參數(shù)和性能進(jìn)行分析,對(duì)比任務(wù)相似度,預(yù)測(cè)出新任務(wù)最適合的參數(shù),屬于第二類的方法。楊志偉等[7]改變?nèi)蝿?wù)調(diào)度策略,優(yōu)先將任務(wù)分發(fā)給資源空余度高的結(jié)點(diǎn),提高集群整體運(yùn)行性能,屬于第一類的方法。Gog等[8]采用內(nèi)存分區(qū)管理的思路,給應(yīng)用中處于生命周期的數(shù)據(jù)對(duì)象劃定內(nèi)存區(qū)塊,運(yùn)行時(shí)不對(duì)此區(qū)塊進(jìn)行GC(Garbage Collector)操作,但這樣需要給每個(gè)對(duì)象指定明確的生命周期,使得編程更為復(fù)雜。Gidra等[9]針對(duì)分布式系統(tǒng)的GC進(jìn)行改造,使各結(jié)點(diǎn)GC進(jìn)程互相發(fā)布消息,避免無效的GC操作,提高運(yùn)行效率,但增加了結(jié)點(diǎn)間通信消耗。
Spark的特點(diǎn)是大量使用內(nèi)存提高計(jì)算速度,因此內(nèi)存資源是Spark最基本的瓶頸,通過內(nèi)存優(yōu)化,可以提升Spark的性能。本文針對(duì)第二類和第三類方法存在的不足,圍繞內(nèi)存在Spark作業(yè)中的分配方法和作用原理,提出不同的內(nèi)存優(yōu)化策略,并設(shè)計(jì)了相應(yīng)的實(shí)驗(yàn)進(jìn)行論證。
Spark的作業(yè)執(zhí)行流程如圖1所示,用戶向Driver結(jié)點(diǎn)提交需要運(yùn)行的作業(yè)后,Driver結(jié)點(diǎn)創(chuàng)建SparkContext。SparkContext根據(jù)程序中的Action操作,將作業(yè)劃分為連續(xù)的Job流,并依次提交到有向無環(huán)圖調(diào)度器DAGScheduler。DAGScheduler對(duì)提交的Job進(jìn)行解析,生成由不同Stage流組成的有向無環(huán)圖,然后依次將每個(gè)Stage提交到線程調(diào)度器TaskSchedulerImpl。TashSchedulerImpl為Stage生成線程集TaskSet,并將TaskSet分發(fā)到各個(gè)工作結(jié)點(diǎn)Worker的線程管理器TaskManager。TaskManager根據(jù)收到的信息啟動(dòng)Task開始運(yùn)算,運(yùn)算結(jié)束后得到的數(shù)據(jù)可能被下一個(gè)Stage所使用,稱為中間數(shù)據(jù),由數(shù)據(jù)塊管理器BlockManager進(jìn)行管理。BlockManager在內(nèi)存充足的時(shí)候?qū)⒅虚g數(shù)據(jù)存儲(chǔ)到內(nèi)存,以提高訪問速度,在內(nèi)存不夠的時(shí)候?qū)⒅虚g數(shù)據(jù)存儲(chǔ)到本地磁盤、HDFS或丟棄。
Figure 1 Spark application execution flow圖1 Spark作業(yè)執(zhí)行流程
Spark作業(yè)運(yùn)行在Java進(jìn)程中,一個(gè)Java進(jìn)程對(duì)應(yīng)一個(gè)JVM。JVM在運(yùn)行過程中管理的內(nèi)存空間叫堆空間,用于存儲(chǔ)程序中創(chuàng)建的對(duì)象,在運(yùn)行過程中可根據(jù)配置動(dòng)態(tài)變化。如圖2所示,JVM堆空間分為年輕代(Young Generation)、年老代(Old Generation)和持久代(Permanent Generation),一般年輕代存放短生命周期的對(duì)象,年老代存放長(zhǎng)生命周期的對(duì)象,持久代存放類、方法等元數(shù)據(jù),年輕代又可分為Eden、Survivor1(S1)和Survivor2(S2)區(qū)間。
Figure 2 Memory management comparison between Spark and JVM圖2 Spark和JVM的內(nèi)存管理對(duì)應(yīng)關(guān)系
JVM運(yùn)行過程中會(huì)使用垃圾收集器GC對(duì)內(nèi)存數(shù)據(jù)進(jìn)行清理。新生成的對(duì)象存放在Eden區(qū)間,Eden區(qū)間滿了后,會(huì)觸發(fā)Minor GC,將Eden和Survivor1區(qū)間仍然存活的對(duì)象復(fù)制到Survivor2區(qū)間。下一次Eden區(qū)間再滿,再觸發(fā)Minor GC,將Eden和Survivor2區(qū)間仍然存活的對(duì)象復(fù)制到Survivor1區(qū)間,如此不斷往復(fù)。當(dāng)對(duì)象生命周期足夠長(zhǎng)或Survivor區(qū)間滿了后,對(duì)象會(huì)被轉(zhuǎn)移到年老代區(qū)域。當(dāng)年老代快滿的時(shí)候,會(huì)觸發(fā)Full GC,清理年老代不需要使用的對(duì)象。當(dāng)持久代有類不需再使用并且需要空間存放別的數(shù)據(jù)時(shí),會(huì)觸發(fā)Major GC,清理持久代的數(shù)據(jù)。
當(dāng)GC被觸發(fā)時(shí),JVM中所有運(yùn)行的線程會(huì)暫時(shí)停止運(yùn)行,等待GC完成后繼續(xù)。如果一次作業(yè)中GC被觸發(fā)的次數(shù)特別多,就會(huì)顯著延長(zhǎng)作業(yè)執(zhí)行總時(shí)間,降低系統(tǒng)性能。其中Full GC需要時(shí)間比Minor GC更長(zhǎng),對(duì)性能影響更大。Java官方文檔[10]顯示,在理想情況下,使用30%的時(shí)間作為GC消耗,在使用2個(gè)CPU內(nèi)核時(shí),系統(tǒng)吞吐量會(huì)下降到80%,而使用32個(gè)CPU內(nèi)核時(shí),系統(tǒng)吞吐量會(huì)下降到10%。而Spark作業(yè)處理大規(guī)模數(shù)據(jù),通常都會(huì)使用較多的CPU內(nèi)核,因此GC對(duì)性能的影響需要引起重視。
Spark框架同樣對(duì)所使用的內(nèi)存進(jìn)行管理,其管理的內(nèi)存主要有兩種用途。如圖2所示,第一種是用于程序執(zhí)行,如join、sort、shuffle、aggregation等算子執(zhí)行時(shí)使用的內(nèi)存;第二種是用于存儲(chǔ),如RDD(Resilient Distributed Datasets)數(shù)據(jù)緩存和廣播變量存儲(chǔ)等。用于這兩種用途的內(nèi)存使用一個(gè)統(tǒng)一的空間(如圖2中的M,其存儲(chǔ)空間大小為M),Spark使用一個(gè)參數(shù)spark.memory.fraction設(shè)置M的大?。?/p>
spark.memory.fraction=M/(Java堆空間-300 MB)(默認(rèn)0.6)
在內(nèi)存M中,一部分用于任務(wù)執(zhí)行(如圖2中的Execution,其空間大小為E),一部分用于緩存數(shù)據(jù)(如圖2的Storage,其存儲(chǔ)空間大小為R)。Spark使用參數(shù)spark.memory.storageFraction設(shè)置兩者比例:
spark.memory.storageFraction=R/M(默認(rèn)0.5)
從圖2可以看出,Spark管理的內(nèi)存和JVM管理的堆空間屬于同一段物理內(nèi)存空間,只是在不同層面上進(jìn)行管理。Spark管理的內(nèi)存基本等于JVM堆空間的年輕代和年老代的空間,一部分用于執(zhí)行運(yùn)算和存儲(chǔ)(M),另一部分空間用于Spark的類等元數(shù)據(jù)的存儲(chǔ)。
我們對(duì)影響Spark作業(yè)執(zhí)行性能的主要因素進(jìn)行分類,如圖3所示。
Figure 3 Influencing factors of Spark application performance圖3 Spark作業(yè)性能影響因素示意圖
圖3中可以看出,目前對(duì)于Spark的優(yōu)化一般從修改源代碼改變系統(tǒng)作業(yè)的執(zhí)行模型,提交作業(yè)時(shí)調(diào)整運(yùn)行參數(shù)和編寫作業(yè)程序時(shí)算法優(yōu)化等幾個(gè)方面入手。系統(tǒng)運(yùn)行參數(shù)又可分為CPU參數(shù)、內(nèi)存參數(shù)和I/O網(wǎng)絡(luò)參數(shù)等。CPU參數(shù)配置包括單執(zhí)行容器使用的核數(shù)、單核的線程數(shù)等。網(wǎng)絡(luò)和I/O的配置包括緩沖區(qū)大小、數(shù)據(jù)分塊大小和文件并發(fā)數(shù)量等。內(nèi)存的運(yùn)行參數(shù)配置方面,可以通過改變單容器內(nèi)存大小、對(duì)緩存數(shù)據(jù)進(jìn)行預(yù)處理、改變JVM和Spark管理內(nèi)存的分配比例等,對(duì)作業(yè)性能產(chǎn)生影響。
一般數(shù)據(jù)從磁盤讀取到內(nèi)存以后,占用空間都會(huì)不同程度地增大。這主要是由于Java的對(duì)象存儲(chǔ)格式所決定的,如每個(gè)String對(duì)象會(huì)占用40 Bytes空間存儲(chǔ)文本宏信息,則平均每行40個(gè)字符的中文數(shù)據(jù)集轉(zhuǎn)化成RDD后,占用內(nèi)存會(huì)比原始數(shù)據(jù)大50%。而對(duì)于英文文本來說,內(nèi)存中存儲(chǔ)的UNICODE格式或UTF-16格式的字符,占用內(nèi)存空間會(huì)比磁盤上存儲(chǔ)的UTF-8或ASCII碼格式大一倍。
Spark將作業(yè)運(yùn)行過程中產(chǎn)生的RDD緩存到內(nèi)存時(shí),RDD中每一行的數(shù)據(jù)指針等頭數(shù)據(jù)都會(huì)緩存下來,而這會(huì)占用大量?jī)?nèi)存空間。序列化可以將RDD轉(zhuǎn)化為字節(jié)序列存儲(chǔ),減小緩存數(shù)據(jù)占用空間,如果序列化后再使用壓縮算法對(duì)字節(jié)序列進(jìn)行壓縮,能更大程度減少空間占用。特別是在內(nèi)存空間緊張時(shí),減少緩存數(shù)據(jù)大小,更多內(nèi)存可以用于代碼執(zhí)行,減少系統(tǒng)的GC觸發(fā)次數(shù),有效避免或減少對(duì)外存的使用,提高運(yùn)行性能。
使用硬件資源越多,系統(tǒng)性能越好,這是業(yè)界普遍的認(rèn)識(shí)。數(shù)據(jù)在內(nèi)存中緩存是以RDD分塊的方式存在的,分塊的大小默認(rèn)跟HDFS存儲(chǔ)文件的分塊大小一致。作業(yè)運(yùn)行過程中,如果空閑內(nèi)存不夠存下整個(gè)分塊,系統(tǒng)會(huì)移除掉部分舊的緩存數(shù)據(jù),存放新的數(shù)據(jù)。如果移除掉舊的部分?jǐn)?shù)據(jù)還不夠的話,系統(tǒng)會(huì)放棄使用內(nèi)存緩存,改用外部存儲(chǔ)緩存或不使用緩存。
對(duì)于數(shù)據(jù)量大而計(jì)算代價(jià)小的作業(yè)而言,使用足夠大的內(nèi)存,可以使得內(nèi)存在緩存全部數(shù)據(jù)后還有足夠的空間執(zhí)行代碼,作業(yè)執(zhí)行過程不會(huì)觸發(fā)GC,系統(tǒng)性能可以達(dá)到最佳。而在可使用的運(yùn)行內(nèi)存較小時(shí),系統(tǒng)調(diào)度策略會(huì)發(fā)生改變,減少數(shù)據(jù)的緩存操作,改由重新計(jì)算得到原來緩存的數(shù)據(jù)。如果減少緩存的數(shù)據(jù)很大,則騰出來的內(nèi)存空間更多,反而會(huì)降低JVM的GC觸發(fā)次數(shù),在重新計(jì)算消耗小的情況下,會(huì)使得整個(gè)系統(tǒng)運(yùn)行過程穩(wěn)定,性能提高。
文獻(xiàn)[10]指出,JVM的GC算法是基于“Java程序中大部分的對(duì)象都是短生命周期”這一假設(shè),即大部分的對(duì)象在Minor GC時(shí)就會(huì)被清理掉,沒機(jī)會(huì)轉(zhuǎn)入到年老代,因此GC過程中Full GC較少,對(duì)系統(tǒng)性能影響不大。而Spark中緩存數(shù)據(jù)在整個(gè)作業(yè)期間會(huì)一直存在,對(duì)于JVM來說都是長(zhǎng)生命周期對(duì)象,并且普遍數(shù)據(jù)量大,占用大量的JVM堆空間。大數(shù)據(jù)背景下的計(jì)算任務(wù),一個(gè)數(shù)據(jù)塊很容易就占滿所有的年老代空間,導(dǎo)致傳統(tǒng)GC不能起到應(yīng)有的作用,反而降低系統(tǒng)性能。通過改變JVM中年輕代和年老代的比例,改變Spark中執(zhí)行內(nèi)存和存儲(chǔ)內(nèi)存的比例,可以調(diào)節(jié)JVM的GC消耗,減少不利影響,提高系統(tǒng)性能。
下一節(jié)我們運(yùn)行大量Spark作業(yè),使用上述不同策略優(yōu)化,驗(yàn)證不同優(yōu)化策略下參數(shù)配置對(duì)系統(tǒng)性能的影響。
為驗(yàn)證和分析優(yōu)化策略的效果,搭建了Spark運(yùn)行平臺(tái)。Spark計(jì)算層設(shè)置4個(gè)結(jié)點(diǎn),其中1個(gè)主結(jié)點(diǎn),3個(gè)工作結(jié)點(diǎn),每個(gè)工作結(jié)點(diǎn)32 GB內(nèi)存,8個(gè)CPU內(nèi)核。HDFS存儲(chǔ)層設(shè)置6個(gè)結(jié)點(diǎn),其中1個(gè)主結(jié)點(diǎn),5個(gè)數(shù)據(jù)結(jié)點(diǎn),HDFS文件塊大小設(shè)置為128 MB。
為驗(yàn)證優(yōu)化策略,設(shè)計(jì)了兩類計(jì)算任務(wù):
任務(wù)A對(duì)不同大小的中文微博數(shù)據(jù)集(1 GB~100 GB)進(jìn)行詞頻統(tǒng)計(jì)。任務(wù)要求將數(shù)據(jù)集讀出后使用不同的方式緩存,然后做詞頻統(tǒng)計(jì)工作。
任務(wù)B對(duì)2 227萬條60維的向量數(shù)據(jù)聚類計(jì)算,數(shù)據(jù)以文本的格式存儲(chǔ)在HDFS上,共9.82 GB,79個(gè)分塊。任務(wù)要求將數(shù)據(jù)讀入后,轉(zhuǎn)化為Vector向量,然后再以不同方式緩存,最后進(jìn)行迭代次數(shù)不大于8次的KMeans聚類操作。
實(shí)驗(yàn)過程中,采用不同的參數(shù)配置運(yùn)行A類任務(wù)和B類任務(wù),觀察任務(wù)運(yùn)行時(shí)間、GC詳情、線程執(zhí)行時(shí)間、數(shù)據(jù)塊存取、資源占用情況等指標(biāo),并作記錄和分析。
表1顯示了任務(wù)A的數(shù)據(jù)經(jīng)過不同方式處理后,緩存占用的空間大小和作業(yè)運(yùn)行時(shí)間。任務(wù)中數(shù)據(jù)集為1 096 MB的中文微博文本數(shù)據(jù),共8 546萬行??梢钥闯?,Spark將數(shù)據(jù)導(dǎo)入后,占用空間比在磁盤占用空間增加17%,而序列化以后,空間減少14%,如果序列化后再進(jìn)行壓縮,則占用空間比不處理減少42%。在運(yùn)行時(shí)間上,壓縮比不壓縮平均降低運(yùn)行時(shí)間2 s,同時(shí)GC時(shí)間減少80%,說明緩存空間的減少能夠有效降低GC消耗。但是,最終不處理情況下運(yùn)行時(shí)間是最小的,因?yàn)樾蛄谢蛪嚎s,包括讀取緩存后的反序列化和解壓縮過程本身需要占用資源。序列化后再壓縮相比序列化后不壓縮,雖然壓縮過程需要時(shí)間,但壓縮后占用空間更少,GC消耗大大降低,降低的GC消耗時(shí)間遠(yuǎn)超過壓縮過程增加的時(shí)間,總作業(yè)運(yùn)行時(shí)間反而比序列化后不壓縮降低18%。
Table 1 Effects of different processingmethods on data size and performance表1 不同處理方式對(duì)數(shù)據(jù)大小和運(yùn)行性能的影響
圖4顯示了對(duì)任務(wù)B用不同緩存方式處理后的性能。任務(wù)中數(shù)據(jù)直接緩存占用空間約10.7 GB,序列化后占用空間約10.3 GB。如果使用壓縮后再緩存的方式,占用空間約7.8 GB,減少24%,與文本數(shù)據(jù)的空間占用規(guī)律基本一致??梢钥闯觯谥苯邮褂孟到y(tǒng)內(nèi)存和HDFS緩存中間數(shù)據(jù)的情況下,隨著提交內(nèi)存的增加,系統(tǒng)運(yùn)行性能提高,這是因?yàn)殡S著內(nèi)存的增加減少了對(duì)HDFS的使用,所以運(yùn)行速度增加。如果將外部存儲(chǔ)方式由HDFS改為DISK,我們可以看到,在內(nèi)存較少的情況下,系統(tǒng)運(yùn)行時(shí)間比HDFS方式降低很多,因?yàn)槿コ司W(wǎng)絡(luò)消耗和多備份消耗后,本地硬盤的讀寫比HDFS讀寫消耗的時(shí)間要短。
Figure 4 Performance comparison of different caching modes圖4 不同緩存方式性能對(duì)比
從以上兩例中可以看出,減小緩存占用的空間可以減少讀寫時(shí)間,降低GC消耗,提高系統(tǒng)性能。但序列化和壓縮兩種減少空間的過程本身需要時(shí)間,在“一次緩存,多次使用”和內(nèi)存嚴(yán)重受限的情況下,適用于這種方式提高系統(tǒng)性能。而在內(nèi)存不夠時(shí),用本地磁盤代替HDFS存儲(chǔ)中間數(shù)據(jù),能夠降低讀寫時(shí)間消耗,在通常的情況下都能比較適用。
圖5顯示了任務(wù)B在提交作業(yè)時(shí)使用不同內(nèi)存大小,對(duì)任務(wù)運(yùn)行的總時(shí)間和GC占用的時(shí)間的影響。可以看出,GC時(shí)間和運(yùn)行總時(shí)間有明顯的正相關(guān)性,GC耗時(shí)長(zhǎng)則運(yùn)行總時(shí)間長(zhǎng)。在內(nèi)存小的時(shí)候,GC用時(shí)明顯較大,在內(nèi)存大的時(shí)候,GC時(shí)間則非常之少。當(dāng)內(nèi)存從1 500 MB到4 000 MB時(shí),GC時(shí)間逐步下降,任務(wù)運(yùn)行時(shí)間也隨內(nèi)存增多而下降。內(nèi)存大于6 000 MB以后,GC用時(shí)基本可以忽略不計(jì),而運(yùn)行總時(shí)間趨于穩(wěn)定。在內(nèi)存小于1 600 MB時(shí),提交內(nèi)存越大反而運(yùn)行時(shí)間越長(zhǎng),這符合策略3分析的結(jié)果。不過直接計(jì)算速度比不上內(nèi)存緩存,所以相比最佳性能還是差一些。當(dāng)內(nèi)存在1 500 MB左右時(shí),GC用時(shí)和任務(wù)運(yùn)行總時(shí)間達(dá)到最高,GC用時(shí)占到了總用時(shí)的30%。
從這里可以看出,在提交Spark作業(yè)的時(shí)候,在內(nèi)存足夠的情況下,應(yīng)盡量使用更多的內(nèi)存,而內(nèi)存有限的情況下,也并不是內(nèi)存越少性能就越差,同時(shí)也要避免如實(shí)驗(yàn)中“1 500 MB”這樣的區(qū)間。
Figure 5 Task running time and GC time scatter distribution圖5 任務(wù)運(yùn)行時(shí)間與GC時(shí)間散點(diǎn)分布
圖6展示了任務(wù)B運(yùn)行過程中所有緩存的數(shù)據(jù)量,包括緩存在內(nèi)存和磁盤的,以及系統(tǒng)因?yàn)閮?nèi)存空間不夠,為緩存新數(shù)據(jù)而從磁盤移除的老數(shù)據(jù)量。可以看出,在運(yùn)行內(nèi)存較小的時(shí)候,系統(tǒng)使用內(nèi)存緩存和磁盤緩存都小,但都隨內(nèi)存增加而增大。而到1 600 MB以后,內(nèi)存緩存基本保持穩(wěn)定,磁盤緩存則顯著下降。說明內(nèi)存足夠大以后,只用內(nèi)存就能滿足所有緩存要求,所以磁盤使用量下降,同時(shí)也越來越不需要移除內(nèi)存給新的數(shù)據(jù)騰空間,所以內(nèi)存緩存移除數(shù)據(jù)同步下降,最后趨于零。前面內(nèi)存越小,內(nèi)存緩存也越小,但磁盤緩存也越小,同圖5類似,說明系統(tǒng)根據(jù)參數(shù)減少了緩存的使用,使用重新計(jì)算的方式得到需要的數(shù)據(jù),這一部分需要消耗部分時(shí)間,但比使用磁盤緩存耗時(shí)反而更低。
Figure 6 Cache data trends圖6 緩存數(shù)據(jù)變化趨勢(shì)
綜合圖5和圖6可以看出,Spark作業(yè)運(yùn)行時(shí)間與GC時(shí)間、存儲(chǔ)設(shè)備讀寫時(shí)間等都密切相關(guān),由多種因素決定。在數(shù)據(jù)量太大而內(nèi)存遠(yuǎn)遠(yuǎn)不夠的情況下運(yùn)行任務(wù),酌情減少內(nèi)存的使用,可能會(huì)達(dá)到更好的效果。
圖7展示了使用不同的JVMNewRatio參數(shù)運(yùn)行任務(wù)B的結(jié)果。NewRatio參數(shù)代表JVM中年老代和年輕代大小的比例,我們分別設(shè)置為1、2、4、8、15,對(duì)比運(yùn)行效果。從圖7中可以看出,當(dāng)NewRatio參數(shù)越來越大,也就是堆空間中年老代占比越來越多的時(shí)候,系統(tǒng)的Minor GC時(shí)間增加,而Full GC時(shí)間減少,任務(wù)運(yùn)行的總時(shí)間減少,在參數(shù)為8時(shí)達(dá)到最小值,此時(shí)性能比參數(shù)為1時(shí)提升25%,比默認(rèn)參數(shù)提升10%。
Figure 7 Running time and GC time in different heap space ratio圖7 不同堆空間比例時(shí)作業(yè)運(yùn)行時(shí)間和GC時(shí)間
這是因?yàn)镾park任務(wù)中緩存的數(shù)據(jù)一般會(huì)保持較長(zhǎng)時(shí)間,并且比較大,在NewRatio較大,即年輕代較小、年老代較大的情況下,會(huì)很快占滿年輕代的Eden區(qū)間,引發(fā)Minor GC,所以Minor GC次數(shù)比較多,時(shí)間比較長(zhǎng),但單次Minor GC的時(shí)間會(huì)短,因?yàn)槟贻p代空間較小,清理對(duì)象較快,如圖8所示。而由于年老代空間較大,對(duì)象的生命周期容忍度比較大,所以年老代的Full GC次數(shù)比較少。從圖8中可以看見,單次Full GC時(shí)間大約是單次Minor GC時(shí)間的10~20倍,因此Full GC的次數(shù)更能影響系統(tǒng)的性能。
Figure 8 Average time of single GC in different heap space ratio圖8 不同堆空間比例時(shí)平均單次GC時(shí)間對(duì)比
Spark有兩個(gè)參數(shù)調(diào)節(jié)管理內(nèi)存的分配,spark.memory.fraction表示Spark管理的內(nèi)存中執(zhí)行和存儲(chǔ)部分占的比例,實(shí)驗(yàn)使用任務(wù)B在0.9~0.002測(cè)試,結(jié)果如圖9所示。從圖9中可以發(fā)現(xiàn),當(dāng)比例在0.001時(shí)運(yùn)行時(shí)間達(dá)到最小值,也就是說提交運(yùn)行內(nèi)存為2 200 MB時(shí),只使用不到20 MB作為執(zhí)行代碼和存儲(chǔ)使用的內(nèi)存,此時(shí)性能比最低水平提升50%,比平均水平提升30%。查找監(jiān)測(cè)數(shù)據(jù)可以發(fā)現(xiàn),該比例時(shí)運(yùn)行作業(yè),JVM只有Minor GC,同時(shí)也幾乎沒有中間數(shù)據(jù)存儲(chǔ)在內(nèi)存和硬盤,因?yàn)榇藭r(shí)Spark使用的內(nèi)存大小遠(yuǎn)小于JVM年老代大小,所以根本不會(huì)觸發(fā)Full GC,在內(nèi)存緩存很小很小的情況下,GC時(shí)間最短的性能最高。
Figure 9 Running time of different Spark memory execution storage ratio圖9 不同Spark內(nèi)存執(zhí)行存儲(chǔ)比例時(shí)運(yùn)行時(shí)間
本文分析Spark運(yùn)行過程中JVM層面和Spark層面對(duì)內(nèi)存的管理機(jī)制,從減少數(shù)據(jù)量、改變提交內(nèi)存大小、改變JVM堆空間內(nèi)存分配比例、改變Spark管理內(nèi)存分配比例幾個(gè)方面探討了優(yōu)化Spark作業(yè)運(yùn)行的方法,并通過實(shí)驗(yàn)對(duì)方法進(jìn)行了驗(yàn)證。
通過實(shí)驗(yàn)發(fā)現(xiàn),總的來說,使用更大的內(nèi)存能夠提升系統(tǒng)性能。但是,在大數(shù)據(jù)處理背景下,內(nèi)存的增長(zhǎng)跟不上數(shù)據(jù)的增長(zhǎng),在有限的內(nèi)存條件下,適當(dāng)減少內(nèi)存使用促使系統(tǒng)用重新計(jì)算代替緩存,根據(jù)Spark緩存對(duì)象和作業(yè)性質(zhì)的特點(diǎn),優(yōu)化JVM中堆內(nèi)存的分配和Spark管理內(nèi)存的大小,能夠有效降低I/O和GC消耗,提高系統(tǒng)性能。
本文的貢獻(xiàn)點(diǎn)在于結(jié)合Spark對(duì)內(nèi)存的管理和JVM對(duì)內(nèi)存的管理,分析兩層之間的內(nèi)存管理的相互聯(lián)系,根據(jù)數(shù)據(jù)對(duì)象在內(nèi)存中的存在方式變化,提出優(yōu)化策略。實(shí)驗(yàn)結(jié)果說明優(yōu)化策略能夠有效提升系統(tǒng)性能,具有一定的代表性,分析的結(jié)論對(duì)如何更好地使用和改進(jìn)Spark也有一定的啟發(fā)作用。
參考文獻(xiàn):
[1] Zaharia M.An architecture for fast and general data processing on large clusters[M].New York:Association for Computing Machinery and Morgan & Claypool,2013.
[2] Zaharia M,Chowdhury M,Franklin M J,et al.Spark: Cluster computing with working sets[C]∥Proc of the 2nd USENIX Conference on Hot Topics in Cloud Computing,2010:10.
[3] Zaharia M,Chowdhury M,Das T,et al.Resilient distributed datasets: A fault-tolerant abstraction for in-memory cluster computing[C]∥Proc of the 9th USENIX Conference on Networked Systems Design and Implementation,2012:141-146.
[4] Apache Spark-Lightning-fast cluster computing[EB/OL].[2016-09-14].http://spark.apache.org/.
[5] Ousterhout K,Rasti R,Ratnasamy S,et al.Making sense of performance in data analytics frameworks[C]∥Proc of the 12th USENIX Conference on Networked Systems Design & Implementation,2015:293-307.
[6] Chen Qiao-an,Li Feng,Cao Yue,et al.Parameter optimization for Spark jobs based on runtime data analysis[J].Computer Engineering & Science,2016,38(1):11-19.(in Chinese)
[7] Yang Zhi-wei,Zheng Quan,Wang Song,et al.Adaptive task scheduling strategy for heterogeneous spark cluster [J].Computer Engineering,2016,42(1):31-35.(in Chinese)
[8] Gog I,Giceva J,Schwarzkopf M,et al.Broom: Sweeping out garbage collection from big data systems[C]∥Proc of the 15th Usenix Conference on Hot Topics in Operating Systems,
2015:2.
[9] Gidra L, Thomas G,Sopena J,et al.NumaGiC: A garbage collector for big data on big NUMA machines[J].Acm Sigplan Notices,2015,50(4):661-673.
[10] Java SE 6 HotSpot[tm] Virtual machine garbage collection tuning[EB/OL].[2016-09-16].http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html.
附中文參考文獻(xiàn):
[6] 陳僑安,李峰,曹越,等.基于運(yùn)行數(shù)據(jù)分析的Spark任務(wù)參數(shù)優(yōu)化[J].計(jì)算機(jī)工程與科學(xué),2016,38(1):11-19.
[7] 楊志偉,鄭烇,王嵩,等.異構(gòu)Spark集群下自適應(yīng)任務(wù)調(diào)度策略[J].計(jì)算機(jī)工程,2016,42(1):31-35.