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智庫】