常規性能調優
1. 最優資源配置
Spark性能調優的第一步,就是為任務分配更多的資源,在一定范圍內,增加資源的分配與性能的提升是成正比的,實現了最優的資源配置后,在此基礎上再考慮進行后面論述的性能調優策略。資源的分配在使用腳本提交Spark任務時進行指定。
名稱 | 說明 |
---|---|
--num-executors | 配置Executor的數量 |
--driver-memory | 配置Driver內存(影響不大) |
--executor-memory | 配置每個Executor的內存大小 |
--executor-cores | 配置每個Executor的CPU core數量 |
配置 | 配置作用 |
---|---|
增加Executor·個數 | 在資源允許的情況下,增加Executor的個數可以提高執行,task的并行度。比如有4個Executor,每個Executor有2個CPU core,那么可以并行執行8個task,如果將Executor的個數增加到8個(資源允許的情況下),那么可以并行執行16個task,此時的并行能力提升了一倍 |
增加每個Executor的CPU core個數 | 在資源允許的情況下,增加每個Executor的Cpu core個數,可以提高執行task的并行度。比如有4個Executor,每個Executor有2個CPU core,那么可以并行執行8個task,如果將每個Executor的CPU core個數增加到4個(資源允許的情況下),那么可以并行執行16個task,此時的并行能力提升了一倍 |
增加每個Executor的內存量 | 在資源允許的情況下,增加每個Executor的內存量以后,對性能的提升有三點: 1. 可以緩存更多的數據(即對RDD進行cache),寫入磁盤的數據相應減少,甚至可以不寫入磁盤,減少了可能的磁盤IO;2. 可以為shuffle操作提供更多內存,即有更多空間來存放reduce端拉取的數據,寫入磁盤的數據相應減少,甚至可以不寫入磁盤,減少了可能的磁盤IO;3. 可以為task的執行提供更多內存,在task的執行過程中可能創建很多對象,內存較小時會引發頻繁的GC,增加內存后,可以避免頻繁的GC,提升整體性能 |
例如:
/usr/local/spark/bin/spark-submit \
--class com.atguigu.spark.WordCount \
--num-executors 80 \
--driver-memory 6g \
--executor-memory 6g \
--executor-cores 3 \
--master yarn-cluster \
--queue root.default \
--conf spark.yarn.executor.memoryOverhead=2048 \
--conf spark.core.connection.ack.wait.timeout=300 \
/usr/local/spark/spark.jar
參數配置參考值:
--num-executors:50~100
--driver-memory:1G~5G
--executor-memory:6G~10G
--executor-cores:3
--master:實際生產環境一般使用yarn-cluster
2. RDD優化
- RDD持久化
在Spark中,當多次對同一個RDD執行算子操作時,每一次都會對這個RDD以之前的父RDD重新計算一次,這種情況是必須要避免的,對同一個RDD的重復計算是對資源的極大浪費,因此,必須對多次使用的RDD進行持久化,通過持久化將公共RDD的數據緩存到內存/磁盤中,之后對于公共RDD的計算都會從內存/磁盤中直接獲取RDD數據。
對于RDD的持久化,有兩點需要說明:
第一,RDD的持久化是可以進行序列化的,當內存無法將RDD的數據完整的進行存放的時候,可以考慮使用序列化的方式減小數據體積,將數據完整存儲在內存中。
第二,如果對于數據的可靠性要求很高,并且內存充足,可以使用副本機制,對RDD數據進行持久化。當持久化啟用了復本機制時,對于持久化的每個數據單元都存儲一個副本,放在其他節點上面,由此實現數據的容錯,一旦一個副本數據丟失,不需要重新計算,還可以使用另外一個副本
- RDD盡可能早的filter操作
獲取到初始RDD后,應該考慮盡早地過濾掉不需要的數據,進而減少對內存的占用,從而提升Spark作業的運行效率
3. 并行度調節
Spark作業中的并行度指各個stage的task的數量。
如果并行度設置不合理而導致并行度過低,會導致資源的極大浪費,例如,20個Executor,每個Executor分配3個CPU core,而Spark作業有40個task,這樣每個Executor分配到的task個數是2個,這就使得每個Executor有一個CPU core空閑,導致資源的浪費。
理想的并行度設置,應該是讓并行度與資源相匹配,簡單來說就是在資源允許的前提下,并行度要設置的盡可能大,達到可以充分利用集群資源。合理的設置并行度,可以提升整個Spark作業的性能和運行速度。
Spark官方推薦,task數量應該設置為Spark作業總CPU core數量的2~3倍。之所以沒有推薦task數量與CPU core總數相等,是因為task的執行時間不同,有的task執行速度快而有的task執行速度慢,如果task數量與CPU core總數相等,那么執行快的task執行完成后,會出現CPU core空閑的情況。如果task數量設置為CPU core總數的2~3倍,那么一個task執行完畢后,CPU core會立刻執行下一個task,降低了資源的浪費,同時提升了Spark作業運行的效率。
Spark作業并行度的設置:
val conf = new SparkConf().set("spark.default.parallelism", "500")
4. 廣播大變量
默認情況下,task中的算子中如果使用了外部的變量,每個task都會獲取一份變量的復本,這就造成了內存的極大消耗。一方面,如果后續對RDD進行持久化,可能就無法將RDD數據存入內存,只能寫入磁盤,磁盤IO將會嚴重消耗性能;另一方面,task在創建對象的時候,也許會發現堆內存無法存放新創建的對象,這就會導致頻繁的GC,GC會導致工作線程停止,進而導致Spark暫停工作一段時間,嚴重影響Spark性能。
假設當前任務配置了20個Executor,指定500個task,有一個20M的變量被所有task共用,此時會在500個task中產生500個副本,耗費集群10G的內存,如果使用了廣播變量, 那么每個Executor保存一個副本,一共消耗400M內存,內存消耗減少了5倍。
廣播變量在每個Executor保存一個副本,此Executor的所有task共用此廣播變量,這讓變量產生的副本數量大大減少。
在初始階段,廣播變量只在Driver中有一份副本。task在運行的時候,想要使用廣播變量中的數據,此時首先會在自己本地的Executor對應的BlockManager中嘗試獲取變量,如果本地沒有,BlockManager就會從Driver或者其他節點的BlockManager上遠程拉取變量的復本,并由本地的BlockManager進行管理;之后此Executor的所有task都會直接從本地的BlockManager中獲取變量。
5. Kryo序列化
默認情況下,Spark使用Java的序列化機制。Java的序列化機制使用方便,不需要額外的配置,在算子中使用的變量實現Serializable接口即可,但是,Java序列化機制的效率不高,序列化速度慢并且序列化后的數據所占用的空間依然較大。
Kryo序列化機制比Java序列化機制性能提高10倍左右,Spark之所以沒有默認使用Kryo作為序列化類庫,是因為它不支持所有對象的序列化,同時Kryo需要用戶在使用前注冊需要序列化的類型,不夠方便,但從Spark 2.0.0版本開始,簡單類型、簡單類型數組、字符串類型的Shuffling RDDs 已經默認使用Kryo序列化方式了。
配置Kryo序列化:
//創建SparkConf對象
val conf = new SparkConf().setMaster(…).setAppName(…)
//使用Kryo序列化庫,如果要使用Java序列化庫,需要把該行屏蔽掉
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
//在Kryo序列化庫中注冊自定義的類集合,如果要使用Java序列化庫,需要把該行屏蔽掉
conf.set("spark.kryo.registrator", "atguigu.com.MyKryoRegistrator");
5. 調節本地化等待時長
Spark作業運行過程中,Driver會對每一個stage的task進行分配。根據Spark的task分配算法,Spark希望task能夠運行在它要計算的數據算在的節點(數據本地化思想),這樣就可以避免數據的網絡傳輸。通常來說,task可能不會被分配到它處理的數據所在的節點,因為這些節點可用的資源可能已經用盡,此時,Spark會等待一段時間,默認3s,如果等待指定時間后仍然無法在指定節點運行,那么會自動降級,嘗試將task分配到比較差的本地化級別所對應的節點上,比如將task分配到離它要計算的數據比較近的一個節點,然后進行計算,如果當前級別仍然不行,那么繼續降級。
當task要處理的數據不在task所在節點上時,會發生數據的傳輸。task會通過所在節點的BlockManager獲取數據,BlockManager發現數據不在本地時,戶通過網絡傳輸組件從數據所在節點的BlockManager處獲取數據。
網絡傳輸數據的情況是我們不愿意看到的,大量的網絡傳輸會嚴重影響性能,因此,我們希望通過調節本地化等待時長,如果在等待時長這段時間內,目標節點處理完成了一部分task,那么當前的task將有機會得到執行,這樣就能夠改善Spark作業的整體性能。
Spark的本地化等級如下:
名稱 | 解析 |
---|---|
PROCESS_LOCAL | 進程本地化,task和數據在同一個Executor中,性能最好。 |
NODE_LOCAL | 節點本地化,task和數據在同一個節點中,但是task和數據不在同一個Executor中,數據需要在進程間進行傳輸。 |
RACK_LOCAL | 機架本地化,task和數據在同一個機架的兩個節點上,數據需要通過網絡在節點之間進行傳輸。 |
NO_PREF | 對于task來說,從哪里獲取都一樣,沒有好壞之分。 |
ANY | task和數據可以在集群的任何地方,而且不在一個機架中,性能最差。 |
在Spark項目開發階段,可以使用client模式對程序進行測試,此時,可以在本地看到比較全的日志信息,日志信息中有明確的task數據本地化的級別,如果大部分都是PROCESS_LOCAL,那么就無需進行調節,但是如果發現很多的級別都是NODE_LOCAL、ANY,那么需要對本地化的等待時長進行調節,通過延長本地化等待時長,看看task的本地化級別有沒有提升,并觀察Spark作業的運行時間有沒有縮短。
注意,不要將本地化等待時長延長地過長,導致因為大量的等待時長,使得Spark作業的運行時間反而增加了。
算子調優
1. mapPartitions
普通的map算子對RDD中的每一個元素進行操作,而mapPartitions算子對RDD中每一個分區進行操作。如果是普通的map算子,假設一個partition有1萬條數據,那么map算子中的function要執行1萬次,也就是對每個元素進行操作。
如果是mapPartition算子,由于一個task處理一個RDD的partition,那么一個task只會執行一次function,function一次接收所有的partition數據,效率比較高。
比如,當要把RDD中的所有數據通過JDBC寫入數據,如果使用map算子,那么需要對RDD中的每一個元素都創建一個數據庫連接,這樣對資源的消耗很大,如果使用mapPartitions算子,那么針對一個分區的數據,只需要建立一個數據庫連接。
mapPartitions算子也存在一些缺點:對于普通的map操作,一次處理一條數據,如果在處理了2000條數據后內存不足,那么可以將已經處理完的2000條數據從內存中垃圾回收掉;但是如果使用mapPartitions算子,但數據量非常大時,function一次處理一個分區的數據,如果一旦內存不足,此時無法回收內存,就可能會OOM,即內存溢出。
因此,mapPartitions算子適用于數據量不是特別大的時候,此時使用mapPartitions算子對性能的提升效果還是不錯的。(當數據量很大的時候,一旦使用mapPartitions算子,就會直接OOM)
在項目中,應該首先估算一下RDD的數據量、每個partition的數據量,以及分配給每個Executor的內存資源,如果資源允許,可以考慮使用mapPartitions算子代替map。
2. foreachPartition優化數據庫操作
在生產環境中,通常使用foreachPartition算子來完成數據庫的寫入,通過foreachPartition算子的特性,可以優化寫數據庫的性能。
如果使用foreach算子完成數據庫的操作,由于foreach算子是遍歷RDD的每條數據,因此,每條數據都會建立一個數據庫連接,這是對資源的極大浪費,因此,對于寫數據庫操作,我們應當使用foreachPartition算子。
與mapPartitions算子非常相似,foreachPartition是將RDD的每個分區作為遍歷對象,一次處理一個分區的數據,也就是說,如果涉及數據庫的相關操作,一個分區的數據只需要創建一次數據庫連接。
使用了foreachPartition算子后,可以獲得以下的性能提升:
- 對于我們寫的function函數,一次處理一整個分區的數據;
- 對于一個分區內的數據,創建唯一的數據庫連接;
- 只需要向數據庫發送一次SQL語句和多組參數;
在生產環境中,全部都會使用foreachPartition算子完成數據庫操作。foreachPartition算子存在一個問題,與mapPartitions算子類似,如果一個分區的數據量特別大,可能會造成OOM,即內存溢出。
3. filter與coalesce的配合使用
在Spark任務中我們經常會使用filter算子完成RDD中數據的過濾,在任務初始階段,從各個分區中加載到的數據量是相近的,但是一旦進過filter過濾后,每個分區的數據量有可能會存在較大差異。
如圖我們可以發現兩個問題:
- 每個partition的數據量變小了,如果還按照之前與partition相等的task個數去處理當前數據,有點浪費task的計算資源;
- 每個partition的數據量不一樣,會導致后面的每個task處理每個partition數據的時候,每個task要處理的數據量不同,這很有可能導致數據傾斜問題。
如圖所示,第二個分區的數據過濾后只剩100條,而第三個分區的數據過濾后剩下800條,在相同的處理邏輯下,第二個分區對應的task處理的數據量與第三個分區對應的task處理的數據量差距達到了8倍,這也會導致運行速度可能存在數倍的差距,這也就是數據傾斜問題。
針對上述的兩個問題,我們分別進行分析:
針對第一個問題,既然分區的數據量變小了,我們希望可以對分區數據進行重新分配,比如將原來4個分區的數據轉化到2個分區中,這樣只需要用后面的兩個task進行處理即可,避免了資源的浪費。
針對第二個問題,解決方法和第一個問題的解決方法非常相似,對分區數據重新分配,讓每個partition中的數據量差不多,這就避免了數據傾斜問題。
那么具體應該如何實現上面的解決思路?我們需要coalesce算子。
repartition與coalesce都可以用來進行重分區,其中repartition只是coalesce接口中shuffle為true的簡易實現,coalesce默認情況下不進行shuffle,但是可以通過參數進行設置。
假設我們希望將原本的分區個數A通過重新分區變為B,那么有以下幾種情況:
A > B(多數分區合并為少數分區)
① A與B相差值不大
此時使用coalesce即可,無需shuffle過程。
② A與B相差值很大
此時可以使用coalesce并且不啟用shuffle過程,但是會導致合并過程性能低下,所以推薦設置coalesce的第二個參數為true,即啟動shuffle過程。A < B(少數分區分解為多數分區)
此時使用repartition即可,如果使用coalesce需要將shuffle設置為true,否則coalesce無效。
我們可以在filter操作之后,使用coalesce算子針對每個partition的數據量各不相同的情況,壓縮partition的數量,而且讓每個partition的數據量盡量均勻緊湊,以便于后面的task進行計算操作,在某種程度上能夠在一定程度上提升性能。
注意:local模式是進程內模擬集群運行,已經對并行度和分區數量有了一定的內部優化,因此不用去設置并行度和分區數量。
4. repartition解決SparkSQL低并行度問題
常規性能調優中我們講解了并行度的調節策略,但是,并行度的設置對于Spark SQL是不生效的,用戶設置的并行度只對于Spark SQL以外的所有Spark的stage生效。
Spark SQL的并行度不允許用戶自己指定,Spark SQL自己會默認根據hive表對應的HDFS文件的split個數自動設置Spark SQL所在的那個stage的并行度,用戶自己通spark.default.parallelism參數指定的并行度,只會在沒Spark SQL的stage中生效。
由于Spark SQL所在stage的并行度無法手動設置,如果數據量較大,并且此stage中后續的transformation操作有著復雜的業務邏輯,而Spark SQL自動設置的task數量很少,這就意味著每個task要處理為數不少的數據量,然后還要執行非常復雜的處理邏輯,這就可能表現為第一個有Spark SQL的stage速度很慢,而后續的沒有Spark SQL的stage運行速度非常快。
為了解決Spark SQL無法設置并行度和task數量的問題,我們可以使用repartition算子。
Spark SQL這一步的并行度和task數量肯定是沒有辦法去改變了,但是,對于Spark SQL查詢出來的RDD,立即使用repartition算子,去重新進行分區,這樣可以重新分區為多個partition,從repartition之后的RDD操作,由于不再設計Spark SQL,因此stage的并行度就會等于你手動設置的值,這樣就避免了Spark SQL所在的stage只能用少量的task去處理大量數據并執行復雜的算法邏輯。
5. reduceByKey本地聚合
reduceByKey相較于普通的shuffle操作一個顯著的特點就是會進行map端的本地聚合,map端會先對本地的數據進行combine操作,然后將數據寫入給下個stage的每個task創建的文件中,也就是在map端,對每一個key對應的value,執行reduceByKey算子函數。
reduceByKey算子的執行過程如下:
使用reduceByKey對性能的提升如下:
1.本地聚合后,在map端的數據量變少,減少了磁盤IO,也減少了對磁盤空間的占用;
2.本地聚合后,下一個stage拉取的數據量變少,減少了網絡傳輸的數據量;
3.本地聚合后,在reduce端進行數據緩存的內存占用減少;
4.本地聚合后,在reduce端進行聚合的數據量減少。
基于reduceByKey的本地聚合特征,我們應該考慮使用reduceByKey代替其他的shuffle算子。比如groupByKey。
Shuffle調優
1. 調節map端緩沖區大小
在Spark任務運行過程中,如果shuffle的map端處理的數據量比較大,但是map端緩沖的大小是固定的,可能會出現map端緩沖數據頻繁spill溢寫到磁盤文件中的情況,使得性能非常低下,通過調節map端緩沖的大小,可以避免頻繁的磁盤IO操作,進而提升Spark任務的整體性能。
map端緩沖的默認配置是32KB,如果每個task處理640KB的數據,那么會發生640/32 = 20次溢寫,如果每個task處理64000KB的數據,機會發生64000/32=2000此溢寫,這對于性能的影響是非常嚴重的。
map端緩沖的配置方法:
val conf = new SparkConf()
.set("spark.shuffle.file.buffer", "64")
2. 調節reduce端拉取數據緩沖區大小
Spark Shuffle過程中,shuffle reduce task的buffer緩沖區大小決定了reduce task每次能夠緩沖的數據量,也就是每次能夠拉取的數據量,如果內存資源較為充足,適當增加拉取數據緩沖區的大小,可以減少拉取數據的次數,也就可以減少網絡傳輸的次數,進而提升性能。
reduce端數據拉取緩沖區的大小可以通過spark.reducer.maxSizeInFlight參數進行設置,默認為48MB,該參數的設置方法:
val conf = new SparkConf()
.set("spark.reducer.maxSizeInFlight", "96")
3. 調節reduce端拉取數據重試次數
Spark Shuffle過程中,reduce task拉取屬于自己的數據時,如果因為網絡異常等原因導致失敗會自動進行重試。對于那些包含了特別耗時的shuffle操作的作業,建議增加重試最大次數(比如60次),以避免由于JVM的full gc或者網絡不穩定等因素導致的數據拉取失敗。在實踐中發現,對于針對超大數據量(數十億~上百億)的shuffle過程,調節該參數可以大幅度提升穩定性。
reduce端拉取數據重試次數可以通過spark.shuffle.io.maxRetries參數進行設置,該參數就代表了可以重試的最大次數。如果在指定次數之內拉取還是沒有成功,就可能會導致作業執行失敗,默認為3.
該參數的設置方法:
val conf = new SparkConf()
.set("spark.shuffle.io.maxRetries", "6")
4. 調節reduce端拉取數據等待間隔
Spark Shuffle過程中,reduce task拉取屬于自己的數據時,如果因為網絡異常等原因導致失敗會自動進行重試,在一次失敗后,會等待一定的時間間隔再進行重試,可以通過加大間隔時長(比如60s),以增加shuffle操作的穩定性。
reduce端拉取數據等待間隔可以通過spark.shuffle.io.retryWait參數進行設置,默認值為5s。
該參數的設置方法:
val conf = new SparkConf()
.set("spark.shuffle.io.retryWait", "60s")
5. 調節SortShuffle排序操作閾值
對于SortShuffleManager,如果shuffle reduce task的數量小于某一閾值則shuffle write過程中不會進行排序操作,而是直接按照未經優化的HashShuffleManager的方式去寫數據,但是最后會將每個task產生的所有臨時磁盤文件都合并成一個文件,并會創建單獨的索引文件。
當你使用SortShuffleManager時,如果的確不需要排序操作,那么建議將這個參數調大一些,大于shuffle read task的數量,那么此時map-side就不會進行排序了,減少了排序的性能開銷,但是這種方式下,依然會產生大量的磁盤文件,因此shuffle write性能有待提高。
SortShuffleManager排序操作閾值的設置可以通過spark.shuffle.sort. bypassMergeThreshold這一參數進行設置,默認值為200。
該參數的設置方法:
val conf = new SparkConf()
.set("spark.shuffle.sort.bypassMergeThreshold", "400")
JVM調優
1. 降低cache操作的內存占比
1). 靜態內存管理機制
根據Spark靜態內存管理機制,堆內存被劃分為了兩塊,Storage和Execution。Storage主要用于緩存RDD數據和broadcast數據,Execution主要用于緩存在shuffle過程中產生的中間數據,Storage占系統內存的60%,Execution占系統內存的20%,并且兩者完全獨立。
在一般情況下,Storage的內存都提供給了cache操作,但是如果在某些情況下cache操作內存不是很緊張,而task的算子中創建的對象很多,Execution內存又相對較小,這回導致頻繁的minor gc,甚至于頻繁的full gc,進而導致Spark頻繁的停止工作,性能影響會很大。
在Spark UI中可以查看每個stage的運行情況,包括每個task的運行時間、gc時間等等,如果發現gc太頻繁,時間太長,就可以考慮調節Storage的內存占比,讓task執行算子函數式,有更多的內存可以使用。
Storage內存區域可以通過spark.storage.memoryFraction參數進行指定,默認為0.6,即60%,可以逐級向下遞減。
內存占比設置:
val conf = new SparkConf()
.set("spark.storage.memoryFraction", "0.4")
2). 統一內存管理機制
根據Spark統一內存管理機制,堆內存被劃分為了兩塊,Storage和Execution。Storage主要用于緩存數據,Execution主要用于緩存在shuffle過程中產生的中間數據,兩者所組成的內存部分稱為統一內存,Storage和Execution各占統一內存的50%,由于動態占用機制的實現,shuffle過程需要的內存過大時,會自動占用Storage的內存區域,因此無需手動進行調節。
2. 調節Executor堆外內存
Executor的堆外內存主要用于程序的共享庫、Perm Space、 線程Stack和一些Memory mapping等, 或者類C方式allocate object。
有時,如果你的Spark作業處理的數據量非常大,達到幾億的數據量,此時運行Spark作業會時不時地報錯,例如shuffle output file cannot find,executor lost,task lost,out of memory等,這可能是Executor的堆外內存不太夠用,導致Executor在運行的過程中內存溢出。
stage的task在運行的時候,可能要從一些Executor中去拉取shuffle map output文件,但是Executor可能已經由于內存溢出掛掉了,其關聯的BlockManager也沒有了,這就可能會報出shuffle output file cannot find,executor lost,task lost,out of memory等錯誤,此時,就可以考慮調節一下Executor的堆外內存,也就可以避免報錯,與此同時,堆外內存調節的比較大的時候,對于性能來講,也會帶來一定的提升。
默認情況下,Executor堆外內存上限大概為300多MB,在實際的生產環境下,對海量數據進行處理的時候,這里都會出現問題,導致Spark作業反復崩潰,無法運行,此時就會去調節這個參數,到至少1G,甚至于2G、4G。
Executor堆外內存的配置需要在spark-submit腳本里配置:
--conf spark.yarn.executor.memoryOverhead=2048
以上參數配置完成后,會避免掉某些JVM OOM的異常問題,同時,可以提升整體Spark作業的性能。
3. 調節連接等待時長
在Spark作業運行過程中,Executor優先從自己本地關聯的BlockManager中獲取某份數據,如果本地BlockManager沒有的話,會通過TransferService遠程連接其他節點上Executor的BlockManager來獲取數據。
如果task在運行過程中創建大量對象或者創建的對象較大,會占用大量的內存,這回導致頻繁的垃圾回收,但是垃圾回收會導致工作現場全部停止,也就是說,垃圾回收一旦執行,Spark的Executor進程就會停止工作,無法提供相應,此時,由于沒有響應,無法建立網絡連接,會導致網絡連接超時。
在生產環境下,有時會遇到file not found、file lost這類錯誤,在這種情況下,很有可能是Executor的BlockManager在拉取數據的時候,無法建立連接,然后超過默認的連接等待時長60s后,宣告數據拉取失敗,如果反復嘗試都拉取不到數據,可能會導致Spark作業的崩潰。這種情況也可能會導致DAGScheduler反復提交幾次stage,TaskScheduler返回提交幾次task,大大延長了我們的Spark作業的運行時間。
連接等待時長需要在spark-submit腳本中進行設置:
--conf spark.core.connection.ack.wait.timeout=300
調節連接等待時長后,通常可以避免部分的XX文件拉取失敗、XX文件lost等報錯。