典型分佈式系統分析: GFS

   

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

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

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

GFS系統簡介

  任何系統都是有本身適用的場景的,因此咱們討論一個系統的時候,首先得明確的是,這個系統是在什麼樣的環境下產生的,是爲了什麼目標而產生的,作了哪些假設或者限制。數據庫

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

  • 系統但願存儲的是大量的大型文件(單個文件size很大)服務器

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

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

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

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

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

分佈式系統原理介紹

相關文章
相關標籤/搜索