GFS 閱讀筆記

這篇博客是我閱讀著名的 GFS 論文(The Google File System)所總結的筆記以及自己一些的思考。這篇論文是一篇非常經典的論文,尤其對於想要了解分佈式或者剛剛開始研究分佈式的人來說,是一篇非常好的讀物,它裏面提到了許多分佈式方向的基本問題,許多分佈式的研究都是圍繞這些基本問題的。

分佈式系統

在瞭解谷歌文件系統(Google File System)之前,我們必須要了解一下有關分佈式系統的一些概念。

Q1:一致性是什麼?

在分佈式文件系統中,很重要的一部分就是數據的複製(replica),爲了保證分佈式文件系統的高可用性,我們常常會把文件在不同的機器上存儲多份,一致性的要求就是保證這些不同機器上的複製品(replicas)能夠保持一致。

Q2:如果只有一個應用程序,它對文件系統進行了一次寫操作,這個應用程序在這次寫操作之後的讀操作會觀測到什麼呢?

它會正常觀測到它剛剛寫入的數據。

Q3:如果另外多個應用程序執行的讀操作呢,它們會觀測到什麼呢?

對於弱一致性的模型來說,這次讀操作有可能會讀取到已經過期的數據;

對於強一致性的模型來說,讀操作讀到的始終是上一次寫入操作進行完成之後的數據。

強一致性能保證寫入操作,但是它會影響性能(強一致性協議複雜)

Q4:理想化的一致性模型是怎樣的?

分佈式文件系統通過在多個機器上覆制文件來保證可用性,在理想化的一致性模型中,在文件系統中所進行的各種操作都要像是在一臺機器上進行的操作。實現理想化一致性模型的難點在於處理高併發問題、如何處理分佈式集羣中的機器崩潰以及達到網絡的高效利用,理想化的一致性模型還會出現裂腦問題(split-brain,如果兩個存儲着相同文件的機器 A,B同時崩潰,其他的機器並不知道是哪一個先崩潰的,所以就不知道該用 A 恢復 B還是用 B 恢復 A)。總之,使用理想化一致性算法會影響性能,並且它的實現非常複雜(例如:Paxos)

GFS 不是採用的理想化一致性模型,但是它解決了機器崩潰恢復的問題以及能夠應對高併發操作同時又能相對高效地利用網絡。

GFS 是什麼?

GFS(Google File System )是一個大規模分佈式文件系統,具有容錯的特性(機器崩潰後的處理),並且具有較高性能,能夠響應衆多的客戶端。

GFS 設計背景

  • 經常會有機器崩潰(因爲機器衆多,難免會有機器崩潰)
  • 有些存儲的文件比較大
  • append 操作更常見(在文件後追加,而不是 overwrite 覆蓋)
  • 主要包括兩種讀取 (read)操作:一種是大的順序讀取(單個文件讀取幾百 KB 甚至是幾 MB);另一種是小的隨機讀取(在隨機位置讀取幾 KB)
  • 需要支持併發(例如,多個客戶端同時進行 append 操作)

GFS 所需提供操作

create(文件創建)、delete(文件刪除)、open(打開文件)、close(關閉文件)、read(讀取文件)、write(寫入文件)、record append(追加文件)、snapshot(快照)。

GFS 架構

GFS 的架構由一臺 master 服務器和許多臺文件服務器(chunkserver)構成,並且有若干客戶端(client)與之交互。

GFS 特點概述

  • 文件分塊(chunks),每塊有一個64位標識符(chunk handle),它是在 chunk 被創建時由 master 分配的,每一個 chunk 會有3個備份,分別在不同的機器上。
  • Master 存儲所有的 metadata,包括命名空間(namespace)、訪問控制信息(access control)、文件與 chunk 的映射關係(mapping)以及 chunk 的存儲位置
  • Master 管理 chunk 租約(lease)、chunk 遷移(如果 chunkserver 掛掉)、chunkserver 之間的通信(heartbeat,它也會向 chunkserver傳達master 的命令,chunkserver 通過 heartbeat 向 master 報告自己的狀態)
  • Client 會和 master 以及 chunkserver 進行交互,client向 master 請求 metadata,然後向 chunkserver 進行讀寫操作
  • client 與 chunkserver 都不會緩存文件數據,爲的是防止數據出現不一致的狀況。但是 client 會緩存 metadata 的信息(但是會出現一個問題,如果 metadata 過期怎麼辦呢?GFS 給出了自己的解決方案,也就是租約 lease)

單一 Master 架構

GFS 爲了簡化設計,在整個系統中只有一個 master 進行管理。Master 不提供讀寫操作,它只會告訴 client,它所請求操作的文件在哪個 chunkserver 上,然後 client 會根據 master 提供的信息,與對應的 chunkserver 進行通信。

