Alluxio跨叢集同步機制的設計與實現

語言: CN / TW / HK

一、Alluxio 應用場景和背景

Alluxio 跨叢集同步機制的設計和實現確保了在執行多個 Alluxio 叢集時,元資料是一致的。

Alluxio 位於儲存和計算層之間,在不同的底層檔案系統(UFS)上層提供高效能快取和統一的名稱空間。雖然通過 Alluxio 對 UFS 進行更新可使 Alluxio 與 UFS 保持一致,但在某些情況下, 例如在執行多個共享某一個或多個 UFS 名稱空間的 Alluxio 叢集時,結果可能並非如此。為了確保這種情況下的一致性,Alluxio 已經實現了跨叢集同步機制,本文將對該機制進行詳細介紹。

1. 背景介紹

隨著資料量的增長,這些資料的儲存和訪問方式也變得越來越複雜。例如,資料可能位於不同的儲存系統中(S3、GCP、HDFS 等),也可能儲存在雲上或本地,或是位於不同的地理區域,還可能因為隱私或安全保護,被進一步隔離。此外,這些複雜性不僅體現在資料儲存上,還包括如何將資料用於計算,例如,資料可能儲存在雲上,而計算則在本地進行。

Alluxio 是一個數據編排平臺,通過在 UFS 上提供統一的訪問介面來降低此類複雜性,並通過提供資料本地性和快取來提高計算效能。

對於許多組織而言,執行一個 Alluxio 叢集可能就足夠了,但有些組織需要執行多個 Alluxio 叢集。例如,如果計算是在多個區域執行,那麼在每個區域執行一個 Alluxio 叢集可能會帶來更大的優勢。此外,某些組織可能出於資料隱私保護的考慮,需要執行獨立的叢集,或是希望通過執行多個叢集來提高可擴充套件性。雖然部分資料空間可能被隔離在某個叢集中,但其他資料可以在多個叢集之間共享。例如,一個叢集可能負責提取和轉換資料,而其他幾個叢集可能會查詢這些資料並進行更新。

由於每個 Alluxio 叢集可能會複製(即掛載)UFS 儲存空間的某些部分,Alluxio 會負責保持其副本與 UFS 的一致性,以便使用者查詢到最新的檔案副本。在本文中,我們將介紹在一個或多個叢集中確保 Alluxio 資料與 UFS 一致所用到的元件。

2.Alluxio 資料一致性

在分散式系統中保持資料的一致性是很複雜的,其中有幾十個不同的一致性級別,每個級別都允許不同的使用者在特定時間查詢和修改資料的不同狀態。這些一致性級別形成了一個從弱到強的範圍區間,一致性越強限制越多,通常越容易在上面搭建應用程式。Alluxio 也不例外,它會根據配置和使用的 UFS 提供不同的一致性保障(詳細資訊見 Alluxio 的資料一致性模型)。

為了簡化關於一致性的討論,我們將做如下假設:

● 對於任何檔案,UFS 都是檔案的 "唯一資料來源"。

