GFS的需求來源
GFS設計需求來源於如何保存一個文件?
先來看上圖,Linux文件系統中一個文件存儲會分為兩部分:
metadata:存儲文件元信息,如名字,創建時間等,另外就是index,存儲文件真正內容的索引
文件內容:一個block=1024Byte
現在如果我們需要保存大文件,如果還是按照block=1024Byte,則會需要大量index數據,會有大量元數據,於是一個自然想法就是增加block大小。
我們新定義一個chunk的概念:
1 chunk = 64MB = 64 * 1024 = 65536 blocks
我們使用chunk後帶來的影響就是小文件會浪費大量空間。
於是我們可以給出GFS的架構了:
上圖中,有幾個概念需要說明:
master:存儲 metadata
chunkServer:存儲具體的文件內容
下面是hdfs中一個文件的存儲示例:
從上圖我們可以很清晰的看到存儲的過程:
將文件按128M block進行切塊
master記錄metadata:文件包含的塊,每個塊在哪個datanode上
每個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。
我們來分別看下上面兩個決策的原因。
the file and chunk namespaces 和 the mapping from files to chunks 的操作都需要記錄日誌。
記錄操作日誌是這兩個信息持久化記錄的方式,通過日誌我們能夠將整個內存中的狀態恢復出來;
通過日誌來保證操作的全局有效性
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 相互交叉。
系統交互
先來看數據寫入流程:
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將數據應用到內存中,該文件對所有客戶端可見。
文件讀取流程
此處需要考慮的是怎麼保證block的數據完整性:通過計算checksum
如果數據損壞的話呢,Chunk Server就找Master恢復數據
同時發送心跳檢查Chunk Server是否運行正常。如果有服務器掛掉的話就向Master申請恢復
總結
本文首先介紹了Linux文件系統存儲文件的方案,指出瞭如果要存儲大文件,需要將block大小提升,於是有了Chunk,1chunk=64MB=64 * 1024 = 65536 blocks,同時我們將文件的metadata存儲在master,而chunk存儲在chunkServer上,為了保證數據的高可用,我們對一個chunk保存了3個副本,每個副本儘量分佈在不同的機器上,接著我們指出了gfs的架構:單master,多slave,並且我們指出了萱蕚單master的架構的原因:可以大大的簡化系統設計,所有的事務操作通過master就能保證有序;最後我們介紹了文件讀和寫的流程,所有的數據交互都讓client和slave直接交互。
你的鼓勵是我繼續寫下去的動力,期待我們共同進步。
閱讀更多 程序猿的進擊之路 的文章