以大型分布式文件系統的鼻祖GFS為例看分布式文件系統的實現原理

程序員書屋 發佈 2021-08-03T08:09:45.650457+00:00

作為網際網路技術的先驅,谷歌率先遇到了大數據(即大規模的搜索索引)的問題。2003年,谷歌發表了GFS論文,向業界介紹了其分布式文件系統設計方案。

作為網際網路技術的先驅,谷歌率先遇到了大數據(即大規模的搜索索引)的問題。2003年,谷歌發表了GFS論文,向業界介紹了其分布式文件系統設計方案。當時,著名的開源搜尋引擎Apache Lucene的作者Doug Cutting也正在被同樣的問題所困擾,看到谷歌的這篇論文後,他很吃驚,這正是他所需要的啊!於是,以此GFS的設計為藍圖,他開始用Java實現一個分布式文件系統,這就是後來的Hadoop HDFS。

受GFS設計思想影響的不只有Doug Cutting,還有國內的網際網路巨頭們。淘寶面臨的問題有些不同,它要存儲的是大量的商品快照縮略圖(促銷時為了防止商家賴帳,淘寶就將商家在促銷時打出的價格和優惠措施截圖保存起來),單個縮略圖儘管不大,但數量眾多,因此傳統的文件系統無法有效存儲[2]。通過採用和GFS類似的架構與設計,淘寶開發了自己的TFS分布式文件系統。京東的JFS和百度的BFS在架構和設計上也是與GFS非常類似的。

下面我們以大型分布式文件系統的鼻祖GFS為例,看一下分布式文件系統的實現原理。

1.關於GFS的幾點假設

GFS論文中提到了幾點假設。

  • 使用普通商用伺服器而不是專用伺服器,以降低成本。
  • 因為使用的是普通商用伺服器,且伺服器數量很多(上千台),所以硬體發生故障是常態。
  • 文件尺寸都比較大,幾個GB的文件是常態。
  • 文件的寫操作主要是在文件尾的添加操作,隨機寫很少。
  • 一旦文件生成,則主要是順序讀操作。
  • 文件系統和應用程式是緊密結合在一起的,而且是一起進行設計的。

2.GFS對外提供的接口

與單機文件系統類似,GFS也提供了文件與目錄的抽象和類似於POSIX[3]文件系統API的編程接口,支持文件和目錄的創建、刪除、打開、關閉、讀和寫操作。此外,GFS還支持高效的取快照(snapshot)操作和滿足原子性的記錄追加(record append)操作。

GFS提供了一個客戶端庫,實現了所有對外提供的API。

3.GFS架構

圖9-1展示的是GFS的架構。

整個系統包括幾個角色:多個GFS客戶端(GFS Client)、一個GFS主伺服器(GFS Master Server)、0個或多個GFS影子主伺服器(GFS Shadow Master Server)和多個GFS數據塊伺服器(GFS Chunk Server)。

  • GFS客戶端:客戶端通過GFS提供的客戶端庫使用GFS提供的功能。
  • GFS主伺服器:主伺服器上面存放了整個GFS系統的元數據(命名空間、權限控制、文件/資料庫/副本之間的映射)。元數據存儲在主伺服器的內存中。對元數據進行修改前,要先寫操作日誌,只有在寫日誌成功後才能對元數據進行修改。操作日誌保存在磁碟上,且在多個機器上保存多份。為了避免操作日誌變得太大,每隔一段時間,GFS就創建一個檢查點(checkpoint)。檢查點被組織成一棵緊湊的、類似於B樹的形式存儲在磁碟上,便於快速加載到內容中使用。GFS系統重啟或恢復時,僅需要加載最新的檢查點,然後再重放操作日誌即可。
  • GFS影子主伺服器:影子主伺服器對外提供「只讀」服務,它上面保存的元數據不保證是最新的,與主伺服器上保存的元數據相比可能會有一點滯後(通常不到1秒)。因此,在主伺服器重啟期間,那些可以接收非最新數據的應用可以通過影子伺服器繼續使用GFS。
  • GFS數據塊伺服器:數據塊的實際存儲者,它和主伺服器有心跳聯繫,並告訴主伺服器它上面保存的文件塊信息,主伺服器據此維護其保存的元數據。

圖9-1 GFS架構

每個GFS文件都被分成固定大小的數據塊(64 MB)。數據塊以文件的形式被保存在運行著Linux的GFS數據塊伺服器上。為了增強可靠性,每個數據塊被保存在多個數據塊伺服器上(默認為3個)。

