典型分布式系統分析: GFS


   

  本文是典型分布式系統分析系列的第二篇,關注的是GFS,一個分布式文件存儲系統。在前面介紹MapReduce的時候也提到,MapReduce的原始輸入文件和最終輸出都是存放在GFS上的,GFS保證了數據的可用性與可靠性,那么本文具體看看GFS是怎么做到的。

  GFS(Google File System)是Google研發的可伸縮、高可用、高可靠的分布式文件系統,提供了類似POSIX的API,按層級目錄來組織文件。在網絡上,有很多對該輪文的翻譯和解讀,尤其是經典論文翻譯導讀之《Google File System》這篇文章,除了對論文的翻譯,還有很多作者的思考、分析。而在本文中,還是首先介紹GFS系統設計中的一些要點,然后從伸縮性、可用性、一致性等方面進行探討。

  本文地址:http://www.cnblogs.com/xybaby/p/8967424.html

GFS系統簡介

  任何系統都是有自己適用的場景的,所以我們討論一個系統的時候,首先得明確的是,這個系統是在什么樣的環境下產生的,是為了什么目標而產生的,做了哪些假設或者限制。

  對於GFS,假設:
  • 系統是構建在普通的、廉價的機器上,因此故障是常態而不是意外

  • 系統希望存儲的是大量的大型文件(單個文件size很大)

  • 系統支持兩種類型讀操作:大量的順序讀取以及小規模的隨機讀取(large streaming reads and small random reads.)

  • 系統的寫操作主要是順序的追加寫,而不是覆蓋寫

  • 系統對於大量客戶端並發的追加寫有大量的優化,以保證寫入的高效性與一致性,主要歸功於原子操作record append

  • 系統更看重的是持續穩定的帶寬而不是單次讀寫的延遲

GFS架構

  

  

  可以看見 GFS系統由三部分組成:GFS master、GFS Client、GFS chunkserver。其中,GFS master任意時刻只有一個,而chunkserver和gfs client可能有多個。

  一份文件被分為多個固定大小的chunk(默認64M),每個chunk有全局唯一的文件句柄 -- 一個64位的chunk ID,每一份chunk會被復制到多個chunkserver(默認值是3),以此保證可用性與可靠性。chunkserver將chunk當做普通的Linux文件存儲在本地磁盤上。

  GFS master是系統的元數據服務器,維護的元數據包括:命令空間(GFS按層級目錄管理文件)、文件到chunk的映射,chunk的位置。其中,前兩者是會持久化的,而chunk的位置信息來自於Chunkserver的匯報。

  GFS master還負責分布式系統的集中調度:chunk lease管理,垃圾回收,chunk遷移等重要的系統控制。master與chunkserver保持常規的心跳,以確定chunkserver的狀態。

  GFS client是給應用使用的API,這些API接口與POSIX API類似。GFS Client會緩存從GFS master讀取的chunk信息(即元數據),盡量減少與GFS master的交互。

  

  那么一個文件讀操作的流程是這樣的:
  • 應用程序調用GFS client提供的接口,表明要讀取的文件名、偏移、長度。

  • GFS Client將偏移按照規則翻譯成chunk序號,發送給master

  • master將chunk id與chunk的副本位置告訴GFS client

  • GFS client向最近的持有副本的Chunkserver發出讀請求,請求中包含chunk id與范圍

  • ChunkServer讀取相應的文件,然后將文件內容發給GFS client。

GFS副本控制協議

  在《帶着問題學習分布式系統之中心化復制集》一文中,介紹過分布式系統中常用的副本控制協議。GFS為了可用性與可靠性,而且使用的都是普通廉價的機器,因此也采用了冗余副本機制,即將同一份數據(chunk)復制到在個物理機上(chunkserver)。

中心化副本控制協議

  GFS采用的是中心化副本控制協議,即對於副本集的更新操作有一個中心節點來協調管理,將分布式的並發操作轉化為單點的並發操作,從而保證副本集內各節點的一致性。在GFS中,中心節點稱之為Primary,非中心節點成為Secondary。中心節點是GFS Master通過lease選舉的。