例如:以 client 要進行讀取操作爲例

  1. client 將應用程序請求的文件名、大小轉化爲 chunk index,然後將文件名和 index 發送給 master
  2. master 返回文件的 chunk handle 和所有該文件備份的位置
  3. client 將這兩個 master 發送給它的信息緩存起來作爲 value,文件名和 chunk index 作爲 key
  4. client 向三個備份之一的 chunkserver 發送讀請求(選擇最近的機器),請求中包含 chunk index 和它要讀取的文件的 Byte 範圍
  5. 如果 client 緩存的信息沒有過期(如何知道是否過期會在後面的文章進行介紹),client 就不用在與 master 進行通信了,以後可以直接與 chunkserver 進行通信

chunk 大小

GFS 中將 chunk 的大小定爲 64MB,它比一般的文件系統的塊大小要大。

這樣做的優點有:

  • 減少 client 與 master 的交互
  • client 可以在一個塊上執行更多的操作,通過 TCP 長連接減少網絡壓力
  • 減小 metadata 的大小

但是這樣做也存在缺點:

  • 一個 chunk 可以存更多的小文件了,這樣的話如果有一個塊存儲了許多小文件,client 和它進行操作的機率大大提高,這個 chunk 的壓力會很大(然而在實際中,這個問題影響並不大)
  • 在批處理系統中存在很大問題(如果在一個 chunk 上有一個可執行文件,同時有許多 client 都要請求執行這個文件,它的壓力會很大。解決方案是把該文件在不同的 chunkserver 上多添加幾個備份,更長久的方案是應該允許 client 去讀取其他 client 的文件)

metadata

GFS 的 metadata 存儲着 3 種類型的信息:

  • 文件名以及 chunk 的名稱
  • 文件與 chunk 的映射關係
  • 各個備份(replicas)的位置

Metadata 通常存儲於內存中,前兩種信息有時會存於磁盤中,它們有時會作爲操作記錄(operation log)備份的一部分存儲於磁盤,備份於遠程機器。

把 metadata 存儲於內存有許多優點,查看 metadata 信息時很方便,速度快,有利於 chunk 的垃圾回收(garbage collection)、再備份(re-replication)以及 chunk 遷移(爲的是負載均衡)。

但是如果如果Metadata都存放於內存的話會不會受限於內存的大小呢?

實際上不會的,因爲每一條 metadata 的大小非常小,namespace 信息也很小,並且使用了前綴壓縮(prefix compression)進行存儲。並且升級內存的花費實際上也很小。

chunk 位置

chunk 的位置信息在 master 中不是一成不變的,master 會通過定期的 heartbeat 進行更新,這樣做能夠減小開銷,這樣做就不用 master 與 chunkserver 時刻保持同步通信(包括 chunkserver 的加入、退出、改名、宕機、重啓等)。chunkserver 上有一個 final word,它表示了哪個 chunk 在它的磁盤上,哪個 chunk 不在。

操作記錄(operation log)

operation log 中包括了 metadata 變更的歷史記錄

  • 它是 metadata 的持久化記錄,備份於磁盤上
  • 它表示了併發操作的時間線
  • 用於 Master 恢復

一致性模型

GFS 採用的一致性模型並不是強一致性模型,這是在考慮了各種問題後權衡的結果。

GFS 是如何保證一致性的?

有關文件命名空間的操作都是原子的(由 namespace lock 保證)

我們先來介紹一下 GFS 保證一致性的前提和一些概念:

  • 如果所有客戶端不論從哪一個備份中讀取同一個文件,得到的結果都是相同的,那麼我們就說這個文件空間是一致的(consistent)
  • defined:如果一個文件區域在經過一系列操作之後依舊是一致的,並且客戶端完全知曉對它所做的所有操作,我們就稱它爲『defined』
  • 一個操作如果沒有被其他併發的寫操作影響,那麼這個被操作的文件區域是 defined 的
  • 成功的併發操作也會導致文件區域 undefined,但是一定是一致的(consistent)(客戶端有可能只看到了最終一致的結果,但是它並不知道過程)
  • 失敗的併發操作會導致文件區域 undefined,所以一定也是不一致的(inconsistent)
  • GFS 並不需要是因爲什麼導致的 undefined(不區分是哪種 undefined),它只需要知道這個區域是 undefined 還是 defined 就可以

造成數據改變的操作可能是寫入(write)或者追加(record append):

  • write:往應用程序指定的 offset 進行寫入
  • record append:往併發操作進行過的 offset 處進行寫入,這個 offset 是由 GFS 決定的(至於如何決定的後面會有介紹),這個 offset 會作爲 defined 區域的起始位置發送給 client。
  • “regular” append:對應於 record append 的一個概念,普通的 append 操作通常 offset 指的是文件的末尾,但是在分佈式的環境中,offset 就沒有這麼簡單了

