因?yàn)榇蠖鄶?shù)的Spark計(jì)算是在內(nèi)存中進(jìn)行的,所以Spark應(yīng)用的瓶頸在于集群的硬件資源:CPU、網(wǎng)絡(luò)帶寬或者內(nèi)存。在大多數(shù)情況下,如果內(nèi)存夠用费薄,網(wǎng)絡(luò)帶寬就會(huì)成為應(yīng)用性能的瓶頸硝全,但在有些時(shí)候,你仍然需要去做一些調(diào)整楞抡,比如用序列化的形式去存儲(chǔ)RDD伟众,以減少內(nèi)存的使用。這篇指導(dǎo)將覆蓋兩個(gè)主題:數(shù)據(jù)的序列化(數(shù)據(jù)的序列化方式是好的網(wǎng)絡(luò)性能以及減少內(nèi)存使用的關(guān)鍵)和內(nèi)存調(diào)整召廷。我們也會(huì)討論幾個(gè)小的主題凳厢。
Data Serialization(數(shù)據(jù)序列化)
在任何的分布式應(yīng)用的性能方面,序列化方式都扮演者一個(gè)極為重要的角色竞慢。如果序列化方式解析對(duì)象的速度太慢或者消耗的字節(jié)數(shù)量過大先紫,將會(huì)極大的放慢計(jì)算的速度。通常筹煮,這將是你在優(yōu)化Spark程序時(shí)需要優(yōu)先考慮調(diào)整的地方遮精。Spark的目標(biāo)是平衡便利(允許你使用任何Java中的類型進(jìn)行操作)和性能。他提供了兩種序列化庫(kù):
-
Java serialization:Spark的默認(rèn)的對(duì)象序列化方式是使用Java的ObjectOutputStream 框架,這種方式允許你使用任何實(shí)現(xiàn)了
java.io.Serializable
接口的類本冲。Java serialization非常靈活但速度太慢准脂,并且會(huì)導(dǎo)致很多的類的序列化結(jié)果太大。 - Kryo serialization:Spark同時(shí)提供了Kryo庫(kù)(版本號(hào)4)來使對(duì)象的序列化速度更快檬洞。Kryo和Java serialization比較狸膏,顯著的加快了對(duì)象序列化的方式,并大大降低了序列化結(jié)構(gòu)的大刑碚(大于10倍)湾戳,但是Kryo并不支持所有可序列化的類型,并且他要求你注冊(cè)你希望改善性能的那些類澎灸。
你可以在初始化Spark作業(yè)的時(shí)候通過調(diào)用SparkConf的set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
來切換Kryo序列化方式院塞。這項(xiàng)配置不僅僅更改了工作節(jié)點(diǎn)之間進(jìn)行數(shù)據(jù)混洗的時(shí)候所用的序列化器,同時(shí)更改了將RDD寫入磁盤的時(shí)候的序列化器性昭。Spark沒有將Kryo作為默認(rèn)的序列化器的原因在于需要開發(fā)者自己注冊(cè)需要序列化的類型拦止,所以我們建議在IO密集型的應(yīng)用中使用Kryo作為序列化器。從Spark2.0.0開始糜颠,我們?cè)趦?nèi)部開始使用Kryo序列化器作為混洗簡(jiǎn)單數(shù)據(jù)類型及其數(shù)組類型的RDD的序列化器汹族。
Spark自動(dòng)包含了來自于Twitter chill庫(kù)中的常用的Scala類的Kryo序列化器的注冊(cè)。
要將你自己的類注冊(cè)成Kryo序列化支持的類其兴,可以使用registerKryoClasses
方法顶瞒。
val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)
Kryo documentation中描述了更多的高級(jí)選項(xiàng),例如添加定制的序列化代碼元旬。
如果你的對(duì)象列表很大榴徐,你可能需要增加spark.kryoserializer.buffer
config,這些配置需要你設(shè)置足夠大的值來存儲(chǔ)你將要序列化的對(duì)象匀归。
最后坑资,如果你沒有注冊(cè)你定制的類型,Kryo仍然可以工作穆端,但是袱贮,Kryo將會(huì)為每一個(gè)對(duì)象存儲(chǔ)完整的類名稱,這將會(huì)帶來浪費(fèi)体啰。
Memory Tuning(內(nèi)存優(yōu)化)
在進(jìn)行內(nèi)存優(yōu)化的時(shí)候有三個(gè)需要考慮的方面:你的數(shù)據(jù)對(duì)象所需要使用的內(nèi)存總數(shù)攒巍,訪問這些對(duì)象的花銷以及垃圾收集機(jī)制(如果你需要對(duì)這些數(shù)據(jù)對(duì)象進(jìn)行頻繁的轉(zhuǎn)換)
默認(rèn)情況下,Java對(duì)象可以快速的進(jìn)行訪問荒勇,但是比起停留在原位置的原始數(shù)據(jù)來說柒莉,Java對(duì)象會(huì)消耗2-5倍的空間。這是因?yàn)槿缦聨讉€(gè)原因:
- 每一個(gè)不同的Java對(duì)象都包含一個(gè)對(duì)象頭沽翔,這個(gè)對(duì)象頭占用16個(gè)字節(jié)的空間常柄,包含了指向其類型的指針等信息。對(duì)于一個(gè)只包含了很少數(shù)據(jù)的對(duì)象來說,對(duì)象頭的空間會(huì)大于數(shù)據(jù)占用的空間西潘。
- Java字符串比原始的字符數(shù)據(jù)多使用了40字節(jié)的空間卷玉,因?yàn)橐鎯?chǔ)字符數(shù)組相關(guān)的一系列信息,例如字符串長(zhǎng)度喷市,并且將每一個(gè)字符占用2個(gè)字節(jié)的空間相种,因?yàn)镴ava內(nèi)部的字符編碼使UTF-16 。因此一個(gè)超過10個(gè)字符的字符串對(duì)象會(huì)很容易的占用60個(gè)字節(jié)的空間
- 通用的集合類品姓,比如HashMap和LinkedList寝并,使用鏈接式的數(shù)據(jù)結(jié)構(gòu),這些集合內(nèi)部存儲(chǔ)的每一個(gè)元素都是封裝過的對(duì)象(比如
Map.Entry
)腹备,這些對(duì)象不僅僅包含一個(gè)對(duì)象頭衬潦,而且還包含一個(gè)指向列表中下一個(gè)節(jié)點(diǎn)的指針(一般是8個(gè)字節(jié))。 - 主要類型的集合通常將他們以裝箱的形式進(jìn)行存儲(chǔ)植酥,比如
java.lang.Integer
镀岛。
本章將開始對(duì)Spark的內(nèi)存管理進(jìn)行綜述,然后討論一些可以使用戶在Spark應(yīng)用中更有效的使用內(nèi)存的策略友驮。我們會(huì)著重描述如何確定應(yīng)用中的對(duì)象的內(nèi)存使用情況以及優(yōu)化她們的方法 - 這些方法要么是改變數(shù)據(jù)的結(jié)構(gòu)漂羊,要么是通過序列化的方式來存儲(chǔ)數(shù)據(jù)。然后我們將介紹如何調(diào)整Spark的緩存大小和Java的垃圾收集機(jī)制卸留。
Memory Management Overview(內(nèi)存管理綜述)
Spark中大多數(shù)的內(nèi)存使用分為兩類:執(zhí)行和存儲(chǔ)走越。執(zhí)行內(nèi)存主要用與計(jì)算中的混洗,連接耻瑟,排序和聚合等操作旨指,存儲(chǔ)內(nèi)存主要用來緩存數(shù)據(jù)以及在集群內(nèi)部傳播數(shù)據(jù)。在Spark中喳整,執(zhí)行和存儲(chǔ)共享統(tǒng)一的內(nèi)存區(qū)域谆构。如果執(zhí)行器沒有使用內(nèi)存,那么存儲(chǔ)功能能夠請(qǐng)求所有可用的內(nèi)存(如果存儲(chǔ)功能沒有使用任何內(nèi)存算柳,執(zhí)行功能也可以請(qǐng)求所有可用內(nèi)存)。當(dāng)然執(zhí)行功能在內(nèi)存的使用方面對(duì)存儲(chǔ)功能具有優(yōu)先權(quán)姓言,如果必要的話執(zhí)行功能可以搶占存儲(chǔ)功能占用的內(nèi)存瞬项,但是只能講存儲(chǔ)占用的內(nèi)存降低到一個(gè)閾值,無法完全搶占何荚。換句話說囱淋,內(nèi)存中有一個(gè)子區(qū)域的緩存塊將永遠(yuǎn)無法被搶占。存儲(chǔ)無法搶占執(zhí)行功能的內(nèi)存餐塘,這是因?yàn)橛?jì)算實(shí)現(xiàn)的復(fù)雜度造成的妥衣。
盡管有兩項(xiàng)相關(guān)的配置,但是對(duì)于絕大多數(shù)工作來說,不建議用戶去調(diào)整配置的默認(rèn)值税手。
-
spark.memory.fraction
表示Spark作業(yè)使用的內(nèi)存區(qū)域與Jvm堆內(nèi)存的占比(默認(rèn)值是0.6)蜂筹。剩余的空間(40%)用來存儲(chǔ)用戶的數(shù)據(jù)結(jié)構(gòu),Spark內(nèi)部的元數(shù)據(jù)芦倒,以及用來防止由稀疏何異常大的記錄所導(dǎo)致的OOM錯(cuò)誤艺挪。 -
spark.memory.storageFraction
表示Spark內(nèi)存區(qū)中存儲(chǔ)區(qū)所占的比例(默認(rèn)是0.5)。存儲(chǔ)區(qū)就是可以防止執(zhí)行功能搶占的內(nèi)存區(qū)兵扬。
spark.memory.fraction
的值的設(shè)置應(yīng)該確保其占用的內(nèi)存能夠容納JVM堆內(nèi)存中老年代和永久代的大小麻裳。我們可以在下面的高級(jí)GC調(diào)整中進(jìn)行詳細(xì)討論
Determining Memory Consumption(確定內(nèi)存消耗)
最好的確定數(shù)據(jù)集內(nèi)存消耗情況的方法是創(chuàng)建一個(gè)RDD并將其緩存,然后通過Spark管理界面(Web UI)的存儲(chǔ)頁面去查看器钟。該頁面將會(huì)告訴你RDD占用的內(nèi)存大小津坑。
去估計(jì)一個(gè)特定對(duì)象的內(nèi)存消耗,可以使用SizeEstimator
類的estimate
方法傲霸。這個(gè)方法可以用來測(cè)試不同的數(shù)據(jù)布局對(duì)內(nèi)存的占用情況疆瑰,除此之外,也可以用來確定廣播變量對(duì)每一個(gè)執(zhí)行器的堆內(nèi)存的占用情況狞谱。
Tuning Data Structures(調(diào)整數(shù)據(jù)結(jié)構(gòu))
第一個(gè)降低內(nèi)存消耗的方法是避免由于Java的功能所過度使用的內(nèi)存乃摹,比如基于指針的數(shù)據(jù)結(jié)構(gòu)以及包裝類對(duì)象。下面有這幾種方法:
- 設(shè)計(jì)你自己的數(shù)據(jù)結(jié)構(gòu)代替Java或者Scala集合類來保存對(duì)象數(shù)組以及主要的數(shù)據(jù)類型(比如HashMap)跟衅,FastUtil 庫(kù)為主要數(shù)據(jù)類型提供了方便使用的集合類并且兼容Java的標(biāo)準(zhǔn)庫(kù)孵睬。
- 避免嵌套小對(duì)象或者指針的使用。
- 盡量用數(shù)字類型的鍵來代替字符類型的鍵
- 如果機(jī)器的內(nèi)存小于32GB伶跷,可以設(shè)置JVM的參數(shù)
-XX:+UseCompressedOops
用4個(gè)字節(jié)指針代替8個(gè)字節(jié)的指針掰读。你可以添加這些選項(xiàng)到spark-env.sh中。
Serialized RDD Storage(序列化RDD的存儲(chǔ))
盡管你做了這些調(diào)整叭莫,但是你的對(duì)象仍然太大蹈集,無法做到高效的存儲(chǔ),一個(gè)最簡(jiǎn)單的降低內(nèi)存使用的方法就是用序列化的形式存儲(chǔ)他們雇初,可以通過RDD persistence API中的序列化存儲(chǔ)級(jí)別來配置拢肆,例如MEMORY_ONLY_SER
。一旦設(shè)置了序列化存儲(chǔ)級(jí)別靖诗,Spark就會(huì)將每一個(gè)RDD分區(qū)存儲(chǔ)成大的字節(jié)數(shù)組郭怪。這樣做的唯一劣勢(shì)就是會(huì)增加訪問時(shí)間,因?yàn)樵谑褂玫臅r(shí)候需要先進(jìn)行反序列化刊橘。如果你打算使用序列化的方式來緩存RDD鄙才,我們強(qiáng)烈建議使用Kryo,Kryo的序列化的結(jié)果遠(yuǎn)遠(yuǎn)小于Java的序列化機(jī)制促绵。
Garbage Collection Tuning(垃圾收集的調(diào)整)
當(dāng)你的程序中攒庵,在RDD的存儲(chǔ)方面嘴纺,有很多的打亂操作時(shí),JVM的垃圾收集將會(huì)成為一個(gè)問題浓冒。(如果你的程序只是讀取RDD一次然后在其上進(jìn)行一些操作栽渴,那么垃圾回收將不會(huì)成為一個(gè)問題)當(dāng)JVM需要回收舊的對(duì)象,為新的對(duì)象騰出空間時(shí)裆蒸,JVM需要掃描幾乎所有的Java對(duì)象然后發(fā)現(xiàn)不在使用的對(duì)象熔萧。主要的點(diǎn)就在于記住垃圾收集的開銷與Java對(duì)象的數(shù)量成正比,所以使用盡可能少的Java對(duì)象會(huì)大大降低垃圾收集的花費(fèi)僚祷。一個(gè)比較有效的方法就是用序列化的方式來緩存對(duì)象佛致,這樣就可以使用一個(gè)對(duì)象(字節(jié)數(shù)組)來存儲(chǔ)一個(gè)RDD分區(qū)。如果你的GC成為了一個(gè)問題辙谜,那么在嘗試其他技術(shù)之前俺榆,可以先嘗試serialized caching(序列化緩存)技術(shù)。
在另一種情況下装哆,GC也會(huì)成為一個(gè)問題罐脊,就是當(dāng)沒有足夠的內(nèi)存來執(zhí)行計(jì)算的時(shí)候,需要回收緩存RDD的內(nèi)存時(shí)蜕琴。下面我們將討論如何控制RDD內(nèi)存的分配來減少這種情況的發(fā)生萍桌。
Measuring the Impact of GC(評(píng)估GC的影響)
在調(diào)整GC的時(shí)候,第一步要做的就是統(tǒng)計(jì)收集GC發(fā)生的頻率和花費(fèi)的總時(shí)間凌简。我們可以通過設(shè)置-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
Jvm參數(shù)來收集GC的的信息上炎。(可以通過configuration guide來查看如何在Spark作業(yè)中設(shè)置Java參數(shù))下次你的Spark作業(yè)在運(yùn)行的時(shí)候,你將會(huì)看到打印在工作節(jié)點(diǎn)線程中的GC發(fā)生的消息雏搂。注意藕施,這些日志會(huì)打印在你的Spark集群的工作節(jié)點(diǎn)上(在工作目錄的stdout文件上),而不是在你的驅(qū)動(dòng)程序那里凸郑。
Advanced GC Tuning(高級(jí)GC調(diào)整)
如果想要進(jìn)一步優(yōu)化GC裳食,我們首先得理解一些JVM內(nèi)存管理的基礎(chǔ)信息:
- Java的堆內(nèi)存區(qū)分為新生代和老年代兩個(gè)部分。新生代用來保存短生命周期的對(duì)象芙沥,老年代用于存儲(chǔ)長(zhǎng)生命周期的對(duì)象诲祸。
- 新生代進(jìn)一步分為三個(gè)區(qū)域:Eden(出生區(qū)),Survivor1(存活區(qū)1), Survivor2(存活區(qū)2)而昨。
- 對(duì)垃圾收集的步驟做一個(gè)簡(jiǎn)單的描述:當(dāng)Eden區(qū)已滿救氯,會(huì)在Eden區(qū)進(jìn)行一次次級(jí)GC,將Eden和Survivor1區(qū)中仍然存活的對(duì)象復(fù)制到Survivor2區(qū)配紫。這些Survivor區(qū)域會(huì)發(fā)生交換径密。如果一個(gè)對(duì)象存活時(shí)間足夠長(zhǎng)或者Survivor2區(qū)已滿午阵,他將會(huì)被放入老年區(qū)躺孝。最后享扔,如果老年區(qū)接近滿了,一次完整的GC將被執(zhí)行植袍。
在Spark中GC優(yōu)化的目標(biāo)是確保只有長(zhǎng)時(shí)間存活的RDDs存儲(chǔ)在老年代中并且確保有充分的青年代空間存儲(chǔ)短生命周期對(duì)象惧眠。這樣可以避免需要通過完整的GC去收集一些任務(wù)執(zhí)行過程中創(chuàng)建的臨時(shí)對(duì)象。下面這些步驟應(yīng)該會(huì)有所幫助
- 通過收集GC的狀態(tài)檢查是否發(fā)生了過多了垃圾收集于个。如果在一次任務(wù)結(jié)束前發(fā)生了多次的完整GC氛魁,這意味著對(duì)于所執(zhí)行的任務(wù)來說,并沒有足夠的可用內(nèi)存厅篓。
- 如果發(fā)生了多次的Minor GC但是Major GC發(fā)生的次數(shù)卻不多秀存,這個(gè)時(shí)候可以通過為Eden區(qū)分配更多的空間來改善這種狀況。你可以為Eden區(qū)設(shè)置一個(gè)比較高的估值羽氮。如果Eden去的大小為E或链,你可以通過選項(xiàng)
-Xmn=4/3*E
來設(shè)置年輕代的大小。(通過4/3這個(gè)比例可以計(jì)算出survivor區(qū)域的大械笛骸) - 當(dāng)GC狀態(tài)被打印了出來澳盐,如果老年代接近滿,可以通過降低
spark.memory.fraction
的值降低緩存對(duì)內(nèi)存的使用量令宿;緩存較少的對(duì)象要好于減慢任務(wù)執(zhí)行的速度叼耙。當(dāng)然,我們也可以考慮減少年輕代的大小粒没。這意味給-Xmn選項(xiàng)設(shè)置一個(gè)較低的值筛婉。如果不想這樣做,可以嘗試更改JVM的NewRatio參數(shù)革娄。大多數(shù)JVM默認(rèn)的NewRatio參數(shù)值是2倾贰,這意味著老年代占用了2/3的堆內(nèi)存空間。這個(gè)比例超過了spark.memory.fraction
拦惋,應(yīng)該足夠大了. - 可以通過
-XX:+UseG1GC
嘗試G1GC方法匆浙。在一些GC成為性能瓶頸的場(chǎng)景,使用G1垃圾收集器能夠提升性能厕妖。注意:如果堆內(nèi)存空間很大首尼,那么通過-XX:G1HeapRegionSize
增加G1區(qū)域的大小就變的非常重要了。 - 舉個(gè)例子言秸,如果的你的任務(wù)需要從HDFS中讀取數(shù)據(jù)软能,那么任務(wù)需要的內(nèi)存總數(shù)可以通過HDFS數(shù)據(jù)塊的大小進(jìn)行估算。注意:HDFS中的數(shù)據(jù)塊是經(jīng)過壓縮的举畸,所以讀入內(nèi)存后需要解壓查排,因此實(shí)際的大小是原來塊大小的2至3倍。如果我們需要能執(zhí)行3到4個(gè)任務(wù)的工作空間抄沮,并且HDFS的塊大小是128M跋核,所以我們估計(jì)Eden區(qū)的大小為43128岖瑰。
- 監(jiān)控一下新的GC配置下,GC發(fā)生的時(shí)間和頻率砂代。
根據(jù)我們的經(jīng)驗(yàn)蹋订,最有效的GC優(yōu)化依賴你的應(yīng)用和可用的總內(nèi)存。在網(wǎng)上有更多關(guān)于GC優(yōu)化的描述刻伊,但是露戒,站在更高層次看,控制完整GC的發(fā)生頻率捶箱,能夠有效降低GC的開銷智什。
執(zhí)行器的GC優(yōu)化可以通過配置作業(yè)的spark.executor.extraJavaOptions
來進(jìn)行。
Other Considerations(其他)
Level of Parallelism(并行度)
除非你為每一個(gè)操作設(shè)置了最高的并行度丁屎,否則不會(huì)利用集群中所有的結(jié)點(diǎn)撩鹿。Spark會(huì)根據(jù)map
操作所處理的每一個(gè)文件的大小來自動(dòng)設(shè)置并行度(雖然你能夠通過可選的參數(shù)來控制他們比如SparkContext.textFile
),對(duì)于那些分布式reduce
操作悦屏,比如groupByKey
和reduceByKey
节沦,Spark會(huì)使用最大的上游RDD的分區(qū)數(shù)來確定并行度。當(dāng)然你可以通過將并行度作為第二個(gè)參數(shù)傳給相關(guān)的操作(可以查看 spark.PairRDDFunctions
的文檔)础爬,或者設(shè)置spark.default.parallelism
的值來改變默認(rèn)的并行度甫贯。一般而言,我們建議在集群中看蚜,每2或3個(gè)任務(wù)使用一個(gè)CPU核叫搁。
Memory Usage of Reduce Tasks(Reduce任務(wù)的內(nèi)存使用)
有些時(shí)候,你將會(huì)遇到OutOfMemoryError供炎,這并非因?yàn)槟愕腞DDs大小超過了可用內(nèi)存渴逻,而是因?yàn)槟愕娜蝿?wù)集合中的某一個(gè)任務(wù),比如groupByKey
這樣的Reduce操作音诫,他太大了惨奕。Spark的混洗操作(sortByKey
,groupByKey
竭钝,reduceByKey
等)會(huì)為每一個(gè)任務(wù)創(chuàng)建一個(gè)哈希表來執(zhí)行分組梨撞,這個(gè)哈希表通常會(huì)很大。最簡(jiǎn)單的解決此類錯(cuò)誤的方式就是提高并行度香罐,提高了并行度每一個(gè)該操作的任務(wù)的輸入集就會(huì)變小卧波。Spark可以有效的支持任務(wù)在200ms結(jié)束,因?yàn)樗诤芏嗟娜蝿?wù)之間重復(fù)使用了執(zhí)行器的JVM庇茫,并且有著較低的任務(wù)啟動(dòng)花費(fèi)港粱,所以你能夠安全的提升并行度的值,使其大于整個(gè)集群的內(nèi)核數(shù)旦签。
Broadcasting Large Variables(廣播大型變量)
使用 broadcast functionality
能夠極大的減少每一次序列化任務(wù)使用的內(nèi)存大小查坪,以及在集群上啟動(dòng)一次作業(yè)的花費(fèi)锈颗。如果你的任務(wù)使用了來自于驅(qū)動(dòng)程序的大型對(duì)象,可以考慮使用廣播變量來調(diào)整他咪惠。Spark會(huì)將每一個(gè)任務(wù)序列化后的大小打印在Master上,所以你能夠看到并且決定你的任務(wù)是否過大淋淀;通常情況下遥昧,任務(wù)大于20KB就值的去進(jìn)行優(yōu)化。
Data Locality(數(shù)據(jù)局部性)
數(shù)據(jù)局部性對(duì)Spark作業(yè)的性能有著巨大的影響朵纷。如果數(shù)據(jù)和代碼在同一個(gè)結(jié)點(diǎn)上那么計(jì)算就會(huì)變的很快炭臭。但是如果代碼和數(shù)據(jù)是分開的,那么其中一個(gè)要移動(dòng)到另一個(gè)所在的結(jié)點(diǎn)袍辞。典型地鞋仍,移動(dòng)序列化后的代碼比移動(dòng)數(shù)據(jù)塊要快的多,這是因?yàn)榇a的大小遠(yuǎn)遠(yuǎn)小于數(shù)據(jù)塊的大小搅吁。Spark在調(diào)度作業(yè)的時(shí)候遵守?cái)?shù)據(jù)局部性的一般原則威创。
數(shù)據(jù)局部性表示數(shù)據(jù)與處理他的程序之間的距離。這里有幾種基于數(shù)據(jù)當(dāng)前位置的局部化的級(jí)別谎懦。順序是由近至遠(yuǎn):
-
PROCESS_LOCAL
數(shù)據(jù)和處理程序在同一個(gè)JVM中肚豺,這是最好的情況 -
NODE_LOCAL
數(shù)據(jù)和處理程序在同一個(gè)結(jié)點(diǎn)上。比如數(shù)據(jù)在HDFS的一個(gè)結(jié)點(diǎn)上界拦,同時(shí)一個(gè)執(zhí)行器也在同一個(gè)結(jié)點(diǎn)上吸申。這比PROCESS_LOCAL
要慢一些,因?yàn)閿?shù)據(jù)要在進(jìn)程之間轉(zhuǎn)移 -
NO_PREF
在任何地方訪問數(shù)據(jù)都是一樣的享甸,沒有任何局部性的優(yōu)先權(quán) -
RACK_LOCAL
數(shù)據(jù)和程序位于同一個(gè)機(jī)架上的不同機(jī)器上截碴,因此數(shù)據(jù)需要通過網(wǎng)絡(luò)進(jìn)行傳輸,典型地通過一個(gè)單開關(guān) -
ANY
數(shù)據(jù)可以在網(wǎng)絡(luò)中的任何位置蛉威,但與處理程序不在同一個(gè)機(jī)架上日丹。
Spark喜歡講所有任務(wù)安排到最好的局部性級(jí)別上,但是這并不總是可能的蚯嫌。在某些情況下聚凹,一些空閑的執(zhí)行器上沒有未處理的數(shù)據(jù)了,Spark會(huì)切換較低的局部性等級(jí)齐帚。這時(shí)候有兩個(gè)選項(xiàng):A)等待非空閑并且有未處理數(shù)據(jù)的執(zhí)行器上的繁忙的CPU空閑的時(shí)候妒牙,在數(shù)據(jù)所在的服務(wù)器上啟動(dòng)一個(gè)新的任務(wù),或者B)立刻在空閑的執(zhí)行上啟動(dòng)1個(gè)新的任務(wù)然后請(qǐng)求移動(dòng)數(shù)據(jù)对妄。
Spark會(huì)選擇哪一個(gè)呢湘今?Spark先選擇A選項(xiàng),等待繁忙的CPU一段期望的時(shí)間剪菱,如果這段時(shí)間后摩瞎,CPU仍然繁忙拴签,那么Spark就會(huì)選擇B選項(xiàng)。
這個(gè)在各個(gè)等級(jí)之間的期望時(shí)間可以分別單獨(dú)設(shè)置旗们,或者可以通過參數(shù)的形式整體來設(shè)置蚓哩;我們可以在configuration page上查看spark.locality
參數(shù)的詳細(xì)信息。如果你的任務(wù)時(shí)間較長(zhǎng)或者局部化特性比較差上渴,你可以適當(dāng)?shù)卦龃筮@些配置的值岸梨,但是默認(rèn)值通常就夠用了。
Summary(總結(jié))
本文是一篇簡(jiǎn)短的指導(dǎo)稠氮,指出了當(dāng)你需要優(yōu)化Spark應(yīng)用時(shí)曹阔,你應(yīng)該關(guān)注哪些點(diǎn)。最重要的兩個(gè)點(diǎn)是數(shù)據(jù)序列化和內(nèi)存優(yōu)化隔披。對(duì)于大多數(shù)程序赃份,使用Kryo序列化以及在緩存數(shù)據(jù)的時(shí)候使用序列化形式將解決大部分常見的性能問題。