數據冗余的粒度

  GFS中,數據的冗余是以Chunk為基本單位的,而不是文件或者機器。這個在劉傑的《分布式原理介紹》中有詳細描述,冗余粒度一般分為:以機器為單位,以數據段為單位。

  以機器為單位,即若干機器互為副本,副本機器之間的數據完全相同。有點是非常簡單,元數據更少。缺點是回復數據時效率不高、伸縮性不好,不能充分利用資源。

     

  上圖中,o p q即為數據段,相比以機器為粒度的副本,以數據段為獨立的副本機制,雖然維護的元數據更多一些,但系統伸縮性更好,故障恢復更迅速,資源利用率更均勻。比如上圖中,當機器1永久故障之后,需要為數據o p q各自增加一份副本,分別可以從機器2 3 4去讀數據。

  在GFS中,數據段即為Chunk,上面提到,這樣元數據會多一些,且GFS master本身又是單點,這個有沒有問題呢。GFS說,問題不大,因為GFS中,一個Chunk的信息,64byte就夠了,且Chunk本身的粒度又是很大的(64M),所以數據量不會太大,而且,在GFS master中,chunk的位置信息是不持久化的。

  在MongoDB中,則是以機器為粒度進行副本冗余的。

數據寫入過程

  在GFS中,數據流與控制流是分開的,如圖所示

  

step1 Client向master請求Chunk的副本信息,以及哪個副本(Replica)是Primary

step2 maste回復client,client緩存這些信息在本地

step2 client將數據(Data)鏈式推送到所有副本

step4 Client通知Primary提交

step5 primary在自己成功提交后,通知所有Secondary提交

step6 Secondary向Primary回復提交結果

step7 primary回復client提交結果

  首先,為什么將數據流與控制消息分開,且采用鏈式推送方法呢,目標是最大化利用每個機器的網絡帶寬,避免網絡瓶頸和高延遲連接,最小化推送延遲

  另外一種推送方式是主從模式:

  

  Client首先將數據推送到Primary,再由Primary推送到所有secodnary。很明顯,Primary的壓力會很大,在GFS中,既然是為了最大化均衡利用網絡帶寬,那么就不希望有瓶頸。而且,不管是Client還是replica都知道哪個節點離自己更近,所以能選出最優的路徑。

  而且,GFS使用TCP流式傳輸數據,以最小化延遲。一旦chunkserver收到數據,即立刻開始推送,即一個replica不用收到完整的數據再發往下一個replica。

同步的數據寫入

  上述流程中第3三步,只是將數據寫到了臨時緩存,真正生效還需要控制消息(第4 5步)。在GFS中,控制消息的寫入是同步的,即Primary需要等到所有的Secondary的回復才返回客戶端。這就是write all, 保證了副本間數據的一致性,因此可以讀取的時候就可以從任意副本讀數據。關於同步寫入、異步寫入,可參考《Distributed systems for fun and profit》。

副本一致性保證

  副本冗余的最大問題就是副本一致性問題:從不同的副本讀到的數據不一致。

  

  這里有兩個術語:consistent, defined

  consistent:對於文件區域A,如果所有客戶端從任何副本上讀到的數據都是相同的,那A就是一致的。

  defined:如果A是一致的,並且客戶端可以看到變異(mutation)寫入的完整數據,那A就是defined,即結果是可預期的。

  顯然,defined是基於consistent的,且有更高的要求。

  表1中,對於寫操作(write,在用戶指定的文件偏移處寫入),如果是順序寫,那么一定是defined;如果是並發寫,那么各個副本之間一定是一致的,但結果是undefined的,可能會出現相互覆蓋的情況。而使用GFS提供的record append這個原子操作(關於append,可以操作linux 的O_APPEND選項,即聲明是在文件的末尾寫入),內容也一定是defined。但是在表1中,寫的是“interspersed with inconsistent”,這是因為如果某個chunkserver寫入數據失敗,都會從寫入流程的step3開始重試,這就導致chunk中有一部分數據在不同的副本中是不一致的。

  record append保證了原子性寫入,而且是at least once,但不保證只寫入了一次,有可能寫入了一部分(padding)就異常了,然后需要重試;也有可能是由於其他副本寫入失敗,即使自己寫入成功了,也要再重新寫入一份。

  GFS提供的一致性保證稱之為“relaxed consistency”,relaxed是指,系統在某些情況下是不保證一致性,比如讀取到尚未完全寫完的數據(數據庫中的Dirty Read);比如上面提到的padding(可以使用checksum機制解決);比如上面提到的重復的append數據(讀取數據的應用自行保證冪等性)。在這些異常情況下,GFS是不保證一致性的,需要應用程序來處理。

  個人覺得,多個副本的寫入其實也是一個分布式事務事務,要么都寫入,要么都不寫入,如果采用類似2PC的方法,那么就不會出現padding或者重復,但是2PC代價是昂貴的,非常影響性能,所以GFS采取重試的方法來應對異常,將問題拋給應用程序。

高性能、高可用的GFS master

  在GFS中,master是單點,任意時刻,只有一個master處於active狀態。單點簡化了設計,集中式調度方便很多,也不用考慮糟心的“腦裂”問題。但是單點對系統的吞吐能力、可用性提出了挑戰。那么如何避免單點成為瓶頸?兩個可行的辦法:減少交互,快速的failover

  master需要在內存中維護元數據,同時與GFS client,chunkserver交互。至於內存,問題並不大,因為GFS系統通常處理的是大文件(GB為單位)、大分塊(默認64M)。每個64M的chunk,對應的元數據信息不超過64byte。而對於文件,使用了文件命令空間,使用前綴壓縮的話,單個文件的元數據信息也少於64byte。

  GFS client盡量較少與GFS master的交互:緩存與批量讀取(預讀取)。首先,允許Chunk的size比較大,這就減少了客戶端想master請求數據的概率。另外,client會將chunk信息緩存在本地一段時間,直到緩存過期或者應用重新打開文件,而且,GFS為chunk分配有遞增的版本號(version),client訪問chunk的時候會攜帶自己緩存的version,解決了緩存不一致的問題。

  GFS Client在請求chunk的時候,一般會多請求幾個后續chunk的信息:
  In fact, the client typically asks for multiple chunks in the same request and the master can also include the informationfor chunks immediately following those requested. This extra information sidesteps several future client-master interactionsat practically no extra cost.

  

  master的高可用是通過操作日志的冗余 + 快速failover來實現的。

  master需要持久化的數據(文件命令空間、文件到chunk的映射)會通過操作日志與checkpoint的方式存儲到多台機器,只有當元數據操作的日志已經成功flush到本地磁盤和所有master副本上才會認為其成功。這是一個write all的操作,理論上會對寫操作的性能有一定的影響,因此GFS會合並一些寫操作,一起flush,盡量減少對系統吞吐量的影響。

  對於chunk的位置信息,master是不持久化的,而是在啟動的時候從chunkserver查詢,並在與chunkserver的常規心跳消息中獲取。雖然chunk創建在哪一個chunkserver上是master指定的,但只有chunkserver對chunk的位置信息負責,chunkserver上的信息才是實時准確的,比如說當chunkserver宕掉的時候。如果在master上也維護chunk的位置信息,那么為了維護一致性視圖就得增加很多消息和機制。

a chunkserver has the final word over what chunks it does or does not have on its own disks.