這意味著 Alluxio 中的每個檔案都對應於 UFS 上的一個檔案,並且 UFS 中總是有該檔案的最新版本。如果 Alluxio 儲存的檔案副本與 UFS 中的檔案不同,那麼 Alluxio 中的檔案版本是不一致的。(這裡我們假設 UFS 本身確保了強一致性,即某種程度的線性一致性(linearizability)或外部一致性(external consistency)。從高層次來看,這允許使用者把 UFS(即便系統是由許多分散式部分所組成) 當作類似實時按順序執行操作的單一的檔案系統來訪問。

在討論 Alluxio 和 UFS 的一致性之前,讓我們先來看一下 Alluxio 的基本架構。Alluxio 是由 master 節點和 worker 節點組成的。master 節點負責跟蹤檔案的元資料,例如它的路徑、大小等,而 worker 節點負責儲存資料本身。如果 client 要讀一個檔案,必須先從某一個 master 節點上讀取元資料,然後用它來定位儲存該資料副本的 worker(必要時可以從 UFS 上載入資料)。如果 client 要寫一個檔案,必須首先在 master 中為該檔案建立元資料,然後通過 worker 將該檔案寫到 UFS,最後在 master 上將該檔案標記為完成。當檔案正在被寫入時,它的元資料會被標記為未完成,從而阻止其他 client 訪問該檔案。

從這個基本設計中,我們可以看到,只要所有對檔案的更新都通過 Alluxio 寫入 UFS,那麼 Alluxio 中的資料將與 UFS 中的資料保持一致,client 將會始終查詢到最新的資料版本。

然而現實情況,並沒有這麼簡單,例如,某些使用者可能在更新 UFS 時不通過 Alluxio,或者 client 可能出現故障,只將部分檔案寫入 UFS,而沒有在 Alluxio master 上標記完成,這些都可能導致 Alluxio 和 UFS 中的資料不一致。

那麼,這些問題是如何處理的呢?由於我們重點假設了 UFS 是唯一的資料來源,要解決這些不一致的問題只需讓 Alluxio 與 UFS 同步即可。

3. 元資料同步

元資料同步是用來檢查和修復 Alluxio 和 UFS 之間不一致的主要元件。當 client 訪問 Alluxio 中的某個路徑時,該功能在一定條件下(後面會討論)可能會被觸發。基本程式如下:

● 從 UFS 載入該路徑的元資料。

● 將 UFS 中的元資料與 Alluxio 中的元資料進行比較。元資料中包含檔案資料的指紋(例如最後修改時間和抗碰撞的雜湊值),可用於檢查資料不一致情況。

● 如果發現任何不一致,則更新 Alluxio 中的元資料,並標記過時的資料,以便將其從 worker 中驅逐。最新資料會根據需要從 UFS 載入到 worker。

 

 

圖:client 讀取時的元資料同步過程。1. client 讀取檔案系統中的一個路徑。2. master 上的元資料同步模組根據使用者配置檢查是否需要同步。3. 通過從 UFS 載入元資料進行同步,並建立一個指紋來比較 Alluxio 和 UFS 中的元資料。如果指紋不同,則 Alluxio 中的元資料會被更新。4. client 根據更新後的元資料從 worker 中讀取檔案資料,必要時從 UFS 中載入資料。

唯一的問題就是決定何時執行這個元資料同步程式,需要我們在更強的一致性和更好的效能之間進行權衡。

每次訪問資料時進行元資料同步

如果 Alluxio 中的 client 每次訪問一個路徑時都進行元資料同步,那麼 client 將始終能檢視到 UFS 上最新的資料狀態。這將為我們提供最高的一致性級別,通常可以達到 UFS 所能確保的最強的一致性。但是,由於每次訪問資料(即使資料沒有被修改)都會與 UFS 進行同步,這也會將導致效能下降。

基於時間進行元資料同步

另外,元資料同步可以基於一個物理時間間隔來執行。在這種情況下,Alluxio master 上的元資料包含路徑最後一次與 UFS 成功同步的時間。現在,只有當用戶定義的時間間隔過後,才會進行新的同步(詳細資訊見 UFS 元資料同步)。

雖然這種方式可能極大地提高了效能,但也導致了相對較弱級別的一致性保障,即最終一致性。這意味著,任何特定的讀取結果可能與 UFS 一致,也可能不一致。此外,資料更新被查詢到的順序可能是任意順序。例如,在 UFS 中,檔案 A 的更新實際早於另一個檔案 B,但是,Alluxio 叢集查詢到的可能是檔案 B 的更新早於檔案 A。因此,系統的使用者必須瞭解這些不同級別的一致性保障,並根據需要調整應用程式。

二、跨叢集同步機制

在上一章節,我們討論了單個 Alluxio 叢集的場景、背景以及如何進行元資料同步。本章將介紹如何在多叢集場景下實現建立元資料同步,從而確保以提供元資料一致性。

1. 基於時間同步的多叢集一致性

其中一個基於時間的元資料同步用例是使用多個 Alluxio 叢集且叢集共享部分 UFS 資料空間的場景。通常,我們可以認為這些叢集正在執行單獨的工作負載,這些工作負載可能需要在某些時間點共享資料。例如,一個叢集可能會提取和轉換來自某一天的資料,然後另一個叢集會在第二天對該資料進行查詢。執行查詢任務的叢集可能不需要總是看到最新的資料,例如可以接受最多一個小時的延遲。

在實踐中,使用基於時間的同步不一定總是有效,因為只有特定的工作負載才會定期更新檔案。事實上,對於許多工作負載來說,大部分檔案僅被寫入一次,而只有一小部分檔案會經常更新。在這種情況下,基於時間的同步效率變低,這是因為大多數同步都是不必要的,增加時間間隔將導致經常修改的檔案處於資料不一致狀態的時間更長。

2. 使用跨叢集同步(Cross Cluster Sync)實現多叢集一致性

為了避免基於時間同步的低效性,跨叢集同步功能允許直接跟蹤不一致性,因此只在必要時才會同步檔案。這意味著每當在 Alluxio 叢集上一條路徑發生更改時,該叢集將釋出一個失效訊息,通知其他 Alluxio 叢集該路徑已被修改。下次當有 client 在訂閱(跨叢集同步功能的)叢集上訪問此路徑時,將觸發與 UFS 的同步操作。

與基於時間的同步相比,跨叢集同步具有兩個主要優點。首先,只對已修改的檔案執行同步,其次,修改可以快速地對其他叢集可見,所需時間即大約等同於從一個叢集傳送訊息到另一個叢集的時間。

由此我們可以看到,當滿足以下假設時,跨叢集同步功能將是最有效用的。

● 多個 Alluxio 叢集掛載的一個或多個 UFS 中有交叉部分。(我們認為系統中部署的 Alluxio 叢集數量的合理範圍是 2-20 個)。

● 至少有一個叢集會對 UFS 上的檔案進行更新。

● 所有對 UFS 的更新都要經過 Alluxio 叢集(關於處理其他情況的方法,請參見下文 "其他用例"內容)。

現在我們要確保來自一個 Alluxio 叢集的更新將最終在其他所有 Alluxio 叢集中被監測到(即叢集與 UFS 滿足最終一致性保障),這樣應用程式就可以在叢集間共享資料。

路徑失效釋出 / 訂閱

跨叢集同步功能是基於釋出 / 訂閱(pub/sub)機制實現的。當 Alluxio 叢集掛載某個 UFS 路徑時,就會訂閱該路徑,每當叢集修改 UFS 上的檔案時,它都會向所有訂閱者釋出修改的路徑。

表 1:三個 Alluxio 叢集掛載不同的 UFS 路徑示例。

參考表 1 中的例子,有三個 Alluxio 叢集,每個叢集掛載一個不同的 S3 路徑。這裡,叢集 C1 將 S3 桶(bucket)s3://bucket/ 掛載到其本地路徑 /mnt/,叢集 C2 將同一個 bucket 的子集 s3://bucket/folder 掛載到其本地路徑 /mnt/folder,最後 C3 將 s3://bucket/other 掛載到其根路徑 /。

由此,叢集 C1 將訂閱路徑(pub/sub 語義中的“主題”)s3://bucket,叢集 C2 將訂閱路徑 s3://bucket/folder,而叢集 C3 將訂閱路徑 s3://bucket/other。訂閱者將收到所有釋出的以訂閱“主題”開頭的訊息。

例如,如果叢集 C1 建立了一個檔案 /mnt/folder/new-file.dat,它將釋出一個包含 s3://bucket/folder/new-file.dat 的無效訊息,叢集 C2 將會收到該訊息。另外,如果叢集 C1 建立了一個檔案 /mnt/other-file.dat,則不會發送任何訊息,這是因為沒有訂閱者的主題與 s3://bucket/other-file.dat 相匹配。

如前所述,Alluxio 的元資料包括該路徑最近一次同步發生的時間。在跨叢集同步的情況下,它還包含最近一次通過 pub/sub 介面收到的路徑失效資訊的時間。利用這一點,當 client 訪問一個路徑時,在以下兩種情況下將會與 UFS 進行同步。

a) 該路徑第一次被訪問。

