來自 Facebook 的 Spark 大作業調優經驗

Facebook Spark 的使用情況

在介紹下面文章之前我們來看看 Facebook 的 Spark 使用情況:

如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop

Spark 是 Facebook 內部最大的 SQL 查詢引擎(按 CPU 使用率計算)在存儲計算分離的集羣上使用 Spark,也就是存儲和計算資源可以單獨擴展升級。考慮到 Facebook 的規模,效率是 Spark 的首要任務,主要包括以下兩個效率:計算效率:優化CPU和內存使用,CPU 的 40% 時間花在讀寫上。存儲效率:優化磁盤大小和IOPS:存儲格式對磁盤佔用大小和 IOPS 有很大影響,Facebook 數據倉庫底層存儲格式使用的是 ORC。

擴展 Spark Driver

動態資源分配

在 Facebook,Spark 集羣啓用了動態資源分配(Dynamic Executor Allocation),以便更好的使用集羣資源,而且在 Facebook 內部,Spark 是運行在多租戶的集羣上,所以這個也是非常合適的。比如典型的配置如下:spark.dynamicAllocation.enabled = truespark.dynamicAllocation.executorIdleTimeout = 2mspark.dynamicAllocation.minExecutors = 1spark.dynamicAllocation.maxExecutors = 2000

如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop

多線程事件處理

在 Spark 2.3 版本之前,事件處理是單線程的架構,也就是說,事件隊列裏面的事件得一個一個處理。如果你的作業很大,並且有很多 tasks,很可能會導致事件處理出現延遲,進一步導致作業性能出現問題,甚至使當前作業失敗。爲了解決這個問題,SPARK-18838 這個 ISSUE 引入了多線程事件處理架構,每個事件都有其單獨的單線程 executor service 去處理,這樣就可以大大減少事件處理延時的問題。另外,由於每類事件都有單獨的事件隊列,所以會增加 Driver 端的內存佔用。

如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop

更好的 Fetch 失敗處理

在 Spark 2.3 版本之前,如果 Spark 探測到 fetch failure,那麼它會把產生這個 shuffle 文件的 Executor 移除掉。但是如果這個 Executor 所在的機器有很多 Executor,而且是因爲這臺機器掛掉導致 fetch failure,那麼會導致很多的 fetch 重試,這種處理機制很低下。SPARK-19753 這個 ISSUE 使得 Spark 可以把上述場景所有 Executor 的 shuffle 文件移除,也就是不再去重試就知道 shuffle 文件不可用。

如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公衆號:iteblog_hadoop

另外,Spark 最大 Fetch 重試次數也可以通過 spark.max.fetch.failures.per.stage 參數配置。

FetchFailed 會在 ShuffleReader 取數據失敗 N 次後拋出,然後由 executor 通過 statusUpdate 傳到 driver 端,實際的處理會在 DAGScheduler.handleTaskCompletion,它會重新提交該 Stage 和該 Stage 對應的 ShuffleMapStage,重試次數超過 spark.stage.maxConsecutiveAttempts 時會退出。

RPC 服務線程調優

當 Spark 同時運行大量的 tasks 時,Driver 很容易出現 OOM,這是因爲在 Driver 端的 Netty 服務器上產生大量 RPC 的請求積壓,我們可以通過加大 RPC 服務的線程數解決 OOM 問題,比如 spark.rpc.io.serverThreads = 64。

擴展 Spark Executor

在介紹 Spark Executor 調優之前,我們需要知道 Spark Executor 的內存模型,如下:

Spark 內存管理 如果想及時瞭解Spark、Hadoop或者HBase相關的文章,歡迎關注微信公共帳號:iteblog_hadoop

默認情況下,Spark 僅僅使用了堆內內存。Executor 端的堆內內存區域大致可以分爲以下四大塊:

Execution 內存:主要用於存放 Shuffle、Join、Sort、Aggregation 等計算過程中的臨時數據Storage 內存:主要用於存儲 spark 的 cache 數據,例如RDD的緩存、unroll數據;用戶內存(User Memory):主要用於存儲 RDD 轉換操作所需要的數據,例如 RDD 依賴等信息。預留內存(Reserved Memory):系統預留內存,會用來存儲Spark內部對象。這塊內容的詳細介紹可以參見過往記憶大數據這篇文章:Apache Spark 統一內存管理模型詳解。

啓用堆外內存

如果想及時瞭解Spark、Hadoop或者Hbase相關的文章,歡迎關注微信公共帳號:iteblog_hadoop

Spark 1.6 開始引入了Off-heap memory(詳見SPARK-11389)。這種模式不在 JVM 內申請內存,而是調用 Java 的 unsafe 相關 API 進行諸如 C 語言裏面的 malloc() 直接向操作系統申請內存,由於這種方式不經過 JVM 內存管理,所以可以避免頻繁的 GC,這種內存申請的缺點是必須自己編寫內存申請和釋放的邏輯。

默認情況下,堆外內存是關閉的,我們可以通過 spark.memory.offHeap.enabled 參數啓用,並且通過 spark.memory.offHeap.size 設置堆外內存大小,單位爲字節。如果堆外內存被啓用,那麼 Executor 內將同時存在堆內和堆外內存,兩者的使用互補影響,這個時候 Executor 中的 Execution 內存是堆內的 Execution 內存和堆外的 Execution 內存之和,同理,Storage 內存也一樣。相比堆內內存,堆外內存只區分 Execution 內存和 Storage 內存。大家可以根據自己的情況啓用堆外內存,進一步減少 GC 的壓力。

垃圾收集調優

Spark 內部會分配大量的連續內存緩存,如果對象大小超過32MB (G1GC 的最大區域大小),那麼由於大量的分配,G1GC 會遭受碎片問題。所以建議在 Spark 中使用 parallel GC 而不是 G1GC,一個典型的配置如下:

spark.executor.extraJavaOptions = -XX:ParallelGCThreads=4 -XX:+UseParallelGC

Shuffle 調優

Spark Shuffle 是 Spark 的核心部分,下面簡單介紹了Sort Shuffle 的過程,主要包括 Sort & Spill to disk,並在磁盤中保存成臨時的 spill files,最後合併成 Final shuffle files,並保存在磁盤中。

如果想及時瞭解Spark、Hadoop或者Hbase相關的文章,歡迎關注微信公共帳號:iteblog_hadoop

Shuffle file buffer 調優

我們都知道,磁盤訪問比內存訪問慢10 - 100K倍,所以我們可以適當調節 Shuffle buffer 的大小,涉及到的參數如:spark.shuffle.file.buffer = 1 MB,該參數用於設置 shuffle write task 的 BufferedOutputStream 的 buffer 緩衝大小。將數據寫到磁盤文件之前,會先寫入 buffer 緩衝中,待緩衝寫滿之後,纔會溢寫到磁盤。如果作業可用的內存資源較爲充足的話,可以適當增加這個參數的大小(比如64k),從而減少 shuffle write 過程中溢寫磁盤文件的次數,也就可以減少磁盤 IO 次數,進而提升性能。在實踐中發現,合理調節該參數,性能會有1%~5%的提升。spark.unsafe.sorter.spill.reader.buffer.size 參數也可以適當加大。

另外,Spark 在 Shuffle 的時候也會採用 lz4 (通過 spark.io.compression.codec 參數配置)來壓縮文件,而默認的壓縮塊大小爲 32kb,這不是最優的,所以我們可以適當調節 spark.io.compression.snappy.blockSize 參數。比如在 Facebook 這個參數設置爲 spark.io.compression.lz4.blockSize = 512KB 最多減少 20% 的 Shuffle/spill 文件。

更多關於 Shuffle 的調優可以參見過往記憶大數據 Spark性能優化:shuffle調優 文章。

擴展 External Shuffle Service

在 Shuffle Server 上緩存索引文件

在 Apache Spark 2.1.0 之前,通過 Shuffle service 獲取索引文件時並沒有緩存起來,如果訪問大量的臨時文件時, Shuffle service 會成爲瓶頸。SPARK-15074 對索引文件引入了緩存,減少了對索引文件的請求。我們可以通過 spark.shuffle.service.index.cache.entries 參數來設置最多可以緩存索引的個數。

如果想及時瞭解Spark、Hadoop或者Hbase相關的文章,歡迎關注微信公共帳號:iteblog_hadoop

shuffle service 工作線程和 backlog 調優

spark.shuffle.io.serverThreads = 128
spark.shuffle.io.backLog = 8192

Configurable shuffle registration timeout and retry

參見 SPARK-20640

spark.shuffle.registration.timeout = 2m
spark.shuffle.registration.maxAttempts = 5

應用程序調優

這個動機是在資源相同的情況下減少作業的延遲。我們可以根據表的輸入大小計算出 mapper 和 reduce 的個數,如下:

// number of mappers
num of mappers = max(256MB, inputTableSize / 50000)


// number of reducers 
num of reducers  = max(200, min(10000, max(inputTableSize / 256MB * 0.125, 200)))

猜你喜歡

1、剛剛晉升爲 Apache 頂級項目的 Hudi 如何在數據湖上玩轉增量處理

2、Apache Spark 在eBay 的優化

3、Elasticsearch如何做到億級數據查詢毫秒級返回?

4、大數據平臺架構設計沒思路?來看這篇就知道了!

過往記憶大數據微信羣,請添加微信:fangzhen0219,備註【進羣】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章