重要問題

  1. GFS 通過在所有的備份(replicas)上應用順序相同的操作來保證一個文件區域的 defined(具體細節後面會討論)
  2. GFS 會使用 chunk version(版本號)來檢測 replicas 是否過期,過期的 replicas 既不會被讀取也不會被寫入
  3. GFS 通過握手(handshakes)來檢測已經宕機的 chunkserver
  4. GFS 會通過校驗和(checksuming)來檢測文件的完整性

系統間的交互

這一部分我們來談談系統中各個部分之間的交互(master 和 chunkserver、client 和 master、chunkserver 等),GFS 設計的目標是儘可能地讓 master 更少的涉及到各種操作中。

租約(lease)和修改的順序(mutation order)

Mutation(修改):mutation 指的是改變了 chunk 的內容或者 metadata,每一次 mutation 都應該作用於所有的備份

GFS 使用租約機制(lease)來保障 mutation 的一致性:多個備份中的一個持有 lease,這個備份被稱爲 primary replica(其餘的備份爲 secondary replicas),GFS 會把所有的 mutation 都序列化(串行化),讓 primary 直行,secondary 也按相同順序執行,primary 是由 master 選出來的。一個 lease 通常60秒會超時。

現在我們以寫操作的數據流程來說明租約機制是如何進行的:

  1. client 向 master 請求持有 lease 的 chunk(primary replica)位置和其他 replicas 的位置(如果沒有 chunk 持有 lease,那麼 master 會授予其中一個 replica 一個 lease)
  2. master 返回 primary 的信息和其他 replicas 的位置,然後 client 將這些信息緩存起來(只有當 primary 無法通信或者該 primary replica 沒有 lease 了,client 纔會向 master 再次請求)
  3. client 會將數據發送到所有的 replicas,每個 chunkserver 會把數據存在 LRU 緩存中
  4. 在所有的 replicas 都收到了數據之後,client 會向 primary 發送寫請求。primary 會給它所收到的所有 mutation 分配序列號(這些 mutation 有可能不是來自於同一個 client),它會在自己的機器上按序列號進行操作
  5. primary 給 secondaries 發送寫請求,secondaries 會按相同的序列執行操作
  6. secondaries 告知 primary 操作執行完畢
  7. primary 向 client 應答,期間的錯誤也會發送給 client,client 錯誤處理程序(error handler)會重試失敗的 mutation

其他問題:

  • 如果一次寫操作要寫的數據比較大,可能會跨越多個 chunk,GFS client 會把它分爲幾次小的操作,GFS 支持的最大的操作大小是 chunk 的1/4的大小
  • **但是如果像上述這麼做會出現 undefined 但是 consistent 的區域,這是爲什麼呢?**GFS 的 record append 操作僅能保證數據在一個原子單位中被寫了一次,並不能保證對所有的 replicas 操作的位置都是相同的,比如每次寫入的 offset 相同,但是 chunk 有可能不一樣

數據流

GFS 對其數據流的設計目標如下:

  • 要充分利用網絡帶寬
  • 避免網絡瓶頸和高延遲
  • 減少數據流動延遲

設計方案如下:

  • 數據以鏈(chain)的形式 在 chunkserver 之間線性流動(每個機器都在用自己的全部帶寬與另外一個機器通信,而不是同時讓多個機器分享帶寬)
  • 每個機器會把數據發送到離自己最近的還沒有收到數據的機器(GFS 中可以通過機器 IP 地址進行計算)
  • 通過 TCP 連接將數據傳輸流水線化(pipelining),pipelining 之所以能夠有效果是因爲 GFS 的網絡是全雙工的交換網絡

Snapshot 快照

GFS 通過 snapshot 來立即創建一個文件或者目錄樹的備份,它可以用於備份文件或者創建 checkpoint(用於恢復),同時 GFS 把寫時複製技術(copy-on-write)引入到了快照操作中,原理與 Linux 進程中的寫時複製基本相同。

當 master 收到 snapshot 操作請求後:

  1. 廢除所有的 lease,準備 snapshot(相當於暫停了所有寫操作)
  2. master 記錄所有操作,並且將記錄寫入磁盤
  3. master 將源文件和目錄樹的 metadata 進行復制,這樣之前的記錄就和當前的內存中所保存的狀態對應起來了,新建的 snapshot 和源文件指向的會是同一個 chunk

Master 職責

  • 執行所有有關於 namespace 的操作
  • 管理整個系統的 chunk replicas:
    • 做出 chunk replicas 的放置決定
    • 創建 chunk/replicas
    • 協調各種操作,保證 chunk 被完全複製
    • 負載均衡
    • 回收閒置空間

管理 namespace

在進行快照操作時,lease 會被廢除,無法進行寫操作,但是 GFS 希望其他 Master 操作不受影響,GFS 採取的方法是使用namespace 鎖

GFS 的namespace 是一個查找表(lookup table),並且採用了前綴壓縮的方式存儲在內存中,它是一個樹結構,namespace 樹中的每一個節點(文件名或者目錄名)都有一個讀/寫鎖。

在 Master 對文件或者目錄進行操作之前它首先需要獲取一個鎖,比如要對 /d1/d2/…/dn/leaf 進行操作,需要獲得 /d1, /d1/d2, /d1/d2/…/dn的讀鎖,需要 /d1/d2/…/dn/leaf 的讀鎖或者寫鎖(根據不同的操作,鎖也不同)

例如,當/home/user 被快照備份至/save/user 時,如果此時要創建/home/user/foo 會發生什麼呢?

快照操作獲得了/home, /save 的讀鎖和/home/user, /save/user 的寫鎖。創建/home/user/foo需要/home, /home/user的讀鎖和/home/user/foo 的寫鎖。因爲兩個操作在 /home/user的鎖上產生了衝突,所以操作會依次執行,在完成 snapshot 操作之後,釋放了/home/user 的寫鎖, /home/user/foo纔會被創建。

放置 replicas

如何安置replicas 的目標是:

  • 最大化數據可靠性和可用性
  • 最大化網絡帶寬的利用

這裏的最大化不僅僅是機器間的問題,還要考慮機架間的問題

在以下3種情況下,Master 會進行創建 replicas 的操作:

  • 創建了新的 chunk
  • 需要重新備份
  • 負載均衡

如何選擇將 replicas放置到哪臺機器上呢?

  1. 優先選擇磁盤利用率低的 chunkserver
  2. GFS 會限制每個 chunkserver『最近』創建的次數。換句話說,如果一個 chunkserver 近期創建 replicas 的操作比較頻繁,就不會優先選擇它(因爲創建就意味着以後會進行讀取,爲了防止突然間大量的讀取出現在同一臺機器上)
  3. 保證可用性,儘可能跨機架進行創建操作

當可用的備份低於要求時(GFS 要求爲3份),master 會對 chunk 進行重新備份,在以下情況有可能需要重新備份:

  • chunkserver 不可用了
  • 備份損壞了
  • 硬盤掛掉了
  • 所要求的最低備份數量提高了

當有多個 chunk 需要備份時,GFS 如何決定先備份哪個呢?策略如下:

  • 優先選擇可用備份少的
  • 優先備份最近沒有 delete 文件的
  • 優先備份阻塞了 client 操作的

當 master 決定了備份哪個之後,會把當前可用的 chunk 直接克隆到目標位置(遵循replicas 放置規則)

垃圾回收

文件 delete 之後,GFS 並不會立即對空間進行回收,而是等待垃圾回收機制會空間進行釋放。

當文件被刪除之後,Master 會想其他操作一樣,把刪除操作記錄下來,但是不進行空間的回收,而是將這塊空間命名爲 hidden(並且包含被刪除時的時間戳),Master 會定期進行掃描,把隱藏了一定時間的文件空間進行回收(這個時間是可以進行配置的),在此期間可以對這塊空間的文件進行恢復(直接通過重命名回原來的名稱就可以)。

除此之外,垃圾回收機制還會掃描孤兒 chunk(所有的文件都沒有用到的非空 chunk),然後對這塊 chunk 的 metadata 進行清除。具體的做法是,在 master 於 chunkserver 的 heartbeat 信息中會攜帶關於 chunk 的信息,master 會把 metadata 中不存在的 chunk 發送給 chunkserver,chunkserver 會把它擁有的 chunk 發送給 master。

過期 replica 檢測

chunkserver 宕機或者是 mutation 的丟失會導致 replica 的過期,GFS 是如何對 replicas 進行檢測,判斷它們是否是最新的呢?

GFS 對於每一個 chunk 都會有一個版本號,這個版本號由 master 進行管理,通過版本號可以對過期的 replica 進行甄別。當 master 授予 lease 的時候,會增加版本號並且通知所有未過期的 replicas,master 和 replicas 都會記錄下最新的版本號(這些操作需要在客戶端進行寫入操作之前完成)。如果這時,有一個 replica 不可用了,它的版本號就不會再增加了,在 chunkserver 重啓或者重新向 master報告它的版本號時,master 就會知道這個 replica 已經過期了,並且會在垃圾回收時將它進行回收。如果 master 的版本號落後了呢,它會更新自己的版本號。


本文的版權歸作者 羅遠航 所有,採用 Attribution-NonCommercial 3.0 License。任何人可以進行轉載、分享,但不可在未經允許的情況下用於商業用途;轉載請註明出處。感謝配合!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章