分散式系統(二)——GFS
- 2021 年 12 月 28 日
- 筆記
分散式存儲系統的難點
在存儲系統中,為了獲得巨大的性能加成,一個很自然的想法就是採用分片(sharding),將數據分割存儲到多台伺服器上,這樣獲得了更大的存儲容量,而且可以並行地從多台伺服器讀取數據。
我們在成百上千台伺服器上進行分片,大量基數的情況下,出現錯誤的頻率也大大提升,我們需要一個自動化的從錯誤中恢復的方法,這就引入了容錯(fault tolerance)。
實現容錯的最有用的方法就是複製(replication),使用副本存儲相同的數據。
有了副本,就需要考慮副本之間的不一致性(inconsistency)問題。
為了解決不一致性的問題,需要進行一定的設計,使各個副本之間保持一致,這需要網路通訊來令伺服器交互。因此,一致性的代價就是低性能。
這是一個循環,我們需要進行一定的權衡和犧牲。
GFS的設計目標
- 系統必須將故障視為一種常態,能夠迅速偵測、冗餘並恢復失效的組件;
- 系統需要支援大文件(數GB),並且能夠有效地進行管理;
- 系統的工作負載主要是兩種讀操作:大規模的流式讀取和小規模的隨機讀取,前者來自同一個客戶機的連續操作讀取同一個文件的連續區域,後者通常是在文件某個隨機位置讀取幾個KB,通常後者會被合併並排序按順序批量讀取,以此提高性能;
- 系統的寫工作負載時大規模的、順序的數據追加方式的寫操作,數據一旦被寫入之後文件就很少會被修改了,同時系統也要支援小規模的隨機位置寫入;
- 支援多客戶端並行追加數據到同一個文件;
- 關注批處理的性能,而非系統的響應速度。
GFS設計概述
GFS有一個master節點(邏輯上的一個),多個chunkserver,為client提供服務。文件被分割成固定大小的chunk,chunk創建的時候,master伺服器會給每個chunk分配一個不變的、全球唯一標識的64位chunk標識,chunk伺服器把chunk以linux文件的形式保存在本地的硬碟上,並且根據指定的chunk標識和位元組範圍來讀寫數據。出於可靠性考慮,每個chunk可能會被複制到多個chunkserver上。
master節點管理所有的文件系統元數據,但不存儲文件數據(這使得GFS將文件系統管理和數據存儲分開了),這些元數據包括名字空間、訪問控制資訊、文件和chunk的映射資訊、當前chunk的位置資訊。master節點還管理著系統範圍內的活動,比如chunk租用、孤兒chunk的回收、以及chunk在chunkserver之間的遷移。master節點用心跳資訊周期地和每個chunkserver通訊,發送指令到各個chunkserver並接收chunkserver的狀態資訊。
GFS客戶端程式碼以庫的形式被鏈接到client的客戶程式里,使client可以通過這些函數進入GFS系統訪問數據。
一次讀取的流程是,首先client將文件名和客戶程式指定的位元組偏移,根據固定的chunk大小轉換成文件的chunk索引,然後把文件名和chunk索引發送給master節點,master節點將相應的chunk表示和副本的位置資訊發送回client,client用文件名和chunk索引作為key來快取這些資訊。之後客戶端發送請求到其中一個最近的副本處(根據ip計算距離),請求資訊包括了chunk的標識和位元組範圍。
tips:單一的master節點雖然大大簡化了設計,但也可能導致master節點成為系統的性能瓶頸所在。因此我們必須盡量減少master節點的讀寫。
客戶端將從master獲取的元數據快取一段時間,後續的操作將直接和chunkserver進行數據讀寫,除非元數據資訊過期或者文件被client重新打開。
一些討論
chunk尺寸
chunk的大小是關鍵的設計參數之一,通常選用64MB,這個尺寸遠遠大於一般文件系統的blocksize(惰性分配可以避免因為大的chunk尺寸造成的內部碎片)。這樣做有許多優點:1. 減少了client和master的通訊,因為只需要一次和master節點的通訊就可以獲取chunk的位置資訊,之後就可以對同一個chunk進行多次的讀寫操作;2. 採用較大的chunk尺寸時,client能夠對一個塊進行多次操作,通過與chunkserver保持較長時間的tcp連接來降低網路負載;3. 較大的chunk尺寸減少了對master存儲能力的要求,同時元數據也更有可能全部放在master的記憶體中,這樣訪問速度會快很多。
大的chunk尺寸也有一定的缺陷:這通常會產生熱點問題。小的文件包含較少的chunk,如果多個client同時對小文件進行訪問,存儲這些chunk的chunkserver會成為熱點。也許多個client同時訪問一個小文件並不是GFS設計目標中的常見情景,但是當GFS初次啟動的時候,一個可執行文件在GFS上存儲為singe-chunk文件,之後這個可執行文件在數百台機器上同時啟動,此時熱點問題就出現了。為了解決熱點問題,通常使用更大的複製參數來保存可執行文件(在更多的chunkserver上複製更多份)、或者錯開系統啟動的時間、再或者允許client從其他client讀取數據(這裡就引入了一個新的通訊路徑)。
元數據
master伺服器主要存儲三種類型的元數據:文件和chunk的命名空間、文件和chunk的對應關係、每個chunk副本的存放地點。命名空間和對應關係的更新採用保存變更日誌的方式更新master伺服器的狀態,日記由作業系統記錄在磁碟上,具有持久性。存放地點不會由master伺服器持久化保存,master伺服器在啟動或者有新的chunkserver加入時,向chunkserver詢問存儲的chunk資訊,同時定期輪詢更新,以此獲得chunk副本最新的存放地點。
tips:master始終通過chunkserver獲取chunk的存放地點,這一設計源於一個思想:只有chunkserver才能最終確定一個chunk是否在它的硬碟上。例如,chunkservre的故障可能會導致chunk的消失,而這是master無法自動獲知的。
一致性
這裡的一致性是指chunk副本之間的一致性,要求所有chunk副本中保存的內容都是已定義的,並且包含最後以此修改操作寫入的數據。為了達到這樣的一致性,GFS有以下兩項措施:對chunk的所有副本的修改操作順序一致、使用chunk的版本號來檢測副本是否因為它所在的chunk伺服器宕機而錯過了修改操作導致其失效。GFS通過master伺服器和所有chunkserver的定期握手來找到失效的chunkserver,並且使用checksum來校驗數據是否損壞。一旦發現問題,數據要儘快利用有效的副本進行恢復。
系統交互
tips:設計系統時,一個重要原則時最小化所有操作和master節點之間的交互。
使用租約(lease)對chunk的多個副本進行主從管理,master節點為chunk的一個副本建立租約,把這個副本叫做主chunk,主chunk對chunk的所有更改操作進行序列化,所有的副本都遵從這個序列進行修改。
- 客戶機向master節點詢問哪一個chunkserver持有當前的租約,以及其他副本的位置(如果沒有任何一個持有租約,那麼master建立一個);
- master將主chunk的標識符和其他副本(也叫secondary副本)的位置返回給客戶機,client快取這些數據,只有當主chunk不可用,或者主chunk回復資訊表明它已經不再持有租約的時候,client才需要重新跟master節點聯繫;
- client推送數據到所有的副本上(可以以任意順序推送)(tips:chunkserver用LRU快取這些數據; 此處網路IO負載非常高,需要合理規劃網路拓撲數據流);
- 當所有副本都確認接收了數據,client發送寫請求到主chunkserver(這個請求標識了之前推送到副本的數據),主chunkserver為所有操作分配連續的序列號,在本地執行。
- 主chunkserver將寫請求傳遞給所有二級副本,二級副本以相同的順序執行這些操作;
- 二級副本完成操作後會回復主chunkserver。
- 主chunkserver回復client,如果失敗或者部分失敗,client將重新執行3~7步。