從 GFS 失敗的架構設計來看一致性的重要性

技術瑣話發表於2019-06-04

從 GFS 失敗的架構設計來看一致性的重要性

作者簡介 陳東明,餓了麼北京技術中心架構組負責人,負責餓了麼的產品線架構設計以及餓了麼基礎架 構研發工作。曾任百度架構師,負責百度即時通訊產品的架構設計。具有豐富的大規模系統構 建和基礎架構的研發經驗,善於複雜業務需求下的大併發、分散式系統設計和持續最佳化。作者微信公眾號dongming_cdm,歡迎關注。

GFS(Google File System)是Google公司開發的一款分散式檔案系統。

在2003年,Google 發表一篇論文詳細描述了GFS的架構。GFS,MapReduce,Bigt able並稱為Google的三架⻢ ⻋,推動了Google的高速發展。其他互聯公司和開源領域紛紛模仿,構建自己的系統。可以 這麼說,GFS,MapReduce,Bigt able引領了網際網路公司的分散式技術的發展。但GFS架構設 計並不是一個完美的架構設計,它有諸多方面的問題,一致性的欠缺就是其中的一個問題。

本文探討一下GFS架構設計、分析其在一致性方面的設計不足,並且看一下一致性對分散式系統 的重要性。

從 GFS 失敗的架構設計來看一致性的重要性

我們從GFS的介面設計說起。

介面

GFS採用了人們非常熟悉的介面,但是被沒有實現如POSIX的標準介面。通常的一些操作包 括:create, delete, open, close, read, write, record append等。create,delete,open,close 和POSIX的介面類似,這裡就不強調說了。這裡詳細講述一下write, record append提供的語意。

  • write操作可以將任意⻓度len的資料寫入到任意指定檔案的位置off set

  • record append操作可以原子的將len<=16MB的資料寫入到指定檔案的末尾

之所以GFS設計這個介面,是因為record append不是簡單的offset等於檔案末尾的write操作。 record append是具有原子特性的操作,本文後面會詳細解釋這個原子特性。

write和record append操作都允許多個客戶端併發操作。

架構

GFS架構如下圖。(摘自GFS的論文)

從 GFS 失敗的架構設計來看一致性的重要性

主要的架構部件有GFS client, GFS master, GFS chunkserver。一個GFS叢集包括:一個 master,多個chunkserver,叢集可以被多個GFS client訪問。

GFS客戶端(GFS client)是執行在應用(Application)程式裡的程式碼,通常以SDK形式 存在。

GFS中的檔案被分割成固定大小的塊(chunk),每個塊的⻓度是固定64MB。GFS chunkserver把這些chunk儲存在本地的Linux檔案系統中,也就是本地磁碟中。通常每個 chunck會被儲存3個副本(replica)。一個chunkserver會儲存多個塊,每個塊都會有一個 標識叫做塊柄(chunk handle)

GFS 主(mast er)維護檔案系統的後設資料(met adat a),包括名字空間(namespace, 也就是常規檔案系統的中的檔案樹),訪問控制資訊,每個檔案有哪些chunk構成,chunk 儲存在哪個chunkserver上,也就是位置(locat ion)。

從 GFS 失敗的架構設計來看一致性的重要性

在這樣的架構下,檔案的讀寫基本過程簡化、抽象成如下的過程:

寫流程

1.client 向mast er傳送creat e請求,請求包含檔案路徑和檔名。mast er根據檔案路徑和檔案 名,在名字空間裡建立一個物件代表這個檔案。

2.client 向3個chunkserver傳送要寫入到檔案中的資料,每個chunkserver收到資料後,將資料 寫入到本地的檔案系統中,寫入成功後,發請求給mast er告知mast er一個chunk寫入成功, 與此同時告知client 寫入成功。

3.mast er收到chunkserver寫入成功後,記錄這個chunk與機器之間的對應關係,也就是chunk 的位置。

4.client 確認3個chunkserver都寫成功後,本次寫入成功。

這個寫流程是一個高度簡化抽象的流程,實際的寫流程是一個非常複雜的流程,要考慮到寫入 型別(即,是隨機寫還是尾部追加),還要考慮併發寫入,後面我們繼續詳細的描述寫流程, 解釋GFS是如何處理不同的寫入型別和併發寫入的。

從 GFS 失敗的架構設計來看一致性的重要性

讀流程

1.應用發起讀操作,指定檔案路徑,偏移量(off set )

2.client 根據固定的chunk大小(也即64MB),計算出資料在第幾個chunk上,也就是chunk索 引號(index)

3.client 向mast er傳送一個請求,包括檔名和索引號,mast er返回3個副本在哪3臺機器上, 也就是副本位置(location of replica)。

4.client 向其中一個副本的機器傳送請求,請求包換塊柄和位元組的讀取範圍。 

5.chunkserver根據塊柄和讀取範圍從本地的檔案系統讀取資料返回給client

這個讀流程未做太多的簡化和抽象,但實際的讀流程還會做一些最佳化,這些最佳化和本文主題關 系不大就不展開了。

從 GFS 失敗的架構設計來看一致性的重要性

寫流程詳述

我們詳細的講一下寫入流程的幾個細節。

1.名字空間管理(namespace management)和鎖保護(locking)

寫入流程需要向主傳送creat e這樣請求,來操作儲存在主上的名字空間。 如果有多個客戶端同時進行寫入操作,那麼這些客戶端也會同時操作向主傳送creat e請求。主 在同一時間收到多個請求,透過加鎖的方式,防止多個客戶端同時修改同一個檔案的後設資料。

2.租約(Lease)

client 需要向3個副本寫入資料,在併發的情況下會有多個client 同時向3個副本寫入資料。GFS 需要一個規則來管理這些資料的寫入。 這個規則簡單來講,每個chunk都只有一個副本來管理多個client 的併發寫入。也就是說,對 於一個chunk,mast er會授予其中一個副本一個租約(lease),具有租約的副本來管理所有要寫 入到這個chunk的資料寫入,這個具有租約的副本稱之為首要副本(primary)。其他的副本稱之 為二級副本(secondary)

3.變更次序(Mutation Order) 

將對檔案的寫入(不管是在任意位置的寫入,還是在末尾追加)稱之為變更(Mut at ion)。 Primary管理所有client 的併發請求,讓所有的請求按照一定順序(Order)應用到chunk上。

從 GFS 失敗的架構設計來看一致性的重要性

4.基本變更流程

1).client 詢問mast er哪個chunkserver持有這個chunk的lease,以及其他的副本的位置。如果 沒有副本持有lease,mast er挑選一個副本,通知這副本它持有lease。

2).mast er回覆client ,告述客戶端首要副本的位置,和二級副本的位置。客戶端聯絡首要副 本,如果首要副本無響應或者回復客戶端它不是首要副本,則客戶端重新聯絡主。 

3).客戶端向所有的副本推送資料。客戶端可以以任意的順序推送。每個chunkserver會快取這 些資料在緩衝區中。 

4).當所有的副本都回復說已經收到資料後,客戶端傳送一個寫入請求(write request)給首 要副本,這個請求裡標識著之前寫入的資料。首要副本收到請求後,會給寫入分配一個連續的 編號,首要副本會按照這個編號的順序,將資料寫入到本地磁碟。 

5).首要副本將這個帶有編號寫入請求轉發給其他二級副本,二級副本也會按照編號的順序, 將資料寫入本地,並且回覆首要副本資料寫入成功。 

6).當首要副本收到所有二級副本的回覆時,說明這次寫入操作成功。 

7).首要副本回復客戶端寫入成功。在任意一個副本上遇到的任意錯誤,都會報告給客戶端失敗。

前面講的writ e介面就是按照這個基本流程進行的。 下圖描述了這個基本過程。(摘自GFS的論文)

從 GFS 失敗的架構設計來看一致性的重要性

5.跨邊界變更 

如果一次寫入的資料量跨過了一個塊的邊界,那麼客戶端會把這次寫入分解成向多個chunk的 多個寫入。

6.原子記錄追加(Atomic Record Appends)

Record Appends在論文中被稱之為原子記錄追加(Atomic Record Appends),這個介面也 遵循基本的變更流程,有一些附加的邏輯:客戶端把資料推送給所有的副本後,客戶端發請求 給首要副本,首要副本收到寫入請求後,會檢查如果把這個record附加在尾部,會不會超出塊 的邊界,如果超過了邊界,它把塊中剩餘的空間填充滿(這裡填充什麼並不重要,後面我們會 解釋這塊),並且讓其他的二級副本做相同的事,再告述客戶端這次寫入應該在下一塊重試。 如果記錄適合塊剩餘的空間,則首要副本把記錄追加尾部,並且告述其他二級副本寫入資料在 同樣的位置,最後通知客戶端操作成功。

從 GFS 失敗的架構設計來看一致性的重要性

原子性

講完架構和讀寫流程,我們開始分析GFS的一致性,首先從原子性開始分析。

Write和Atomic Record Append的區別 前面講過,如果一次寫入的數量跨越了塊的邊界,那麼會分解成多個操作,writ e和record append在處理資料跨越邊界時的行為是不同的。我們舉例2個例子來說明一下。

例子1,檔案目前有2個chunk,分別是chunk1, chunk2。

Client 1要在54MB的位置寫入20MB資料。這寫入跨越了邊界,要分解成2個操作,第一個操 作寫入chunk1最後10 MB,第二個操作寫入chunk2的開頭10MB。Client 2也要在54MB的位置寫入20MB的資料。這個寫入也跨越邊界,也要分解為2個操作, 作為第三個操作寫入chunk1最後10 MB,作為第四個操作寫入chunk2的開頭10MB。

2個客戶端併發寫入資料,那麼第一個操作和第三個操作在chunk1上就是併發執行的,第二個 操作和第四個操作在chunk2上併發執行,如果chunk1的先執行第一操作再執行第三個操作, chunk2先執行第四個操作再執行第二個操作,那麼最後,在chunk1上會保留client 1的寫入的 資料,在chunk2上保留了client 2的寫入的資料。雖然client 1和client 2的寫入都成功了,但最 後既不是client 1想要的結果,也不是client 2想要的結果。最後的結果是client 1和client 2寫入 的混合。對於client 1和client 2來說,他們操作都不是原子的。

例子2,檔案目前有2個chunk,分別是chunk1, chunk2。

Client 要在54MB的位置寫入20MB資料。這寫入跨越了邊界,要分解成2個操作,第一個操作 寫入chunk1最後10 MB,第二個操作寫入chunk2的開頭10MB。chunk1執行第一個操作成功 了,chunk2執行第二個操作失敗了,也就是寫入的這部分資料,一部分是成功的,一部分是 失敗的,這也不是原子操作。

接下來看record append。由於record append最多能寫入16MB的資料,並且當寫入的資料量 超過塊的剩餘空間時,剩餘的空間會被填充,這次寫入操作會在下個塊重試,這2點保證了 record append操作只會在一個塊上生效。這樣就避免了跨越邊界分解成多個操作,從而也就 避免了,寫入的資料一部分成功一部分失敗,也避免了併發寫入資料混合在一起,這2種非原 子性的行為。

從 GFS 失敗的架構設計來看一致性的重要性

GFS原子性的含義 

所以,GFS的原子性不是指多副本之間原子性,而是指發生在多chunk上的多個操作的的原子性。可以得出這樣的推論,如果Writ e操作不跨越邊界,那麼沒有跨越邊界的writ e操作也滿足GFS 所說的原子性。

GFS多副本不具有原子性 

GFS一個chunk的副本之間是不具有原子性的,不具有原子性的副本複製,它的行為是:

一個寫入操作,如果成功,他在所有的副本上都成功,如果失敗,則有可能是一部分副本 成功,而另外一部分失敗。

在這樣的行為如下,失敗是這樣處理的:

  • 如果是write失敗,那麼客戶端可以重試,直到write成功,達到一致的狀態。但是如果在 重試達到成功以前出現當機,那麼就變成了永久的不一致了。

  • Record Append在寫入失敗後,也會重試,但是與writ e的重試不同,不是在原有的off set 上重試,而是接在失敗的記錄後面重試,這樣Record Append留下的不一致是永久的不一 致,並且還會有重複問題,如果一條記錄在一部分副本上成功,在另外一部分副本上失 敗,那麼這次Record Append就會報告給客戶端失敗,並且讓客戶端重試,如果重試後成 功,那麼在某些副本上,這條記錄就會成功的寫入2次。

我們可以得出,Record Append保證是至少一次的原子操作(at least once atomic)。

從 GFS 失敗的架構設計來看一致性的重要性

一致性

GFS把自己的一致性稱為鬆弛的一致性模型(relaxed consistency model)。這個模型分析元 資料的一致性和檔案資料的一致性,鬆弛主要是指檔案資料具有鬆弛的一致性。

後設資料的一致性
後設資料的操作都是由單一的mast er處理的,並且操作透過鎖保護,所以是保證原子的,也保 證正確性的。

檔案資料的一致性 

在說明鬆弛一致性模型之前,我們先看看這個模型中的2個概念。對於一個檔案中的區域:

  • 如果無論從哪個副本讀取,所有的客戶端都能總是看到相同的資料,那麼就叫一致的 (consist ent );

  • 在一次資料變更後,這個檔案的區域是一致的,並且客戶端可以看到這次資料變更寫入的 所有資料,那麼就叫界定的(defined)。

GFS論文中,用下面的這個表格總結了鬆弛一致性:


Write

Record Append

Serial success

defined

defined interspersed with inconsistent

Concurrent successes

consistent but undefined

Failure

inconsistent

分別說明表中的幾種情況:

1.在沒有併發的情況下,寫入不會有相互干擾,成功的寫入是界定的,那麼必然也就是一致的 

2.在有併發的情況下,成功的寫入是一致的,但不是界定的,也就是我們前面所舉的例子2。 

3.如果寫入失敗,那麼副本之間就會出現不一致。

4.Record Append能夠保證是界定的,但是在界定的區域之間夾雜著一些不一致的區域。 Record Append會填充資料,不管每個副本是否填充相同的資料,這部分割槽域都會認為是 inconsist ent 的。

從 GFS 失敗的架構設計來看一致性的重要性

如何適應鬆弛的一致性模型

這種鬆弛的一致性模型,實際上是一種不能保證一致性的模型,或者更準確的說是一致性的數 據中間夾雜不一致資料。

這些夾雜其中的不一致資料,對應用來說是不可接受的。在這種一致性下,應該如何使用GFS 那?GFS的論文中,給出了這樣幾條使用GFS的建議:依賴追加(append)而不是依賴覆蓋 (overwrite),設立檢查點(checkpointing),寫入自校驗(write self-validating),自記錄標識 (self-identifying records)。

使用方式1:只有單個寫入的情況下,按從頭到尾的方式生成檔案。 

  • 方法1.1:先臨時寫入一個檔案,再全部資料寫入成功後,將檔案改名成一個永久的名 字,檔案的讀取方只能透過永久的檔名訪問到這個檔案。 

  • 方法1.2:寫入方按一定的週期,寫入資料,寫入成功後,記錄一個寫入進度檢查點 (checkpoint ),這個檢查點包括應用級的校驗數(checksum)。讀取方只去校驗、處理檢查點之前的資料。即便寫入方出現當機的情況,重啟後的寫入方或者新的寫入方,會 從檢查點開始,繼續重新寫入資料,這樣就修復了不一致的資料。

使用方式2:多個寫入併發向一個檔案尾部追加,這個使用方式就像是一個生產消費型的訊息 佇列,多個生產者向一個檔案尾部追加訊息,消費者從檔案讀取訊息

方法2.1:使用record append介面,保證資料至少被成功的寫入一次。但是應用需要應對 不一致資料,和重複資料。

  • 為了校驗不一致資料,給每個記錄新增校驗數(checksum),讀取方透過校驗數識別 出不一致的資料,並且丟棄不一致的資料。

  • 如果應用讀取資料沒有冪等處理,那麼應用就需要過濾掉重複資料。寫入方寫入記錄時 額外寫入一個唯一的標識(ident ifier),讀取方讀取資料後透過標識辨別之前是否已經 處理過這個標識的資料。

從 GFS 失敗的架構設計來看一致性的重要性

GFS的設計哲學

可以看出基於GFS的應用需要透過一些特殊的手段來應對GFS鬆弛的一致性模型帶來的各種問 題。GFS的一致性保證對於使用者是非常不友好的,很多人第一次看到這樣的一致性保證都是 比較吃驚的。

那麼GFS為什麼要設計這樣的一致性模型那?GFS在架構上選擇這樣的設計有它自己的設計哲 學。GFS追求的是簡單、夠用的原則。GFS主要要解決的問題是如何使用廉價的伺服器儲存海 量的資料,並且達到非常高的吞吐量(GFS非常好的做到了這2點,但不是本文的主題,這裡 就不展開了),並且檔案系統本身的實現要簡單,能夠快速的實現出來(GFS的開發者在開發 完GFS之後,很快就去開發BigT able了)。GFS很好的完成了完成了這樣的目標。但是留下了 一致性問題,給使用者帶來的負擔。但是在GFS應用的前期,一致性不是問題,GFS的主要使 用者(BigT able)就是GFS開發者,他們深知應該如何使用GFS,這種不一致在BigT able中被 很好遮蔽掉(採用上面所說的方法),BigT able提供了很好的一致性保證。

但是隨著GFS使用的不斷深入,GFS簡單夠用的架構開始帶來很多問題,一致性問題僅僅是其 中的一個。主導了GFS很⻓時間的Leader Sean Quinlan在一次採訪中,詳細說明了在GFS度 過了應用的初期之後,這種簡單的架構帶來的各種問題[1]。

開源分散式檔案系統HDFS,清晰的看到了GFS的一致性模型給使用者帶來的不方便,堅定地 摒棄了GFS的一致性模型,提供了很好的一致性保證。HDFS達到了怎樣的一致性,這裡就不 在展開了,另外再做詳細的討論。

從 GFS 失敗的架構設計來看一致性的重要性

總之,保證一直性對於分散式檔案系統,甚至所有的分散式系統都是很重要的。

1.GFS: Evolution on Fast-forward, Sean Quinlan, 2009, 

m?id=1594206

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

相關文章