實踐場景:解決Spark流處理產生的小檔案

大資料技術前線發表於2024-02-29

來源:大資料左右手

背景

做流批一體,湖倉一體的大資料架構,常見的做法就是:

資料來源->spark Streaming->ODS(資料湖)->spark streaming->DWD(資料湖)->...

那麼資料來源->spark Streaming->ODS,以這段為例,在資料來源透過spark structured streaming寫入ODS在資料湖(Delta Lake)落盤時候必然會產生很多小檔案。

實踐場景:解決Spark流處理產生的小檔案

目的

為了在批處理spark-sql執行更快,也避免因為小檔案而導致報錯。

影響

WARNING: Failed to connect to /172.16.xx.xx:9866 for block, add to deadNodes and continue. java.net.SocketException: 
Too many open files
  1. 小檔案在批處理資料IO消耗巨大,程式可能卡死。

  2. 小檔案塊都有對應的後設資料,後設資料放在NameNode,導致需要的記憶體大大增大,增加NameNode壓力,這樣會限制了叢集的擴充套件。

  3. 在HDFS或者物件儲存中,小檔案的讀寫處理速度要遠遠小於大檔案,(定址耗時)。

解決思路

事前

(1)避免寫入時候產生過多小檔案 做好分割槽partitionBy(年,月,日), 避免小檔案過於分散 Trigger觸發時間可以設定為1分鐘,這樣會攢一批一寫入,避免秒級別寫入而產生大量小檔案(但是使用spark structured 想要做real-time不能這樣,只適合做準實時)

(2)開啟自適應框架的開關

spark.sql.adaptive.enabled true

(3)透過spark的coalesce()方法和repartition()方法

val rdd2 = rdd1.coalesce(8, true) //(true表示是否shuffle)
val rdd3 = rdd1.repartition(8)

coalesce:coalesce()方法的作用是返回指定一個新的指定分割槽的Rdd,如果是生成一個窄依賴的結果,那麼可以不發生shuffle,分割槽的數量發生激烈的變化,計算節點不足,不設定true可能會出錯。

repartition:coalesce()方法shuffle為true的情況。

事後(小檔案引起已經產生)

(1)最佳化 Delta 表的寫入,避免小檔案產生 在開源版 Spark 中,每個 executor 向 partition 中寫入資料時,都會建立一個表檔案進行寫入,最終會導致一個 partition 中產生很多的小檔案。

Databricks 對 Delta 表的寫入過程進行了最佳化,對每個 partition,使用一個專門的 executor 合併其他 executor 對該 partition 的寫入,從而避免了小檔案的產生。

實踐場景:解決Spark流處理產生的小檔案

該特性由表屬性 delta.autoOptimize.optimizeWrite 來控制:可以在建立表時指定

CREATE TABLE student (id INT, name STRING)
TBLPROPERTIES (delta.autoOptimize.optimizeWrite = true);

也可以修改表屬性

ALTER TABLE table_name
SET TBLPROPERTIES (delta.autoOptimize.optimizeWrite = true);

該特性有兩個優點:

(1)透過減少被寫入的表檔案數量,提高寫資料的吞吐量;

(2)避免小檔案的產生,提升查詢效能。

缺點:

其缺點也是顯而易見的,由於使用了一個 executor 來合併表檔案的寫入,從而降低了表檔案寫入的並行度,此外,多引入的一層 executor 需要對寫入的資料進行 shuffle,帶來額外的開銷。因此,在使用該特性時,需要對場景進行評估。

場景:

該特性適用的場景:頻繁使用 MERGE,UPDATE,DELETE,INSERT INTO,CREATE TABLE AS SELECT 等 SQL 語句的場景;

該特性不適用的場景:寫入 TB 級以上資料。

(2)自動合併小檔案

在流處理場景中,比如流式資料入湖場景下,需要持續的將到達的資料插入到 Delta 表中,每次插入都會建立一個新的表檔案用於儲存新到達的資料,假設每10s觸發一次,那麼這樣的流處理作業一天產生的表檔案數量將達到8640個,且由於流處理作業通常是 long-running 的,執行該流處理作業100天將產生上百萬個表檔案。這樣的 Delta 表,僅後設資料的維護就是一個很大的挑戰,查詢效能更是急劇惡化。

為了解決上述問題,Databricks 提供了小檔案自動合併功能,在每次向 Delta 表中寫入資料之後,會檢查 Delta 表中的表檔案數量,如果 Delta 表中的小檔案(size < 128MB 的視為小檔案)數量達到閾值,則會執行一次小檔案合併,將 Delta 表中的小檔案合併為一個新的大檔案。

該特性由表屬性 delta.autoOptimize.autoCompact 控制,和特性 delta.autoOptimize.optimizeWrite 相同,可以在建立表時指定,也可以對已建立的表進行修改。自動合併的閾值由 spark.databricks.delta.autoCompact.minNumFiles 控制,預設為50,即小檔案數量達到50會執行表檔案合併;

合併後產生的檔案最大為128MB,如果需要調整合並後的目標檔案大小,可以透過調整配置 spark.databricks.delta.autoCompact.maxFileSize 實現。

(3)手動合併小檔案(我常用,每天定時執行合併分割槽內小檔案,再去處理批任務)

自動小檔案合併會在對 Delta 表進行寫入,且寫入後表中小檔案達到閾值時被觸發。除了自動合併之外,Databricks 還提供了 Optimize 命令使使用者可以手動合併小檔案,最佳化表結構,使得表檔案的結構更加緊湊。

在實現上 Optimize 使用 bin-packing 演算法,該演算法不但會合並表中的小檔案,且合併後生成的表檔案也更均衡(表檔案大小相近)。例如,我們要對 Delta 表 student 的表檔案進行最佳化,僅需執行如下命令即可實現:(Optimize 命令不但支援全表小檔案的合併,還支援特定的分割槽的表檔案的合併)。

OPTIMIZE student WHERE date >= '2024-01-01'

附加

面試官可能會問,我執行optimize合併小檔案,但是小檔案太多了,直接卡死執行不了程式(某網際網路面試題)

(1)首先停掉程式,這裡注意deltalake因為有歷史版本這個概念,所以不存在執行一半覆蓋原來版本情況,可以基於上一個版本重新執行(考點)。

(2)第二點,大資料思想分而治之,“分”,即把複雜的任務分解為若干個“簡單的任務”來處理。

OPTIMIZE student WHERE date > '2024-01-01' and date < '2024-01-02'

因為前面做了partitionby(年月日),那麼縮小optimize範圍,在遍歷這個月的每一天日期,分治處理。

(3)第三點,大資料思想,自己不行找兄弟,加節點,加計算資源。




來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70027827/viewspace-3007647/,如需轉載,請註明出處,否則將追究法律責任。

相關文章