Spark性能優(yōu)化的基礎(chǔ)是什么,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
成都創(chuàng)新互聯(lián)公司自2013年創(chuàng)立以來(lái),先為墊江等服務(wù)建站,墊江等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為墊江企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。在大數(shù)據(jù)計(jì)算領(lǐng)域,Spark已經(jīng)成為了越來(lái)越流行、越來(lái)越受歡迎的計(jì)算平臺(tái)之一。Spark的功能涵蓋了大數(shù)據(jù)領(lǐng)域的離線批處理、SQL類處理、流式/實(shí)時(shí)計(jì)算、機(jī)器學(xué)習(xí)、圖計(jì)算等各種不同類型的計(jì)算操作,應(yīng)用范圍與前景非常廣泛。在美團(tuán)?大眾點(diǎn)評(píng),已經(jīng)有很多同學(xué)在各種項(xiàng)目中嘗試使用Spark。大多數(shù)同學(xué)(包括筆者在內(nèi)),最初開始嘗試使用Spark的原因很簡(jiǎn)單,主要就是為了讓大數(shù)據(jù)計(jì)算作業(yè)的執(zhí)行速度更快、性能更高。
然而,通過Spark開發(fā)出高性能的大數(shù)據(jù)計(jì)算作業(yè),并不是那么簡(jiǎn)單的。如果沒有對(duì)Spark作業(yè)進(jìn)行合理的調(diào)優(yōu),Spark作業(yè)的執(zhí)行速度可能會(huì)很慢,這樣就完全體現(xiàn)不出Spark作為一種快速大數(shù)據(jù)計(jì)算引擎的優(yōu)勢(shì)來(lái)。因此,想要用好Spark,就必須對(duì)其進(jìn)行合理的性能優(yōu)化。
Spark的性能調(diào)優(yōu)實(shí)際上是由很多部分組成的,不是調(diào)節(jié)幾個(gè)參數(shù)就可以立竿見影提升作業(yè)性能的。我們需要根據(jù)不同的業(yè)務(wù)場(chǎng)景以及數(shù)據(jù)情況,對(duì)Spark作業(yè)進(jìn)行綜合性的分析,然后進(jìn)行多個(gè)方面的調(diào)節(jié)和優(yōu)化,才能獲得最佳性能。
筆者根據(jù)之前的Spark作業(yè)開發(fā)經(jīng)驗(yàn)以及實(shí)踐積累,總結(jié)出了一套Spark作業(yè)的性能優(yōu)化方案。整套方案主要分為開發(fā)調(diào)優(yōu)、資源調(diào)優(yōu)、數(shù)據(jù)傾斜調(diào)優(yōu)、shuffle調(diào)優(yōu)幾個(gè)部分。開發(fā)調(diào)優(yōu)和資源調(diào)優(yōu)是所有Spark作業(yè)都需要注意和遵循的一些基本原則,是高性能Spark作業(yè)的基礎(chǔ);數(shù)據(jù)傾斜調(diào)優(yōu),主要講解了一套完整的用來(lái)解決Spark作業(yè)數(shù)據(jù)傾斜的解決方案;shuffle調(diào)優(yōu),面向的是對(duì)Spark的原理有較深層次掌握和研究的同學(xué),主要講解了如何對(duì)Spark作業(yè)的shuffle運(yùn)行過程以及細(xì)節(jié)進(jìn)行調(diào)優(yōu)。
作為Spark性能優(yōu)化的基礎(chǔ)篇,小編主要講解開發(fā)調(diào)優(yōu)以及資源調(diào)優(yōu)。
Spark性能優(yōu)化的第一步,就是要在開發(fā)Spark作業(yè)的過程中注意和應(yīng)用一些性能優(yōu)化的基本原則。開發(fā)調(diào)優(yōu),就是要讓大家了解以下一些Spark基本開發(fā)原則,包括:RDD lineage設(shè)計(jì)、算子的合理使用、特殊操作的優(yōu)化等。在開發(fā)過程中,時(shí)時(shí)刻刻都應(yīng)該注意以上原則,并將這些原則根據(jù)具體的業(yè)務(wù)以及實(shí)際的應(yīng)用場(chǎng)景,靈活地運(yùn)用到自己的Spark作業(yè)中。
// 需要對(duì)名為“hello.txt”的HDFS文件進(jìn)行一次map操作,再進(jìn)行一次reduce操作。也就是說,需要對(duì)一份數(shù)據(jù)執(zhí)行兩次算子操作。 // 錯(cuò)誤的做法:對(duì)于同一份數(shù)據(jù)執(zhí)行多次算子操作時(shí),創(chuàng)建多個(gè)RDD。 // 這里執(zhí)行了兩次textFile方法,針對(duì)同一個(gè)HDFS文件,創(chuàng)建了兩個(gè)RDD出來(lái),然后分別對(duì)每個(gè)RDD都執(zhí)行了一個(gè)算子操作。 // 這種情況下,Spark需要從HDFS上兩次加載hello.txt文件的內(nèi)容,并創(chuàng)建兩個(gè)單獨(dú)的RDD;第二次加載HDFS文件以及創(chuàng)建RDD的性能開銷,很明顯是白白浪費(fèi)掉的。 val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt") rdd1.map(...) val rdd2 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt") rdd2.reduce(...) // 正確的用法:對(duì)于一份數(shù)據(jù)執(zhí)行多次算子操作時(shí),只使用一個(gè)RDD。 // 這種寫法很明顯比上一種寫法要好多了,因?yàn)槲覀儗?duì)于同一份數(shù)據(jù)只創(chuàng)建了一個(gè)RDD,然后對(duì)這一個(gè)RDD執(zhí)行了多次算子操作。 // 但是要注意到這里為止優(yōu)化還沒有結(jié)束,由于rdd1被執(zhí)行了兩次算子操作,第二次執(zhí)行reduce操作的時(shí)候,還會(huì)再次從源頭處重新計(jì)算一次rdd1的數(shù)據(jù),因此還是會(huì)有重復(fù)計(jì)算的性能開銷。 // 要徹底解決這個(gè)問題,必須結(jié)合“原則三:對(duì)多次使用的RDD進(jìn)行持久化”,才能保證一個(gè)RDD被多次使用時(shí)只被計(jì)算一次。 val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt") rdd1.map(...) rdd1.reduce(...)
// 錯(cuò)誤的做法。 // 有一個(gè)格式的RDD,即rdd1。 // 接著由于業(yè)務(wù)需要,對(duì)rdd1執(zhí)行了一個(gè)map操作,創(chuàng)建了一個(gè)rdd2,而rdd2中的數(shù)據(jù)僅僅是rdd1中的value值而已,也就是說,rdd2是rdd1的子集。 JavaPairRDD rdd1 = ... JavaRDD rdd2 = rdd1.map(...) // 分別對(duì)rdd1和rdd2執(zhí)行了不同的算子操作。 rdd1.reduceByKey(...) rdd2.map(...) // 正確的做法。 // 上面這個(gè)case中,其實(shí)rdd1和rdd2的區(qū)別無(wú)非就是數(shù)據(jù)格式不同而已,rdd2的數(shù)據(jù)完全就是rdd1的子集而已,卻創(chuàng)建了兩個(gè)rdd,并對(duì)兩個(gè)rdd都執(zhí)行了一次算子操作。 // 此時(shí)會(huì)因?yàn)閷?duì)rdd1執(zhí)行map算子來(lái)創(chuàng)建rdd2,而多執(zhí)行一次算子操作,進(jìn)而增加性能開銷。 // 其實(shí)在這種情況下完全可以復(fù)用同一個(gè)RDD。 // 我們可以使用rdd1,既做reduceByKey操作,也做map操作。 // 在進(jìn)行第二個(gè)map操作時(shí),只使用每個(gè)數(shù)據(jù)的tuple._2,也就是rdd1中的value值,即可。 JavaPairRDD rdd1 = ... rdd1.reduceByKey(...) rdd1.map(tuple._2...) // 第二種方式相較于第一種方式而言,很明顯減少了一次rdd2的計(jì)算開銷。 // 但是到這里為止,優(yōu)化還沒有結(jié)束,對(duì)rdd1我們還是執(zhí)行了兩次算子操作,rdd1實(shí)際上還是會(huì)被計(jì)算兩次。 // 因此還需要配合“原則三:對(duì)多次使用的RDD進(jìn)行持久化”進(jìn)行使用,才能保證一個(gè)RDD被多次使用時(shí)只被計(jì)算一次。
// 如果要對(duì)一個(gè)RDD進(jìn)行持久化,只要對(duì)這個(gè)RDD調(diào)用cache()和persist()即可。 // 正確的做法。 // cache()方法表示:使用非序列化的方式將RDD中的數(shù)據(jù)全部嘗試持久化到內(nèi)存中。 // 此時(shí)再對(duì)rdd1執(zhí)行兩次算子操作時(shí),只有在第一次執(zhí)行map算子時(shí),才會(huì)將這個(gè)rdd1從源頭處計(jì)算一次。 // 第二次執(zhí)行reduce算子時(shí),就會(huì)直接從內(nèi)存中提取數(shù)據(jù)進(jìn)行計(jì)算,不會(huì)重復(fù)計(jì)算一個(gè)rdd。 val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt").cache() rdd1.map(...) rdd1.reduce(...) // persist()方法表示:手動(dòng)選擇持久化級(jí)別,并使用指定的方式進(jìn)行持久化。 // 比如說,StorageLevel.MEMORY_AND_DISK_SER表示,內(nèi)存充足時(shí)優(yōu)先持久化到內(nèi)存中,內(nèi)存不充足時(shí)持久化到磁盤文件中。 // 而且其中的_SER后綴表示,使用序列化的方式來(lái)保存RDD數(shù)據(jù),此時(shí)RDD中的每個(gè)partition都會(huì)序列化成一個(gè)大的字節(jié)數(shù)組,然后再持久化到內(nèi)存或磁盤中。 // 序列化的方式可以減少持久化的數(shù)據(jù)對(duì)內(nèi)存/磁盤的占用量,進(jìn)而避免內(nèi)存被持久化數(shù)據(jù)占用過多,從而發(fā)生頻繁GC。 val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt").persist(StorageLevel.MEMORY_AND_DISK_SER) rdd1.map(...) rdd1.reduce(...)
對(duì)于persist()方法而言,我們可以根據(jù)不同的業(yè)務(wù)場(chǎng)景選擇不同的持久化級(jí)別。
持久化級(jí)別 | 含義解釋 |
---|---|
MEMORY_ONLY | 使用未序列化的Java對(duì)象格式,將數(shù)據(jù)保存在內(nèi)存中。如果內(nèi)存不夠存放所有的數(shù)據(jù),則數(shù)據(jù)可能就不會(huì)進(jìn)行持久化。那么下次對(duì)這個(gè)RDD執(zhí)行算子操作時(shí),那些沒有被持久化的數(shù)據(jù),需要從源頭處重新計(jì)算一遍。這是默認(rèn)的持久化策略,使用cache()方法時(shí),實(shí)際就是使用的這種持久化策略。 |
MEMORY_AND_DISK | 使用未序列化的Java對(duì)象格式,優(yōu)先嘗試將數(shù)據(jù)保存在內(nèi)存中。如果內(nèi)存不夠存放所有的數(shù)據(jù),會(huì)將數(shù)據(jù)寫入磁盤文件中,下次對(duì)這個(gè)RDD執(zhí)行算子時(shí),持久化在磁盤文件中的數(shù)據(jù)會(huì)被讀取出來(lái)使用。 |
MEMORY_ONLY_SER | 基本含義同MEMORY_ONLY。唯一的區(qū)別是,會(huì)將RDD中的數(shù)據(jù)進(jìn)行序列化,RDD的每個(gè)partition會(huì)被序列化成一個(gè)字節(jié)數(shù)組。這種方式更加節(jié)省內(nèi)存,從而可以避免持久化的數(shù)據(jù)占用過多內(nèi)存導(dǎo)致頻繁GC。 |
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ù)占用過多內(nèi)存導(dǎo)致頻繁GC。 |
DISK_ONLY | 使用未序列化的Java對(duì)象格式,將數(shù)據(jù)全部寫入磁盤文件中。 |
MEMORY_ONLY_2, MEMORY_AND_DISK_2, 等等. | 對(duì)于上述任意一種持久化策略,如果加上后綴_2,代表的是將每個(gè)持久化的數(shù)據(jù),都復(fù)制一份副本,并將副本保存到其他節(jié)點(diǎn)上。這種基于副本的持久化機(jī)制主要用于進(jìn)行容錯(cuò)。假如某個(gè)節(jié)點(diǎn)掛掉,節(jié)點(diǎn)的內(nèi)存或磁盤中的持久化數(shù)據(jù)丟失了,那么后續(xù)對(duì)RDD計(jì)算時(shí)還可以使用該數(shù)據(jù)在其他節(jié)點(diǎn)上的副本。如果沒有副本的話,就只能將這些數(shù)據(jù)從源頭處重新計(jì)算一遍了。 |
如果有可能的話,要盡量避免使用shuffle類算子。因?yàn)镾park作業(yè)運(yùn)行過程中,最消耗性能的地方就是shuffle過程。shuffle過程,簡(jiǎn)單來(lái)說,就是將分布在集群中多個(gè)節(jié)點(diǎn)上的同一個(gè)key,拉取到同一個(gè)節(jié)點(diǎn)上,進(jìn)行聚合或join等操作。比如reduceByKey、join等算子,都會(huì)觸發(fā)shuffle操作。
shuffle過程中,各個(gè)節(jié)點(diǎn)上的相同key都會(huì)先寫入本地磁盤文件中,然后其他節(jié)點(diǎn)需要通過網(wǎng)絡(luò)傳輸拉取各個(gè)節(jié)點(diǎn)上的磁盤文件中的相同key。而且相同key都拉取到同一個(gè)節(jié)點(diǎn)進(jìn)行聚合操作時(shí),還有可能會(huì)因?yàn)橐粋€(gè)節(jié)點(diǎn)上處理的key過多,導(dǎo)致內(nèi)存不夠存放,進(jìn)而溢寫到磁盤文件中。因此在shuffle過程中,可能會(huì)發(fā)生大量的磁盤文件讀寫的IO操作,以及數(shù)據(jù)的網(wǎng)絡(luò)傳輸操作。磁盤IO和網(wǎng)絡(luò)數(shù)據(jù)傳輸也是shuffle性能較差的主要原因。
因此在我們的開發(fā)過程中,能避免則盡可能避免使用reduceByKey、join、distinct、repartition等會(huì)進(jìn)行shuffle的算子,盡量使用map類的非shuffle算子。這樣的話,沒有shuffle操作或者僅有較少shuffle操作的Spark作業(yè),可以大大減少性能開銷。
如果因?yàn)闃I(yè)務(wù)需要,一定要使用shuffle操作,無(wú)法用map類的算子來(lái)替代,那么盡量使用可以map-side預(yù)聚合的算子。
所謂的map-side預(yù)聚合,說的是在每個(gè)節(jié)點(diǎn)本地對(duì)相同的key進(jìn)行一次聚合操作,類似于MapReduce中的本地combiner。map-side預(yù)聚合之后,每個(gè)節(jié)點(diǎn)本地就只會(huì)有一條相同的key,因?yàn)槎鄺l相同的key都被聚合起來(lái)了。其他節(jié)點(diǎn)在拉取所有節(jié)點(diǎn)上的相同key時(shí),就會(huì)大大減少需要拉取的數(shù)據(jù)數(shù)量,從而也就減少了磁盤IO以及網(wǎng)絡(luò)傳輸開銷。通常來(lái)說,在可能的情況下,建議使用reduceByKey或者aggregateByKey算子來(lái)替代掉groupByKey算子。因?yàn)閞educeByKey和aggregateByKey算子都會(huì)使用用戶自定義的函數(shù)對(duì)每個(gè)節(jié)點(diǎn)本地的相同key進(jìn)行預(yù)聚合。而groupByKey算子是不會(huì)進(jìn)行預(yù)聚合的,全量的數(shù)據(jù)會(huì)在集群的各個(gè)節(jié)點(diǎn)之間分發(fā)和傳輸,性能相對(duì)來(lái)說比較差。
比如如下兩幅圖,就是典型的例子,分別基于reduceByKey和groupByKey進(jìn)行單詞計(jì)數(shù)。其中第一張圖是groupByKey的原理圖,可以看到,沒有進(jìn)行任何本地聚合時(shí),所有數(shù)據(jù)都會(huì)在集群節(jié)點(diǎn)之間傳輸;第二張圖是reduceByKey的原理圖,可以看到,每個(gè)節(jié)點(diǎn)本地的相同key數(shù)據(jù),都進(jìn)行了預(yù)聚合,然后才傳輸?shù)狡渌?jié)點(diǎn)上進(jìn)行全局聚合。
詳情見“原則五:使用map-side預(yù)聚合的shuffle操作”。
原理類似于“使用mapPartitions替代map”,也是一次函數(shù)調(diào)用處理一個(gè)partition的所有數(shù)據(jù),而不是一次函數(shù)調(diào)用處理一條數(shù)據(jù)。在實(shí)踐中發(fā)現(xiàn),foreachPartitions類的算子,對(duì)性能的提升還是很有幫助的。比如在foreach函數(shù)中,將RDD中所有數(shù)據(jù)寫MySQL,那么如果是普通的foreach算子,就會(huì)一條數(shù)據(jù)一條數(shù)據(jù)地寫,每次函數(shù)調(diào)用可能就會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接,此時(shí)就勢(shì)必會(huì)頻繁地創(chuàng)建和銷毀數(shù)據(jù)庫(kù)連接,性能是非常低下;但是如果用foreachPartitions算子一次性處理一個(gè)partition的數(shù)據(jù),那么對(duì)于每個(gè)partition,只要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接即可,然后執(zhí)行批量插入操作,此時(shí)性能是比較高的。實(shí)踐中發(fā)現(xiàn),對(duì)于1萬(wàn)條左右的數(shù)據(jù)量寫MySQL,性能可以提升30%以上。
repartitionAndSortWithinPartitions是Spark官網(wǎng)推薦的一個(gè)算子,官方建議,如果需要在repartition重分區(qū)之后,還要進(jìn)行排序,建議直接使用repartitionAndSortWithinPartitions算子。因?yàn)樵撍阕涌梢砸贿呥M(jìn)行重分區(qū)的shuffle操作,一邊進(jìn)行排序。shuffle與sort兩個(gè)操作同時(shí)進(jìn)行,比先shuffle再sort來(lái)說,性能可能是要高的。
// 以下代碼在算子函數(shù)中,使用了外部的變量。 // 此時(shí)沒有做任何特殊操作,每個(gè)task都會(huì)有一份list1的副本。 val list1 = ... rdd1.map(list1...) // 以下代碼將list1封裝成了Broadcast類型的廣播變量。 // 在算子函數(shù)中,使用廣播變量時(shí),首先會(huì)判斷當(dāng)前task所在Executor內(nèi)存中,是否有變量副本。 // 如果有則直接使用;如果沒有則從Driver或者其他Executor節(jié)點(diǎn)上遠(yuǎn)程拉取一份放到本地Executor內(nèi)存中。 // 每個(gè)Executor內(nèi)存中,就只會(huì)駐留一份廣播變量副本。 val list1 = ... val list1Broadcast = sc.broadcast(list1) rdd1.map(list1Broadcast...)
Java中,有三種類型比較耗費(fèi)內(nèi)存:
對(duì)象,每個(gè)Java對(duì)象都有對(duì)象頭、引用等額外的信息,因此比較占用內(nèi)存空間。
字符串,每個(gè)字符串內(nèi)部都有一個(gè)字符數(shù)組以及長(zhǎng)度等額外信息。
集合類型,比如HashMap、LinkedList等,因?yàn)榧项愋蛢?nèi)部通常會(huì)使用一些內(nèi)部類來(lái)封裝集合元素,比如Map.Entry。
因此Spark官方建議,在Spark編碼實(shí)現(xiàn)中,特別是對(duì)于算子函數(shù)中的代碼,盡量不要使用上述三種數(shù)據(jù)結(jié)構(gòu),盡量使用字符串替代對(duì)象,使用原始類型(比如Int、Long)替代字符串,使用數(shù)組替代集合類型,這樣盡可能地減少內(nèi)存占用,從而降低GC頻率,提升性能。
但是在筆者的編碼實(shí)踐中發(fā)現(xiàn),要做到該原則其實(shí)并不容易。因?yàn)槲覀兺瑫r(shí)要考慮到代碼的可維護(hù)性,如果一個(gè)代碼中,完全沒有任何對(duì)象抽象,全部是字符串拼接的方式,那么對(duì)于后續(xù)的代碼維護(hù)和修改,無(wú)疑是一場(chǎng)巨大的災(zāi)難。同理,如果所有操作都基于數(shù)組實(shí)現(xiàn),而不使用HashMap、LinkedList等集合類型,那么對(duì)于我們的編碼難度以及代碼可維護(hù)性,也是一個(gè)極大的挑戰(zhàn)。因此筆者建議,在可能以及合適的情況下,使用占用內(nèi)存較少的數(shù)據(jù)結(jié)構(gòu),但是前提是要保證代碼的可維護(hù)性。
在開發(fā)完Spark作業(yè)之后,就該為作業(yè)配置合適的資源了。Spark的資源參數(shù),基本都可以在spark-submit命令中作為參數(shù)設(shè)置。很多Spark初學(xué)者,通常不知道該設(shè)置哪些必要的參數(shù),以及如何設(shè)置這些參數(shù),最后就只能胡亂設(shè)置,甚至壓根兒不設(shè)置。資源參數(shù)設(shè)置的不合理,可能會(huì)導(dǎo)致沒有充分利用集群資源,作業(yè)運(yùn)行會(huì)極其緩慢;或者設(shè)置的資源過大,隊(duì)列沒有足夠的資源來(lái)提供,進(jìn)而導(dǎo)致各種異常??傊?,無(wú)論是哪種情況,都會(huì)導(dǎo)致Spark作業(yè)的運(yùn)行效率低下,甚至根本無(wú)法運(yùn)行。因此我們必須對(duì)Spark作業(yè)的資源使用原理有一個(gè)清晰的認(rèn)識(shí),并知道在Spark作業(yè)運(yùn)行過程中,有哪些資源參數(shù)是可以設(shè)置的,以及如何設(shè)置合適的參數(shù)值。
了解完了Spark作業(yè)運(yùn)行的基本原理之后,對(duì)資源相關(guān)的參數(shù)就容易理解了。所謂的Spark資源參數(shù)調(diào)優(yōu),其實(shí)主要就是對(duì)Spark運(yùn)行過程中各個(gè)使用資源的地方,通過調(diào)節(jié)各種參數(shù),來(lái)優(yōu)化資源使用的效率,從而提升Spark作業(yè)的執(zhí)行性能。以下參數(shù)就是Spark中主要的資源參數(shù),每個(gè)參數(shù)都對(duì)應(yīng)著作業(yè)運(yùn)行原理中的某個(gè)部分,我們同時(shí)也給出了一個(gè)調(diào)優(yōu)的參考值。
參數(shù)說明:該參數(shù)用于設(shè)置每個(gè)Executor進(jìn)程的內(nèi)存。Executor內(nèi)存的大小,很多時(shí)候直接決定了Spark作業(yè)的性能,而且跟常見的JVM OOM異常,也有直接的關(guān)聯(lián)。
參數(shù)調(diào)優(yōu)建議:每個(gè)Executor進(jìn)程的內(nèi)存設(shè)置4G~8G較為合適。但是這只是一個(gè)參考值,具體的設(shè)置還是得根據(jù)不同部門的資源隊(duì)列來(lái)定??梢钥纯醋约簣F(tuán)隊(duì)的資源隊(duì)列的大內(nèi)存限制是多少,num-executors乘以executor-memory,是不能超過隊(duì)列的大內(nèi)存量的。此外,如果你是跟團(tuán)隊(duì)里其他人共享這個(gè)資源隊(duì)列,那么申請(qǐng)的內(nèi)存量最好不要超過資源隊(duì)列大總內(nèi)存的1/3~1/2,避免你自己的Spark作業(yè)占用了隊(duì)列所有的資源,導(dǎo)致別的同學(xué)的作業(yè)無(wú)法運(yùn)行。
參數(shù)說明:該參數(shù)用于設(shè)置Driver進(jìn)程的內(nèi)存。
參數(shù)調(diào)優(yōu)建議:Driver的內(nèi)存通常來(lái)說不設(shè)置,或者設(shè)置1G左右應(yīng)該就夠了。唯一需要注意的一點(diǎn)是,如果需要使用collect算子將RDD的數(shù)據(jù)全部拉取到Driver上進(jìn)行處理,那么必須確保Driver的內(nèi)存足夠大,否則會(huì)出現(xiàn)OOM內(nèi)存溢出的問題。
參數(shù)說明:該參數(shù)用于設(shè)置RDD持久化數(shù)據(jù)在Executor內(nèi)存中能占的比例,默認(rèn)是0.6。也就是說,默認(rèn)Executor 60%的內(nèi)存,可以用來(lái)保存持久化的RDD數(shù)據(jù)。根據(jù)你選擇的不同的持久化策略,如果內(nèi)存不夠時(shí),可能數(shù)據(jù)就不會(huì)持久化,或者數(shù)據(jù)會(huì)寫入磁盤。
參數(shù)調(diào)優(yōu)建議:如果Spark作業(yè)中,有較多的RDD持久化操作,該參數(shù)的值可以適當(dāng)提高一些,保證持久化的數(shù)據(jù)能夠容納在內(nèi)存中。避免內(nèi)存不夠緩存所有的數(shù)據(jù),導(dǎo)致數(shù)據(jù)只能寫入磁盤中,降低了性能。但是如果Spark作業(yè)中的shuffle類操作比較多,而持久化操作比較少,那么這個(gè)參數(shù)的值適當(dāng)降低一些比較合適。此外,如果發(fā)現(xiàn)作業(yè)由于頻繁的gc導(dǎo)致運(yùn)行緩慢(通過spark web ui可以觀察到作業(yè)的gc耗時(shí)),意味著task執(zhí)行用戶代碼的內(nèi)存不夠用,那么同樣建議調(diào)低這個(gè)參數(shù)的值。
以下是一份spark-submit命令的示例,大家可以參考一下,并根據(jù)自己的實(shí)際情況進(jìn)行調(diào)節(jié):
./bin/spark-submit \ --master yarn-cluster \ --num-executors 100 \ --executor-memory 6G \ --executor-cores 4 \ --driver-memory 1G \ --conf spark.default.parallelism=1000 \ --conf spark.storage.memoryFraction=0.5 \ --conf spark.shuffle.memoryFraction=0.3 \
關(guān)于Spark性能優(yōu)化的基礎(chǔ)是什么問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司行業(yè)資訊頻道了解更多相關(guān)知識(shí)。