大資料實踐解析(下):Spark的讀寫流程分析

petterchx發表於2021-09-11

導讀:

眾所周知,在大資料/資料庫領域,資料的儲存格式直接影響著系統的讀寫效能。spark是一種基於記憶體的快速、通用、可擴充套件的大資料計算引擎,適用於新時代的資料處理場景。在“”中,我們分析了spark的多種檔案儲存格式,以及分割槽和分桶的設計。接下來,本文透過簡單的例子來分析在Spark中的讀寫流程,主要聚焦於Spark中的高效並行讀寫以及在寫過程中如何保證事務性。

1、檔案讀

如何在Spark中做到高效的查詢處理呢?這裡主要有兩個最佳化手段:

1)減少不必要的資料處理。資料處理涉及檔案的IO以及計算,它們分別需要耗費大量的IO頻寬和CPU計算。在實際的生產環境中,這兩類資源都是有限的,同時這些操作十分耗時,很容易成為瓶頸,所以減少不必要的資料處理能有效提高查詢的效率;

以下面的查詢為例:

spark.read.parquet("/data/events")
.where("year = 2019")
.where("city = 'Amsterdam'")
.select("timestamp")

由於在events表中按照year欄位做了分割槽,那麼首先透過 year 欄位我們就可以過濾掉所有year欄位不為 2019 的分割槽:

大資料實踐解析(下):Spark的讀寫流程分析

因為檔案是parquet的檔案格式,透過謂詞下推可以幫助我們過濾掉 city 欄位不是 "Amsterdam" 的 row groups;同時,由於我們的查詢最終需要輸出的投影欄位只有 "timestamp" ,所以我們可以進行列裁剪最佳化,不用讀取其他不需要的欄位,所以最終整個查詢所讀的資料只有剩下的少部分,過濾掉了大部分的資料,提升了整體的查詢效率:

大資料實踐解析(下):Spark的讀寫流程分析

2)並行處理,這裡主流的思想分為兩類:任務並行和資料並行。任務並行指充分利用多核處理器的優勢,將大的任務分為一個個小的任務交給多個處理器執行並行處理;資料並行指現如今越來越豐富的SIMD指令,一次動作中處理多個資料,比如AVX-512可以一次處理16個32bit的整型數,這種也稱為向量化執行。當然,隨著其他新硬體的發展,並行也經常和GPU聯絡在一起。本文主要分析Spark讀流程中的任務並行。

下面是Spark中一個讀任務的過程,它主要分為三個步驟:

大資料實踐解析(下):Spark的讀寫流程分析

(1)將資料按照某個欄位進行hash,將資料儘可能均勻地分為多個大小一致的Partition;

(2)發起多個任務,每個任務對應到圖中的一個Executor;

(3)任務之間並行地進行各自負責的Partition資料讀操作,提升讀檔案效率。

2、檔案寫

Spark寫過程的目標主要是兩個:並行和事務性。其中並行的思想和讀流程一樣,將任務分配給不同的Executor進行寫操作,每個任務寫各自負責的資料,互不干擾。

大資料實踐解析(下):Spark的讀寫流程分析

為了保證寫過程的事務性,Spark在寫過程中,任何未完成的寫都是在臨時資料夾中進行寫檔案操作。如下圖所示:寫過程中,results資料夾下只存在一個臨時的資料夾_temporary;不同的job擁有各自job id的檔案目錄,相互隔離;同時在各目錄未完成的寫操作都是存在臨時資料夾下,task的每次執行都視為一個taskAttempt,並會分配一個task attempt id,該目錄下的檔案是未commit之前的寫檔案。

大資料實踐解析(下):Spark的讀寫流程分析

當task完成自己的寫任務時,會進行commit操作,commit成功後,該任務目錄下的臨時資料夾會移動,寫檔案移到對應的位置,表示該任務已經寫完成。

大資料實踐解析(下):Spark的讀寫流程分析大資料實踐解析(下):Spark的讀寫流程分析

當寫任務失敗時,首先需要刪除之前寫任務的臨時資料夾和未完成的檔案,之後重新發起該寫任務(relaunch),直到寫任務commit提交完成。

大資料實踐解析(下):Spark的讀寫流程分析

整個任務的描述可用下圖表示,如果commit成功,將寫完成檔案移動到最終的資料夾;如果未commit成功,寫失敗,刪除對應的檔案,重新發起寫任務。當寫未完成時,所有寫資料都存在對應的臨時檔案中,其他任務不可見,直到整個寫commit成功,保證了寫操作的事務性。

大資料實踐解析(下):Spark的讀寫流程分析

當所有任務完成時,所有的臨時資料夾都移動,留下最終的資料檔案,它是最終commitJob之後的結果。

大資料實踐解析(下):Spark的讀寫流程分析

本文介紹的演算法是 FileOutputCommitter v1的實現,它的commitJob階段由Driver負責依次移動資料到最終的目錄。但是在當前廣泛應用的雲環境下,通常採取存算分離的架構,這時資料一般存放在物件儲存中(如AWS S3,華為雲OBS),Spark FileOutputCommitter中的資料移動並不像HDFS檔案系統移動那麼高效,v1的commitJob過程耗時可能會非常長。為了提升FileOutputCommitter 的效能,業界提出了FileOutputCommitter v2的實現,它們可以透過 spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version = 1或2 配置項來設定,它和v1的不同點在於,每個Task在commitTask時就將檔案移動到最終的目錄,而在commitJob時,Driver只需要負責將Task留下來的空目錄刪除,這樣相比 v1 帶來好處是效能提升, 但是由於commit task時直接寫最終目錄,在執行未完成時,部分資料就對外可見。同時,如果job失敗了,成功的那部分task產生的資料也會殘留下來。這些情況導致spark寫作業的事務性和一致性無法得到保障。

其實v1也不完全一定能保證資料一致性,檔案移動過程中完成的資料對外是可見的,這部分資料外部已經可以讀取,但是正在移動和還未移動的資料對外是不可見的,而在雲環境下,這個移動耗時會進一步加長,加重資料不一致的情況。

那麼有沒有能夠使得Spark 分析在雲環境下也可以保證資料的事務性和一致性的解決方案呢?改進了v1和v2這兩種演算法,使得Spark 分析在雲環境下也可以保證資料的事務性和一致性,同時做到高效能,並且完全相容Apache Spark和Apache Flink生態, 是實現批流一體的Serverless大資料計算分析服務,歡迎點選體驗。

參考

【1】Databricks. 2020. Apache Spark's Built-In File Sources In Depth - Databricks. [online] Available at: <>.

 

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

相關文章