GFS主伺服器通過周期性的心跳機制監控數據塊伺服器的狀態。

4.讀操作的實現

讀操作的步驟如圖9-1所示,具體描述如下。

(1)編譯時,GFS客戶端連接GFS客戶端庫。讀文件內容的API(即open函數),帶至少兩個參數,其中一個是文件名稱,另一個是開始讀的文件偏移(offset)。因為數據塊的大小是固定的,所以可將文件偏移轉換成塊索引(chunk index)。然後,GFS客戶端庫發送一個請求給主伺服器,請求中包含欲讀的文件名和塊索引。

(2)收到來自客戶端庫的請求後,主伺服器查詢元數據,得到欲讀塊的各個副本的存儲位置。然後,返回給客戶端庫一個句柄(handle)及其各個副本的存儲位置。

(3)收到主伺服器返回的元數據後,GFS客戶端庫就挑選一個副本(通常是最近的一個副本),然後發送讀請求給存放這個副本的數據塊伺服器。

(4)該數據塊伺服器返回欲讀塊的內容給客戶端庫。

從上面的步驟可見,只有在查詢塊存放位置時,GFS客戶端才需要和主伺服器打交道。一旦獲知塊存放放置後,GFS客戶端就直接和數據塊伺服器聯繫,而不需要主伺服器了。

5.單數據塊寫操作的實現

因為一個數據塊被存儲多份(一般為3份),分布在多個數據塊伺服器上,所以對某個塊的寫操作必須修改其所有副本。為了減輕主伺服器對寫操作的管理負擔,GFS引入了租期機制(lease mechanism)。

假設每個塊被存儲3份,那麼主伺服器就從這3個副本中選出一個作為主副本(primary replica),在租約(一般為60秒)到期之前,這個主副本一直負責該數據塊的多個副本的一致性,並定義租約內對該數據塊所有寫操作的執行順序。其餘的兩個副本為從副本(secondary replica)。

圖9-2演示了GFS單數據塊寫操作的實現方式,單數據塊寫操作的步驟如下。

(1)同讀操作類似,GFS客戶端庫先將要寫的文件偏移轉換成塊索引,然後發送請求給主伺服器,查詢該數據塊的存儲位置。

(2)主伺服器查詢自己維護的元數據,將該數據塊的3個副本所在的數據塊伺服器以及當前的主副本是誰等信息返回給客戶端庫。

(3)為了提高寫操作的執行性能,客戶端庫先將欲寫入的數據塊內容推送到3個副本所在的數據塊伺服器上。收到後,數據塊伺服器將其緩存到本地的LRU(Least Recently Used)緩存中。

(4)當所有的副本都確認數據塊接收成功後,客戶端庫就發送寫請求給主副本。收到寫請求後,主副本就給該寫請求分配一個序列號。該序列號用於給當前租約期內的所有寫操作賦予一個確定的順序。然後,主副本按照序列號約定的順序,在本地執行該寫操作。

(5)當本地的寫入成功後,主副本將寫請求轉發給其餘兩個從副本所在的數據塊伺服器。每個從副本都按照序列號約定的順序執行該寫請求。

(6)從副本返回給主副本寫操作執行完成,無論是否寫入成功。

(7)主副本返回給客戶端庫寫操作執行完成。因為主副本一定寫成功了,否則主副本不會將寫請求轉發給從副本,所以最後的結果一定是「主副本成功 + 0個或2個從副本成功」。

(8)如果不是所有的副本都寫成功,客戶端庫就重複第3~7步數次,直到所有的副本都寫成功或者重試次數達到最大值為止。

(9)如果重試次數達到最大值後依然有某些從副本沒有寫成功,那麼主伺服器很快會發現該數據塊的副本數低於3,因此它就會選擇新的數據塊伺服器,並將數據塊複製到上面去,以滿足數據副本數不低於3的要求。

圖9-2 GFS寫操作的實現

6.多數據塊寫操作的實現

如果一次寫操作跨多個數據塊,那麼GFS將其拆分成多個單數據塊寫操作而分別獨立執行。

如果有兩個多數據塊寫操作A和B,假如A涉及兩個連續的數據塊A1和A2,B涉及兩個連續的數據塊A2和A3。注意,A和B都會修改數據塊A2。由於GFS並發地執行這4個單數據塊寫操作,因此最後A2的結果是不確定的,這取決於是A的A2先執行,還是B的A2先執行,但不論哪個先執行,由於單數據塊寫操作中的序列號機制,最後A2的內容要麼來自A,要麼來自B,而不會是二者的混合。

換句話說,對於單數據塊寫操作,GFS是串行執行的,因此能保證其原子性。而對於跨多個數據塊的寫操作,GFS不能保證多個單數據塊操作的串行性,因此也就不能保證其原子性。

7.追加操作的實現

GFS對追加操作有一個限制,就是追加的數據大小不能超過數據塊大小的1/4。

GFS的追加操作很多地方與單數據塊的寫操作類似,不同的地方如下。

(1)在單數據塊操作的第1步,客戶端庫向主伺服器查詢的是最後一個數據塊的存儲位置。

(2)在單數據塊操作的第4步,當多個副本(即最後一個數據塊的多個副本)所在的數據塊伺服器都收到欲追加的數據後,客戶端庫給主副本所在的數據塊伺服器發送追加請求。主副本所在的數據塊伺服器收到追加請求後,檢查其剩餘的空間是否能夠容納得下欲追加的數據。如果剩餘空間不夠,就將剩餘空間用某種方式填充滿[4],實際上就是將最後一塊的剩餘空間浪費了,然後通知各個從副本也做同樣的處理,然後通知客戶端庫在下一個數據塊上重試。如果剩餘空間足以容納欲追加的數據,則將其追加到最後一塊中,然後通知從副本也在同樣的偏移追加。

(3)在單數據塊操作的最後一步,如果不是全部副本都追加成功了,則客戶端庫一直重試,直到全部副本都追加成功為止。

上面的追加處理方式,對於追加的數據,在某些副本中有可能會被追加多次。例如,第一次嘗試時,副本1、2成功,但副本3失敗了。第二次嘗試時,3個副本都追加成功了。那麼,在副本1和2中,數據被追加了兩次,而副本3則只追加了一次。但無論如何,最後一次嘗試的所有副本都是寫成功的。另外,每次寫的位置(即文件的偏移值)都是一樣的。也就是說,GFS並不保證不同副本的每個字節都是相同的,但保證每份副本都包含所有的數據,雖然有些數據會被包含多次。

8.取快照操作的實現

熟悉Linux內核的讀者都知道,當fork()/clone()系統調用創建一個新的進程時,並不會複製當前進程的所有內存頁面到新建的進程中,而是採用了所謂的寫時複製(Copy On Write,COW)技術,即只是增加當前進程所有內存頁面的引用計數,只有當某個頁面的內容在以後的某個時刻被修改時,才會給新進程建立該頁面的獨立副本。這種做法的好處是顯而易見的,既節約了內存空間,也縮短了系統調用的執行時間。

GFS的取快照操作也採用了COW技術。具體地說就是,當對一個文件(或目錄)取快照時,並不會立刻對該文件(或目錄)的所有數據塊進行複製,而是僅增加其引用計數,只有當以後某個數據塊的內容被修改時,才為該數據塊創建新的獨立副本。

9.GFS的保證

因為GFS上的文件和目錄內容都存儲了多份,所以就存在數據一致性的問題。對此,GFS的實現有如下保證。

(1)對於文件(或目錄)命名空間的修改(如創建新文件),GFS通過加鎖保證其原子性。

(2)每次寫操作後,文件(或目錄)最後一個字節的偏移值在各個副本中都是一樣的。

(3)每次寫操作修改的文件(或目錄)位置(即偏移值)都是一樣的。

(4)單數據塊的寫操作保證其原子性,而且結果是確定的。

(5)一致性是指多個副本上都保存有同樣的數據,確定性是指無論幾個多數據塊寫操作如何執行,其結果都是唯一的。因此,對於多數據塊寫操作,GFS僅保證其多個副本上的數據是一致的,但不保證有確定的結果,因為多個單數據塊寫操作是獨立進行的。

(6)追加操作保證其原子性,而且結果是確定的。

本文截選自《分布式系統設計實踐》,李慶旭 著

  • 系統梳理現有的各種分布式技術
  • 具體闡述各種分布式產品的設計思想和架構,包含前端、資料庫、存儲技術等幾大重要內容
  • 架構師、工程師、項目管理人員參考書

本書將分布式系統中涉及的技術分為前端構造技術、分布式中間件技術和分布式存儲技術三大類,對每類技術都詳細介紹了其原理、設計思想和架構,以及相關應用場景。此外,本書還總結了分布式系統的構建思想,並分別對業界幾個非常成功的大型分布式系統(谷歌搜索系統、淘寶網電商平台、阿里雲公有雲平台、領英社交平台)進行了案例研究。

關鍵字: