GFS 原理深入淺出

GFS的需求來源

GFS設計需求來源於如何保存一個文件?

GFS 原理深入淺出

先來看上圖,Linux文件系統中一個文件存儲會分為兩部分:

  • metadata:存儲文件元信息,如名字,創建時間等,另外就是index,存儲文件真正內容的索引

  • 文件內容:一個block=1024Byte

現在如果我們需要保存大文件,如果還是按照block=1024Byte,則會需要大量index數據,會有大量元數據,於是一個自然想法就是增加block大小。

我們新定義一個chunk的概念:

1 chunk = 64MB = 64 * 1024 = 65536 blocks

我們使用chunk後帶來的影響就是小文件會浪費大量空間。

於是我們可以給出GFS的架構了:

GFS 原理深入淺出

上圖中,有幾個概念需要說明:

  • master:存儲 metadata

  • chunkServer:存儲具體的文件內容

下面是hdfs中一個文件的存儲示例:

GFS 原理深入淺出

從上圖我們可以很清晰的看到存儲的過程:

  1. 將文件按128M block進行切塊

  2. master記錄metadata:文件包含的塊,每個塊在哪個datanode上

  3. 每個datanode上記錄block在磁盤上的位置

注意:此處block和磁盤的關係維護在slave上,可以讓文件位置改變的信息對master透明

上面架構設計的一個關鍵點:單master,單master的架構大大簡化了我們的設計。

  • 所有的關鍵信息由master維護,保證了數據的一致性。例如:不會存在每個客戶端讀取到的文件所在chunkServer的不同。

  • 所有寫經過master,可以通過master來實現全局操作的有序,避免了多master的寫亂序問題

元數據

下面我們來看master中保存的元數據有哪些?主要有下面的3類:

  • the file and chunk namespaces:文件和chunk的命名空間

  • the mapping from files to chunks:文件到chunk的映射

  • the locations of each chunk’s replicas:每個chunk所處的節點位置

上面3類信息都是直接保存在內存中的,其中第一和第二是信息是會通過記錄變更日誌的方式來保證可靠性,第三個信息則是在每個chunkserver啟動的時候將chunk信息告知給master。

我們來分別看下上面兩個決策的原因。

  1. the file and chunk namespaces 和 the mapping from files to chunks 的操作都需要記錄日誌。

  • 記錄操作日誌是這兩個信息持久化記錄的方式,通過日誌我們能夠將整個內存中的狀態恢復出來;

  • 通過日誌來保證操作的全局有效性

  1. chunk對應的server信息由chunkServer保存,並在啟動或者新加入集群的時候主動上報給master。

    因為只有chunkServer才能確認一個chunk在磁盤上的位置。我們不通過master來維護這樣一個全局信息,因為chunkServer可能隨時會fail,導致chunk對應信息失效,這樣子就減少了master和chunkServer之間的數據同步的問題。

一致性模型

分佈式系統一個關鍵就是看系統提供了什麼樣的一致性模型。

我們可以知道操作有讀、寫兩種類型,那意味著我們需要考慮的操作排列有:

  • 讀寫

  • 讀讀

  • 寫讀

  • 寫寫

上面可能有一致性問題的操作是:寫讀、寫寫,並且只有在併發情況下我們討論一致性才有意義。

以下一致性內容來自:GFS一致性總結

接著我們討論有一致性問題的操作有哪些:

  • 元數據修改

  • 寫數據:向一個文件塊中寫數據,客戶端指定要寫的數據和要寫的數據在一個文件中的偏移量。

  • 追加數據:客戶端指定要將哪些數據追加到哪個文件後,系統返回追加成功的數據的起始位置。

元數據一致性

那我們先來看元數據的一致性問題。

元數據只在master上保存,並且會先寫日誌,然後再更新內存,這就意味著我們通過日誌將所有的操作都串行了,並且在操作內存的時候會加鎖,保證元數據一致性。

文件塊的一致性

這裡只討論 一個chunk ,也就是 一個文件塊 的寫操作,不涉及整個文件的寫流程中數據和元數據的流程,原論文裡好像也沒介紹文件的寫流程。

每個chunk默認有3個副本,不同副本會存在不同節點上,master會設置1個主副本(primary),2個二級(secondary)副本。

當寫操作和追加操作失敗,數據會出現部分被修改的情況,於是肯定會出現副本不一致的情況,這時就依賴master的重備份來將好的副本備份成N份。以下只考慮操作成功的情況。

寫一個chunk+無併發

寫一個chunk時,客戶端向primary發送寫請求(一個chunk對應幾個寫請求不確定,這裡不影響理解,當做一個看就可以了)。primary確定寫操作的順序,由於沒有併發,只有一個寫請求,直接執行這個寫請求,然後再命令secondary副本執行這個寫請求。其他secondary都按照這個順序執行寫操作,保證了全局有序,並且只有當所有副本都寫成功,才返回成功,用系統延遲保證了數據強一致,即 consistent(所有副本的值都一樣) 。

這個強一致指每個寫成功後,所有客戶端都能看到這個修改。即論文中說的 defined 。defined 的意思是知道這個文件是誰寫的(那麼誰知道呢?肯定是自己知道,其他客戶端看不到文件的創建者)。也就是當前客戶端在寫完之後,再讀數據,肯定能讀到剛才自己寫的。

寫一個chunk+併發

這時primary可能同時接受到多個客戶端對自己的寫操作。舉個例子,兩個客戶端同時寫一個chunk。w1或w2代表(寫操作+數據)。下邊表示client1想將這個chunk寫成w1,client2想將這個chunk寫成w2。

client1:w1

client2:w2

於是primary要將這些寫操作按某個機制排個順序:

primary:w2,w1

然後在primary本地執行,於是這個chunk首先被寫成w2,之後被覆蓋成w1。

之後所有secondary副本都會按照這個順序來執行操作,於是所有副本都是w1,這時數據是 consistent 的,也就是副本一致的。因為所有操作都正確執行了,所以兩個client都收到寫成功了。但是誰也不能保證數據一定是自己剛才寫的,也就是 undefined 。這與最終一致性有點像(系統保證所有副本最終都一樣,但是不保證是什麼值)。

追加數據+無併發

追加數據時,會追加到最後一個chunk,其實和寫一個chunk+無併發基本一樣。

但由於追加操作和寫文件不一樣,追加操作不是冪等的,當一次追加操作沒有成功,客戶端重試時,不同副本可能被追加了不同次數。

假設追加了一個數據a

client:追加a。

第一次追加請求執行了一半失敗了,這個chunk的所有副本現在是這樣:

primary:原始數據,offset1:a

second1:原始數據,offset1:a

second2:原始數據

於是客戶端重新發送追加請求,因為primary會先執行操作再將請求發給secondary,所以primary當前文件是最長的(先不考慮primary改變的情況)。primary繼續往offset2(當前文件末尾)追加,並通知所有secondary往offset2追加,但是secondary2的offset2不是末尾,所以會先補空。如果這次追加操作成功,數據最終會是這樣:

primary:原始數據,offset1:a,offset2:a

second1:原始數據,offset1:a,offset2:a

second2:原始數據,offset1:*,offset2:a

並且給客戶端返回 offset2 。

於是數據中間一部分是 inconsistent。但是對於追加的數據是 defined 。客戶端再讀offset2,不管從可以確定讀到a。

這就是追加操作的defined interspersed with inconsistent。

追加數據+併發

兩個客戶端分別向同一個文件追加數據a和b

client1:追加a

client2:追加b

最後一個文件塊的primary接收到追加操作後進行序列化

primary:b,a

然後執行,b失敗了一次,於是client2再發送一次追b。primary再追加一次。

primary:原始數據,off1:b,off2:a,off3:b

second1:原始數據,off1:b,off2:a,off3:b

second2:原始數據,off1: ,off2:a,off3:b

client1收到GFS返回的off2(表示a追加到了文件的off2位置),client2收到off3

也滿足off2和off3是 defined ,off1是 inconsistent ,所以總體來說是 defined interspersed with inconsistent

可以看到,不管有沒有併發,追加數據都不能保證數據全部 defined,只能保證有 defined ,但是可能會與 inconsistent 相互交叉。

系統交互

先來看數據寫入流程:

GFS 原理深入淺出

0. hadoop fs -put word.txt /input

1. 創建rpc調用請求上傳文件

2. namenode 檢查目錄是否已經存在,文件是否已經上傳,是否有寫入權限

3. 返回結果,允許上傳

4. 客戶端讀取配置(block大小,副本數等),將文件切分為block

5. 上傳第一個block請求

6. namenode 檢查datanode信息

7. namenode 返回datanode位置(3副本則返回3個datanode地址)

8. 請求建立block傳輸通道,主datanode根據副本信息與其他datanode建立傳輸通道

9. 以packet為單位發送數據,只有當所有副本都寫入成功後才返回客戶端【注意:此處通過主datanode保證了各個副本之間的數據一致性】

10. 當所有block都寫入成功後,客戶端通知namenode文件寫入成功,此時namenode將數據應用到內存中,該文件對所有客戶端可見。

文件讀取流程

GFS 原理深入淺出

此處需要考慮的是怎麼保證block的數據完整性:通過計算checksum

GFS 原理深入淺出

如果數據損壞的話呢,Chunk Server就找Master恢復數據

GFS 原理深入淺出

同時發送心跳檢查Chunk Server是否運行正常。如果有服務器掛掉的話就向Master申請恢復

GFS 原理深入淺出

總結

本文首先介紹了Linux文件系統存儲文件的方案,指出瞭如果要存儲大文件,需要將block大小提升,於是有了Chunk,1chunk=64MB=64 * 1024 = 65536 blocks,同時我們將文件的metadata存儲在master,而chunk存儲在chunkServer上,為了保證數據的高可用,我們對一個chunk保存了3個副本,每個副本儘量分佈在不同的機器上,接著我們指出了gfs的架構:單master,多slave,並且我們指出了萱蕚單master的架構的原因:可以大大的簡化系統設計,所有的事務操作通過master就能保證有序;最後我們介紹了文件讀和寫的流程,所有的數據交互都讓client和slave直接交互。

你的鼓勵是我繼續寫下去的動力,期待我們共同進步。


分享到:


相關文章: