GFS是Google在2003年提出的一個分布式文件存儲系統,是Google三劍客之一。GFS主要適用於大文件的順序讀取以及追加寫。
GFS由一個master和多個chunck server構成。master負責維護文件和塊命名空間、文件到塊的映射和每個塊副本的位置,以及對塊租約、chunck server和塊回收和遷移等進行管理。chunck server負責保存塊的副本。
日志
master會將對文件和塊命名空間以及文件到塊的映射的操作以日志的形式保存在磁盤中,用於故障恢復。而每個塊副本的位置會在啟動后由chunck server上報。只有當日志寫入成功后操作才會返回給客戶端,GFS會將多個日志合並批量寫入來加快速度。
master會通過回放操作日志來進行恢復,而為了避免恢復時間過長,當日志增長到一定程度后master就會設置checkpoint,checkpoint以B樹的形式保存,下一次恢復就可以直接從checkpoint開始。checkpoint之前的日志理論上是可以丟棄的,但為了容災,還是會保留一定量的之前的日志。
之所以使用日志而不是數據庫等來保存數據,是因為數據庫的寫入是磁盤隨機寫入,而日志追加是磁盤順序寫入,兩者的性能相差是很大的。
一致性
- 對於串行寫入來說,GFS的行為是確定的。
- 對於並行寫入來說,GFS是一致的但是結果是未定義的,例如同時寫入偏移量為50~80和60~100的位置,那么60~80這一段的結果是未定義的,可能是第一個寫入也可能是第二個,但是GFS是能保證所有副本的結果是一致的。
- 對於追加操作來說,不論是串行還是並行都是確定的。
- 當發生故障時,所有操作的結果都不能保證一致性。
讀操作
讀操作即客戶端根據文件名以及偏移量讀取數據的過程,主要有以下幾步:
- 發送文件名和偏移量到master
- master根據文件名和偏移量查找到對應的塊
- master根據chunck號查找到對應的chunck server,將chunck號以及server列表返回給客戶端
- 客戶端發送chunck號和偏移量到對應的chunck server,讀取數據。客戶端會對第三步的結果進行緩存,當再次讀取相同chunck就不用請求master了
之所以要發送整個server列表是因為客戶端可以選擇距離最近的server請求數據,同時如果當一個server不可用時,客戶端也可以直接請求其他server。
寫操作
當沒有主副本時,master找到所有具有最新副本的server,選擇一個作為主副本,增加版本號,通知所有副本更新版本號,master將版本號寫入磁盤。master選出主副本后,會提供一個租約給主副本,當租約過期后就不是主副本了。
客戶端向所有副本發送要寫入的數據(客戶端發送到最近的副本,最近的副本再鏈式轉發到其他副本),數據此時只是保存在緩存中;當收到所有副本的ACK之后,客戶端發送寫請求到主副本,主副本將選擇一個偏移量並通知所有二級副本進行寫入;當主副本收到所有二級副本ACK之后,主副本返回成功給客戶端,否則主副本就會返回失敗,客戶端將進行重試。
在寫入過程中,就可能會出現某些副本上寫入成功,某些寫入失敗的情況,此時整個追加操作是失敗的,客戶端會進行重試直到得到一個一致的結果。但是,失敗的追加操作在某些副本上的成功寫入就會導致數據的重復出現,也就是說GFS實現的語義是最少一次(at least once),當需要精確一次語義時就需要客戶端的處理了。
腦裂
當master無法連接主副本時,就需要重新選擇一個新的主副本。但是此時舊副本可能是仍然存活的,僅僅是因為網絡分區的原因而無法連通;那么就會導致兩個主副本的出現,不同的客戶端可能會同時在兩個主副本上執行寫操作,破壞系統的一致性。這個問題就稱為腦裂(split brain)。
GFS通過租約來解決腦裂問題,當master選出主副本時,會提供一個租約時間(60s),master和主副本都知道租約的過期時間。當主副本發現租約過期時,就不再處理客戶端的請求;master必須等到租約過期之后才能選擇一個新的主副本,保證舊副本已經停止工作。