Serverless Spark的彈性利器 - EMR Shuffle Service

背景與動機

計算存儲分離下的剛需

計算存儲分離是雲原生的重要特徵。通常來講,計算是CPU密集型,存儲是IO密集型,他們對於硬件配置的需求是不同的。在傳統計算存儲混合的架構中,爲了兼顧計算和存儲,CPU和存儲設備都不能太差,因此犧牲了靈活性,提高了成本。在計算存儲分離架構中,可以獨立配置計算機型和存儲機型,具有極大的靈活性,從而降低成本。

存儲計算分離是新型的硬件架構,但以往的系統是基於混合架構設計的,必須進行改造才能充分利用分離架構的優勢,甚至不改造的話會報錯,例如很多系統假設本地盤足夠大,而計算節點本地盤很小;再例如有些系統在Locality上的優化在分離架構下不再適用。Spark Shuffle就是一個典型例子。衆所周知,Shuffle的過程如下圖所示。

image

每個mapper把全量shuffle數據按照partitionId排序後寫本地文件,同時保存索引文件記錄每個partition的offset和length。reduce task去所有的map節點拉取屬於自己的shuffle數據。大數據場景T級別的shuffle數據量很常見,這就要求本地磁盤足夠大,導致了跟計算存儲分離架構的衝突。因此,需要重構傳統的shuffle過程,把shuffle數據卸載到存儲節點。

穩定性和性能

除了計算存儲分離架構下的剛需,在傳統的混合架構下,目前的shuffle實現也存在重要缺陷: 大量的隨機讀寫和小數據量的網絡傳輸。考慮1000 mapper * 2000 reducer的stage,每個mapper寫128M shuffle數據,則屬於每個reduce的數據量約爲64k。從mapper的磁盤角度,每次磁盤IO請求隨機讀64K的數據; 從網絡的角度,每次網絡請求傳輸64k的數據:都是非常糟糕的pattern,導致大量不穩定和性能問題。因此,即使在混合架構下,重構shuffle也是很必要的工作。

EMR Shuffle Service設計

基於以上的動機,阿里雲EMR團隊設計開發了EMR Shuffle Service服務(以下稱ESS),同時解決了計算存儲分離和混合架構下的shuffle穩定性和性能問題。

整體設計

ESS包含三個主要角色: Master, Worker, Client。其中Master和Worker構成服務端,Client以不侵入方式集成到Spark裏。Master的主要職責是資源分配和狀態管理;Worker的主要職責是處理和存儲shuffle數據;Client的主要職責是緩存和推送shuffle數據。整體流程如下所示(其中ResourceManager和MetaService是Master的組件):

image

ESS採用Push Style的shuffle模式,每個Mapper持有一個按Partition分界的緩存區,Shuffle數據首先寫入緩存區,每當某個Partition的緩存滿了即觸發PushData。

在PushData觸發之前Client會檢查本地是否有PartitionLocation信息,該Location規定了每個Partition的數據應該推送的Worker地址。若不存在,則向Master發起getOrAllocateBuffers請求。Master收到後檢查是否已分配,若未分配則根據當前資源情況選擇互爲主從的兩個Worker並向他們發起AllocateBuffer指令。Worker收到後記錄Meta並分配內存緩存。Master收到Worker的ack之後把主副本的Location信息返回給Client。

Client開始往主副本推送數據。主副本Worker收到請求後,把數據緩存到本地內存,同時把該請求以Pipeline的方式轉發給從副本。從副本收到完整數據後立即向主副本發ack,主副本收到ack後立即向Client回覆ack。

爲了不block PushData的請求,Worker收到PushData請求後會先塞到一個queue裏,由專有的線程池異步處理。根據該Data所屬的Partition拷貝到事先分配的buffer裏,若buffer滿了則觸發flush。ESS支持多種存儲後端,包括DFS和local。若後端是DFS,則主從副本只有一方會flush,依靠DFS的雙副本保證容錯;若後端是Local,則主從雙方都會flush。

在所有的Mapper都結束後,Master會觸發StageEnd事件,向所有Worker發送CommitFiles請求,Worker收到後把屬於該Stage的buffer裏的數據flush到存儲層,close文件,並釋放buffer。Master收到所有ack後記錄每個partition對應的文件列表。若CommitFiles請求失敗,則Master標記此Stage爲DataLost。

在Reduce階段,reduce task首先向Master請求該Partition對應的文件列表,若返回碼是DataLost,則觸發Stage重算或直接abort作業。若返回正常,則直接讀取文件數據。

ESS的設計要點,一是採用PushStyle的方式做shuffle,避免了本地存儲,從而適應了計算存儲分離架構;二是按照reduce做了聚合,避免了小文件隨機讀寫和小數據量網絡請求;三是做了兩副本,提高了系統穩定性。

容錯

除了雙副本和DataLost檢測,ESS在容錯上做了很多事情保證正確性。

PushData失敗

當PushData失敗次數(Worker掛了,網絡繁忙,CPU繁忙等)超過MaxRetry後,Client會給Master發消息請求新的Partition Location,此後本Client都會使用新的Location地址。

若Revive是因爲Client端而非Worker的問題導致,則會產生同一個Partition數據分佈在不同Worker上的情況,Master的Meta組件會正確處理這種情形。

若發生WorkerLost,則會導致大量PushData同時失敗,此時會有大量同一Partition的Revive請求打到Master。爲了避免給同一個Partition分配過多的Location,Master保證僅有一個Revive請求真正得到處理,其餘的請求塞到pending queue裏,待Revive處理結束後返回同一個Location。

WorkerLost

當發生WorkerLost時,對於該Worker上的副本數據,Master向其peer發送CommitFile的請求,然後清理peer上的buffer。若Commit Files失敗,則記錄該Stage爲DataLost;若成功,則後續的PushData通過Revive機制重新申請Location。

數據冗餘

Speculation task和task重算會導致數據重複。解決辦法是每個PushData的數據片裏encode了所屬的mapId,attemptId和batchId,並且Master爲每個map task記錄成功commit的attemtpId。read端通過attemptId過濾不同的attempt數據,並通過batchId過濾同一個attempt的重複數據。

ReadPartition失敗

在DFS模式下,ReadPartition失敗會直接導致Stage重算或abort job。在Local模式,ReadPartition失敗會觸發從peer location讀,若主從都失敗則觸發Stage重算或abort job。

多backend支持

ESS目前支持DFS和Local兩種存儲後端。

跟Spark集成

ESS以不侵入Spark代碼的方式跟Spark集成,用戶只需把我們提供的Shuffle Client jar包配置到driver和client的classpath裏,並加入以下配置即可切換到ESS方式:

spark.shuffle.manager=org.apache.spark.shuffle.ess.EssShuffleManager

監控報警
我們對ESS服務端進行了較爲詳盡的監控報警並對接了Prometheus和Grafana,如下所示:

image

性能數字

TeraSort的性能數字如下(2T, 4T, 10T規模):

image

10T規模TPC-DS的性能數字如下:

image

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