There is no point in trying to maintain a consistent view of this information on the master because errors on a chunkserver may cause chunks to vanish spontaneously (e.g., a disk may go bad and be disabled) or an operator may rename a chunkserver.

  如果master故障,幾乎是可以瞬時重啟,如果master機器故障,那么會在另一台冗余機器上啟動新的master進程,當然,這個新的機器是持有所有的操作日志與checkpoint信息的。

  master 重新啟動之后(不管是原來的物理機重啟,還是新的物理機),都需要恢復內存狀態,一部分來之checkpoint與操作日志,另一部分(即chunk的副本位置信息)則來自chunkserver的匯報

系統的伸縮性、可用性、可靠性

  作為一個分布式存儲系統,需要良好的伸縮性來面對存儲業務的增長;需要在故障成為常態的時候保證高可用;最為重要的,需要保證數據的可靠性,這樣應用才放心將數據存放在系統中。

伸縮性

  GFS具有良好的伸縮性,直接往系統中添加Chunkserver即可,而且前面提到,由於是以chunk為粒度進行副本冗余,允許每次增加一個ChunkServer。系統理論上的瓶頸在於master,因此master是單點,需要在內存中維護諸多元數據,需要與GFS client、GFS chunkserver交互,但基於上面的分析,master也很難成為事實上的瓶頸。系統以chunk為粒度進行副本冗余,這樣當往系統中添加、刪除機器的時候,也不會某個chunkserver、某個文件有較大影響。

可用性

  元數據服務器(GFS master)的可用性保證在上面已經提到了,這里再來看看用戶數據(文件)的可用性。

  數據以chunk為單位冗余在多個chunkserver上,而且,默認是跨機架(rack)的冗余,這樣及時出現了影響整個機架的故障(如交換機故障、電力故障)也不會對可用性有影響。而且,跨機架也能更好的均攤對數據的讀操作,更充分利用網絡帶寬,讓應用程序更可能地找到最近的副本位置。

  當Master發現了某個chunk的冗余副本數目達不到要求時(比如某個chunkserver宕掉),會為這個chunk補充新的副本;當有新的chunkserver添加到系統中時,也會進行副本遷移--將chunk為負載較高的chunkserver遷移到負載低的chunkserver上,達到動態負載均衡的效果。

  當需要選擇一個chunkserver來持久化某個chunk時,會考慮以下因素:

  • 選擇磁盤利用率降低的chunkserver;
  • 不希望一個chunkserver在短時間創建大量chunk;
  • chunk要跨機架

可靠性

  可靠性指數據不丟失、不損壞(data corruption)。副本冗余機制保證了數據不會丟失;而GFS還提供了checksum機制,保證數據不會因為磁盤原因損壞。

  關於checksum,一個chunk被分解為多個64KB的塊,每個塊有對應32位的checksum。checksum被保存在內存中,並用利用日志持久化保存,與用戶數據是隔離的,當然,這里的內存和持久化都是在chunkserver上。當chunkserver收到讀數據請求的時候,會比對文件數據與對應的checksum,如果發現不匹配,會告知client,client從其他的讀取;同時,也會告知master,master選擇新的chunkserver來restore這個損壞的chunk

其他

第一:chunk惰性分配存儲空間

第二:使用copy on write來創建快照(snapshot)

第三:解決問題的最好方法就是不解決,交給使用者來解決:

  第一點,GFS對於文件的並發讀寫並不保證一致性,一來標准文件API就沒有保證,二來把這種問題交給用戶自己處理也大大簡化了系統的設計

  第二點,由於Chunk size較大,那么當文件較小時就只有一個chunk,如果文件讀取頻繁,對應的chunkserver就可能成為壓力。解決辦法就是用戶提高冗余級別,然后不要集中在一個時間讀取文件,分攤chunkserver的壓力。

第四:防止文件命名空間死鎖的方法:

  一個操作必須按特定的順序來申請鎖:首先按命名空間樹的層級排序,在相同層級再按字典序。

they are first ordered by level in the namespace tree and lexicographically within the same level

 

references

Google File System

經典論文翻譯導讀之《Google File System》

Distributed systems for fun and profit

分布式系統原理介紹


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM