手把手教你Spark性能調優(yōu)
0、背景
日前接到反饋,集群部分 spark 任務執(zhí)行很慢,且經常出錯,參數(shù)改來改去怎么都無法優(yōu)化其性能和解決頻繁隨機報錯的問題。
看了下任務的歷史運行情況,平均時間 3h 左右,而且極其不穩(wěn)定,偶爾還會報錯:
1、優(yōu)化思路
任務的運行時間跟什么有關?
(1)數(shù)據(jù)源大小差異
在有限的計算下,job的運行時長和數(shù)據(jù)量大小正相關,在本例中,數(shù)據(jù)量大小基本穩(wěn)定,可以排除是日志量級波動導致的問題:
(2)代碼本身邏輯缺陷
比如代碼里重復創(chuàng)建、初始化變量、環(huán)境、RDD資源等,隨意持久化數(shù)據(jù)等,大量使用 shuffle 算子等,比如reduceByKey、join等算子。
在這份100行的代碼里,一共有 3 次 shuffle 操作,任務被 spark driver 切分成了 4 個 stage 串行執(zhí)行,代碼位置如下:
咱們需要做的就是從算法和業(yè)務角度盡可能減少 shuffle 和 stage,提升并行計算性能,這塊是個大的話題,本次不展開詳述。
(3)參數(shù)設置不合理
這塊技巧相對通用,咱們來看看之前的核心參數(shù)設置:
- num-executors=10 || 20 ,executor-cores=1 || 2, executor-memory= 10 || 20,driver-memory=20,spark.default.parallelism=64
假設咱們的 spark 隊列資源情況如下:
- memory=1T,cores=400
參數(shù)怎么設置在這里就有些技巧了,首先得明白 spark 資源的分配和使用原理:
在默認的非動態(tài)資源分配場景下, spark 是預申請資源,任務還沒起跑就獨占資源,一直到整個 job 所有 task 結束,比如你跳板機起了一個 spark-shell 一直沒退出,也沒執(zhí)行任務,那也會一直占有所有申請的資源。(如果設置了 num-executors,動態(tài)資源分配會失效)
注意上面這句話,spark 的資源使用分配方式和 mapreduce/hive 是有很大差別的,如果不理解這個問題就會在參數(shù)設置上引發(fā)其它問題。
比如 executor-cores 設多少合適?少了任務并行度不行,多了會把整個隊列資源獨占耗光,其他同學的任務都無法執(zhí)行,比如上面那個任務,在 num-executors=20 executor-cores=1 executor-memory= 10 的情況下,會獨占20個cores,200G內存,一直持續(xù)3個小時。
那針對本case中的任務,結合咱們現(xiàn)有的資源,如何設置這 5 個核心參數(shù)呢?
- 1) executor_cores*num_executors 不宜太小或太大!一般不超過總隊列 cores 的 25%,比如隊列總 cores 400,***不要超過100,最小不建議低于 40,除非日志量很小。
- 2) executor_cores 不宜為1!否則 work 進程中線程數(shù)過少,一般 2~4 為宜。
- 3) executor_memory 一般 6~10g 為宜,***不超過 20G,否則會導致 GC 代價過高,或資源浪費嚴重。
- 4) spark_parallelism 一般為 executor_cores*num_executors 的 1~4 倍,系統(tǒng)默認值 64,不設置的話會導致 task 很多的時候被分批串行執(zhí)行,或大量 cores 空閑,資源浪費嚴重。
- 5) driver-memory 早前有同學設置 20G,其實 driver 不做任何計算和存儲,只是下發(fā)任務與yarn資源管理器和task交互,除非你是 spark-shell,否則一般 1-2g 就夠了。
Spark Memory Manager:
- 6)spark.shuffle.memoryFraction(默認 0.2) ,也叫 ExecutionMemory。這片內存區(qū)域是為了解決 shuffles,joins, sorts and aggregations 過程中為了避免頻繁IO需要的buffer。如果你的程序有大量這類操作可以適當調高。
- 7)spark.storage.memoryFraction(默認0.6),也叫 StorageMemory。這片內存區(qū)域是為了解決 block cache(就是你顯示調用dd.cache, rdd.persist等方法), 還有就是broadcasts,以及task results的存儲。可以通過參數(shù),如果你大量調用了持久化操作或廣播變量,那可以適當調高它。
- 8)OtherMemory,給系統(tǒng)預留的,因為程序本身運行也是需要內存的, (默認為0.2)。Other memory在1.6也做了調整,保證至少有300m可用。你也可以手動設置 spark.testing.reservedMemory . 然后把實際可用內存減去這個reservedMemory得到 usableMemory。 ExecutionMemory 和 StorageMemory 會共享usableMemory * 0.75的內存。0.75可以通過 新參數(shù) spark.memory.fraction 設置。目前spark.memory.storageFraction 默認值是0.5,所以ExecutionMemory,StorageMemory默認情況是均分上面提到的可用內存的。
例如,如果需要加載大的字典文件,可以增大executor中 StorageMemory 的大小,這樣就可以避免全局字典換入換出,減少GC,在這種情況下,我們相當于用內存資源來換取了執(zhí)行效率。
效果如下:
(4)通過執(zhí)行日志分析性能瓶頸
***的任務還需要一個小時,那這一個小時究竟耗在哪了?按我的經驗和理解,一般單天的數(shù)據(jù)如果不是太大,不涉及復雜迭代計算,不應該超過半小時才對。
由于集群的 Spark History Server 還沒安裝調試好,沒法通過 spark web UI 查看歷史任務的可視化執(zhí)行細節(jié),所以我寫了個小腳本分析了下前后具體的計算耗時信息,可以一目了然的看到是哪個 stage 的問題,有針對性的優(yōu)化。
可以看到優(yōu)化后的瓶頸主要在***寫 redis 的階段,要把 60G 的數(shù)據(jù),25億條結果寫入 redis,這對 redis 來說是個挑戰(zhàn),這個就只能從寫入數(shù)據(jù)量和 kv 數(shù)據(jù)庫選型兩個角度來優(yōu)化了。
(5)其它優(yōu)化角度
當然,優(yōu)化和高性能是個很泛、很有挑戰(zhàn)的話題,除了前面提到的代碼、參數(shù)層面,還有怎樣防止或減少數(shù)據(jù)傾斜等,這都需要針對具體的場景和日志來分析,此處也不展開。
2、spark 初學者的一些誤區(qū)
對于初學者來說 spark 貌似無所不能而且高性能,甚至在某些博客、技術人眼里 spark 取代 mapreduce、hive、storm 分分鐘的事情,是大數(shù)據(jù)批處理、機器學習、實時處理等領域的銀彈。但事實確實如此嗎?
從上面這個 case 可以看到,會用 spark、會調 API 和能用好 spark,用的恰到好處是兩碼事,這要求咱們不僅了解其原理,還要了解業(yè)務場景,將合適的技術方案、工具和合適的業(yè)務場景結合——這世上本就不存在什么銀彈。。。
說道 spark 的性能,想要它快,就得充分利用好系統(tǒng)資源,尤其是內存和CPU:核心思想就是能用內存 cache 就別 spill 落磁盤,CPU 能并行就別串行,數(shù)據(jù)能 local 就別 shuffle。
別手握屠龍寶刀,卻用來切水果,還嫌不利索。:)