b) 路徑的失效時間晚於最近一次同步時間。

假設系統中沒有故障,顯然最終一致性將得到保證。對檔案的每一次修改都會導致每個訂閱叢集收到一個失效訊息,從而在下一次訪問該檔案時進行同步。

圖 1:檔案建立過程中的跨叢集同步機制。A. client 在叢集 1 上建立一個檔案。B. client 將檔案寫入 worker。C. worker 把檔案寫入 UFS。D. client 在 master 上完成了該檔案。E. 叢集 1 向叢集 2 的訂閱者釋出檔案的失效訊息。F. 叢集 2 在其元資料同步元件中將該檔案標記為需要同步。以後當 client 訪問該檔案時,將同樣使用圖 1 所示的步驟 1-5 進行同步。

實現 Pub/sub 機制

Pub/sub 機制是通過發現機制(discovery mechanism)和網路元件來實現的,前者允許叢集知道其他叢集掛載了什麼路徑,後者用來發送訊息。

發現機制是一個名為 CrossClusterMaster 的單一 java 程序,須能讓所有 Alluxio 叢集通過可配置的地址 / 埠組合進行訪問。每當一個 Alluxio 叢集啟動時,都會通知 CrossClusterMaster 該叢集的所有 master 節點的地址。此外,每當叢集掛載或解除安裝 UFS 時,掛載的路徑都將被髮送到 CrossClusterMaster。每次這些值被更新時,CrossClusterMaster 節點都會把新值傳送給所有 Alluxio 叢集。

利用這些資訊,每個 Alluxio 叢集將計算其本地 UFS 掛載路徑與外部叢集的所有 UFS 掛載路徑的交集。對於每個相交的路徑,叢集的 master 將使用 GRPC 連線建立一個以該路徑為主題的訂閱給外部叢集的 master。在表 1 的例子中,C1 將向 C2 建立一個主題為 s3://bucket/folder 的訂閱,以及向 C3 建立一個主題為 s3://bucket/other 的訂閱。此外,C2 將向 C1 建立一個主題為 s3://bucket/folder 的訂閱,而 C3 將向 C1 建立一個主題為 s3://bucket/other 的訂閱。這樣一來,每當叢集要修改某個路徑時,例如建立一個檔案,它都會把修改的路徑釋出給任何主題是該路徑字首的訂閱者。例如,如果 C1 建立一個檔案 /mnt/other/file,它將釋出 s3://bucket/other/file 到 C3。

為了主動維護對其他叢集的訂閱,每個 Alluxio master 上都會執行一個執行緒,以應對路徑的掛載或解除安裝、叢集的加入或者脫離,以及出現連線故障等情況的發生。

每當訂閱者收到路徑時,它就會將失效時間元資料更新為當前時間,這樣一來,下一次 client 訪問該路徑時,就會與 UFS 進行一次同步。按照我們上面的例子,下一次 client 在叢集 C3 上讀取路徑 /file 時,將在 s3://bucket/other/file 上執行與 UFS 的同步。

確保最終一致性

如果能保證每條釋出的訊息都向所有訂閱者(包括未來的訂閱者)僅傳遞一次(exactly once) ,那麼顯然最終一致性將得到保證,因為每一次修改都會讓訂閱者在訪問路徑時進行同步。但是,連線可能中斷、叢集可能脫離和接入系統、節點也可能出現故障,我們該如何保證訊息的準確傳遞呢?簡單的答案是,我們不能。相反,只有在訂閱(使用底層 TCP 連線)處於執行狀態時,才能確保僅一次訊息傳遞。此外,當訂閱首次建立時,訂閱者將標記根路徑(主題)的元資料為需要同步。這意味著,在訂閱建立後,對於任何作為主題的超集路徑,在第一次訪問該路徑時將進行同步。

例如,當 C1 用主題 s3://bucket/folder 建立對 C2 的訂閱時,C1 將標記 s3://bucket/folder 為需要同步。然後,例如在第一次訪問 s3://bucket/folder/file 時,將進行同步。

這大大簡化了處理系統中的故障或配置變化的任務。如果某個訂閱因為任何原因而失敗,如網路問題、master 故障切換、配置變化,那麼恢復過程是一樣的——重新建立訂閱,並將相應的路徑標記為不同步。為了減輕網路問題的影響,可以設定一個使用者定義的引數,以確定有多少訊息可以快取在釋出者的傳送佇列中,以及在佇列已滿的情況下超時等待多久會發生操作阻塞的可能性。

當然,按照預期,雖然我們的系統會發生故障,但不會經常發生,否則效能會受到影響。所幸即使在頻繁發生故障的情況下,效能下降也會與使用基於時間的同步的情況相似。例如,如果每 5 分鐘發生一次故障,預計效能與啟用基於時間(5 分鐘間隔)同步下的效能類似。

請注意,如果 CrossClusterMaster 程序發生故障,那麼新的叢集和路徑掛載發現將不起作用,但叢集將保持其現有的訂閱而不會中斷。此外,CrossClusterMaster 是無狀態的(可以把它看作是叢集交換地址和掛載路徑的一個點),因此,可以在必要時停止和重新啟動。

其他用例

前面提到,為了使這個功能發揮作用,所有對 UFS 的更新都應該通過 Alluxio 叢集進行。當然這個條件不一定能滿足,有幾種方法來處理這個問題。

● 使用者可以手動將一個路徑標記為需要同步。

● 基於時間的同步可以和跨叢集同步一起啟用。

三、探討與結論

1. 探討與未來工作

為什麼不使用確保僅一次訊息傳遞的 pub/sub 機制?

我們知道,如果使用確保僅一次訊息傳遞的 pub/sub 機制會大大簡化我們的設計,而且也確實存在許多強大的系統,如 Kafka 和 RabbitMQ,正是為了解決這個問題而建立的。使用這些系統的好處是,故障對效能的影響可能較小。例如,如果某個訂閱者處連線斷開,在重新連線時,系統可以從它之前斷開的地方繼續執行。

儘管如此維護這些系統本身就是一項非常複雜的任務。首先,你需要弄清楚一些問題,比如,要部署多少個節點的物理機,要複製多少次訊息,保留多長時間,當由於連線問題而不能釋出訊息時要不要阻塞操作等。而且,最終很可能還是需要故障恢復機制,從而導致更復雜的設計。

(注意,為了保證最終一致性,我們實際上只需要至少一次 (at least once) 訊息傳遞,因為多次傳遞訊息只會對效能產生負面影響,而不會影響資料一致性,但即便在這種情況下,大部分困難仍然存在)。

擴充套件至 20 個 Alluxio 叢集以上或處理頻發故障

未來,我們希望能支援擴充套件到數百個 Alluxio 叢集,但從 20 個叢集擴充套件至數百個叢集可能有不同的設計考量。首先,我們預期故障的發生會更加頻繁;其次,設計可能會導致 master 產生大量開銷。

如前所述,故障頻繁發生會使效能降低到與採用基於時間同步時類似。在有數百個叢集的情況下,我們預期網路或 master 節點故障會相當頻繁地發生。(請注意,這也取決於配置,因為故障只會影響掛載了與故障 UFS 路徑有交集的叢集。因此,如果叢集大多掛載了不相交的 UFS 路徑,那麼可能問題不大)。此外,如果所有叢集掛載的路徑都有交集,那麼它們將必須維護對所有其他叢集的訂閱,且一個釋出就需要傳送數百條訊息。

在這種情況下,我們可能需要納入一個可靠的 pub/sub 機制,如 Kafka 或 RabbitMQ,但這裡只是替代點對點的訂閱,而不是改變整個系統的設計。故障仍然會發生,叢集將以同樣的方式恢復——將相交的 UFS 路徑標記為需要同步。只有可靠的 pub/sub 機制才會隱藏 Alluxio 的許多故障。例如,如果該機制想要可靠地儲存最後 5 分鐘的訊息,那麼只有持續時間超過 5 分鐘的故障才需要用原來的方法進行恢復。此外,這些系統能夠不考慮 Alluxio 叢集的數量進行擴充套件,在必要時新增更多節點。不過,使用和維護這些系統會產生大量的開銷,可能只有在某些配置中才值得嘗試。

關於一致性的一些看法

雖然本文介紹了確保最終一致性的基本思路,但還有幾個重要的內容沒有詳細說明。

首先,失效訊息必須在對 UFS 的修改完成後才能釋出,其次,UFS 必須線上性一致性或外部一致性(S3 中的一致性)層面上確保強一致性。如果這兩個條件中的任何一個沒有得到滿足,那麼當訂閱者收到失效資訊並執行同步時,叢集可能無法觀測到檔案的最新版本。第三,如果一個叢集與 CrossClusterMaster 的連線斷開,後來又重新建立了連線,那麼該叢集也必須經歷故障恢復過程,這是因為在連線中斷期間可能有某個外部叢集掛載並修改了路徑。

釋出完整的元資料

如前所述,釋出的失效訊息只包含被修改的路徑。但是,這些訊息也可以包括路徑的更新元資料,從而避免在訂閱叢集上進行同步。之所以不這樣做是因為無法通過常規方法知道哪個版本的元資料是最新的版本。

例如,兩個 Alluxio 叢集 C1 和 C2 在 UFS 上更新同一個檔案。在 UFS 上,叢集 C1 的更新發生在叢集 C2 的更新之前。然後,兩個叢集都將他們更新的元資料釋出到第三個叢集 C3。由於網路條件的原因,C2 的訊息比 C1 先到達。此時,C3 需要知道,它應該放棄來自 C1 的更新,因為已經有了最新的元資料版本。當然,如果元資料包含版本資訊,就可以做到這一點,但可惜對於 Alluxio 支援的所有 UFS,常規方法都做不到。因此,C3 仍然需要與 UFS 進行元資料同步,以便直接從唯一的資料來源獲得最新的版本。

訂閱通知服務

某些底層儲存系統(UFS)(例如 Amazon SNS 和 HDFS iNotify)提供通知服務,讓使用者知道檔案何時被修改了。對於這類 UFS,相較於訂閱 Alluxio 叢集,訂閱這些服務可能是更好的選擇。這樣做的好處是支援不通過 Alluxio 對 UFS 進行寫入。同樣,系統設計將保持不變,只是不訂閱其他 Alluxio 叢集,而是訂閱此類通知服務。

請注意,Alluxio 還為 HDFS 提供了 ActiveSync 功能,允許元資料與底層 UFS 保持同步。這與跨叢集的同步機制有所不同,因為 ActiveSync 在檔案更新時執行同步,而跨叢集同步只在檔案被訪問時執行同步。

四、結論

本文主要介紹了執行多個 Alluxio 叢集能帶來優勢的場景,以及 Alluxio 使用基於時間同步和跨叢集同步功能,用來保持叢集與所掛載 UFS 同步的過程。關於如何部署跨叢集同步功能的更多內容,請點選閱讀原文檢視。

想要了解更多關於Alluxio的乾貨文章、熱門活動、專家分享,可點選進入【Alluxio智庫】