《The Google File System》論文研讀
阿新 • • 發佈:2020-07-04
# GFS 論文總結
**說明**:本文為論文 **《The Google File System》** 的個人總結,難免有理解不到位之處,歡迎交流與指正 。
**論文地址**:[GFS Paper](https://github.com/XutongLi/Learning-Notes/blob/master/Distributed_System/Paper_Reading/GFS/The%20Google%20File%20System.pdf)
> 閱讀此論文的過程中,感覺內容繁多且分散,一個概念的相關內容在不同部分相交地出現 。所以本文儘量將同一概念的相關內容串聯並總結在一起 。
>
> 本文以批註的形式添加個人理解 。
***
## 1. 前言
**Google File System (GFS)** 是由 **Google** 設計並實現的、一個面向大規模資料密集型應用的分散式檔案系統,它不僅滿足所有分散式檔案系統共有的 `高效能` 、`伸縮性` 、`可靠性` 、`可用性` ,還以 **Google** 自身 `應用程式` 和 `技術環境` 為基礎進行了特有的設計 ,主要包括:
- 因為 **GFS** 使用裝置數多,將元件失效視為常態事件 。因此 `持續監控` 、`錯誤檢測` 、`災難冗餘` 和 `自動恢復` 的機制必須包括在 **GFS** 中 。
- 主要對大檔案的管理進行了優化 。
- 主要應用於 `對檔案尾部追加資料` 的修改,而非覆蓋原有資料的方式,如 “生產者-消費者” 佇列,或者其他多路檔案合併操作 。一旦寫完後,對檔案的操作通常是順序讀 。
- 採用了較弱的 `一致性` 要求,引用 `原子性的記錄追加` 操作,保證多個客戶端能夠同時進行追加操作,不需要額外的同步操作來保證資料的一致性 。
- 目標程式絕大多數要求高速率、大批量地處理資料,極少要求對單一讀寫操作有嚴格的響應時間要求 。即 `高效能的穩定網路頻寬` 比 `低延遲` 更重要 。
***
## 2. 架構
### 2.1 介面
**GFS** 提供了一套類似傳統檔案系統的 `API介面函式` ,檔案以 `分層目錄` 的形式組織,用 `路徑名` 來標識 。支援:建立新檔案、刪除檔案、開啟檔案、關閉檔案、讀和寫檔案,以及快照和記錄追加操作 。
### 2.2 架構設計
![](https://img2020.cnblogs.com/blog/2035097/202007/2035097-20200704130750998-2076211442.png)
一個 **GFS** 叢集包含:一個 `master` 、多臺 `chunkserver` 、同時被多個 `client` 訪問 ,這些都是普通的 **Linux** 機器 。
每個檔案被分為多個 `chunk` ,每個 **chunk** 大小為 **64MB** 。
**chunk** 儲存在 **chunkserver** 上,每個 **chunk** 含有 **3** 個 `副本` ,分別存於 **3** 個不同機架上的不同 **chunkserver** 。
檔案只以本地檔案形式儲存在 **chunkserver** ,不在 **client** 或 **chunksever** 進行快取 。
***
## 3. Chunk及其副本
### 3.1 chunk 大小與數量
**chunk** 大小為 **64MB** ,每個 **chunk** 副本都以普通 **Linux** 檔案的形式儲存在 **chunkserver** ,使用 **惰性分配** 策略避免了因內部碎片造成的空間浪費 。
> 惰性分配:指直到使用一個資源的時候再去給它分配空間 。
**chunkserver** 把 **chunk** 以 **Linux** 檔案形式儲存在本地硬碟,並根據 **chunk handle** 和位元組範圍來讀寫 **chunk** 。
出於可靠性的考慮,每個 **chunk** 都會複製到多個 **chunksever** 上,預設副本數為 **3** 。
**選擇較大 chunk 尺寸的優點**:
- 減少了客戶端和 **master** 節點通訊的需求
- 用較大的 **chunk** 尺寸,客戶端能夠對一個塊進行多次操作,這樣就可以與 **chunkserver** 保持較長時間的 *TCP* 連線而減少網路負載
- 減少了 **master** 節點需要的元資料的數量
### 3.2 副本的位置
**GFS** 叢集是高度分佈的 `多層佈局結構` 。一般將一個 **chunk** 的多個副本本別儲存在多個 `機架` 上,可以:
- 最大化資料可靠性和可用性:不僅可以預防硬碟損壞、伺服器宕機引發的問題,還可以預防網路、電源故障失效,比僅部署在不同伺服器上擁有更高的可靠性
- 最大化網路頻寬利用率:針對 **chunk** 的讀操作,能夠利用多個機架的整合頻寬
### 3.3 chunk 建立
**master** 建立一個 **chunk** 時,會選擇在哪裡放置初始的空的副本,主要考慮幾個因素:
- 希望在低於平均硬碟使用率的 **chunkserver** 上儲存新的副本
- 希望限制在每個 **chunkserver** 上 `最近` 的 **chunk** 建立操作的次數
- 希望把 **chunk** 分佈在多個機架上
### 3.4 chunk 重新複製
當 **chunk** 的有效副本數量少於使用者指定的複製因素的時候( 預設為 **3** ),**master** 會重新複製它 。
可能的原因有:**chunkserver** 不可用了、**chunkserver** 上副本損壞、**chunkserver** 磁碟不可用或 **chunk** 副本的複製因素被提高了 。
優先順序因素:優先複製 `副本數量和複製因素相差多的` 、`優先複製活躍的檔案而非剛被刪除的檔案` 、`優先複製會阻塞 client 程式的chunk` 。
**master** 選擇優先順序最高的 **chunk** ,然後命令 **chunkserver** 直接從可用的副本克隆一個副本出來,選擇新副本位置的策略和建立時的策略相同 。
### 3.5 chunk 重新負載均衡
**master** 伺服器週期性地對副本進行 `重新負載均衡` :它檢查當前的副本分佈情況,然後移動副本以便更好地利用硬碟空間、更有效地進行負載均衡 。
若加入了新的 **chunkserver** ,**master** 會通過重新負載均衡的方式逐漸填滿這個新的 **chunkserver** ,而不是短時間內填滿它(可能導致過載)。
### 3.6 租約 (lease)
**master** 為 **chunk** 的一個副本建立一個 **lease** ,將這個副本稱為 `primary` ( 主 **chunk** ),剩餘副本為 **secondary** 。
**primary** 對 **chunk** 的所有更改操作進行序列化,所有的副本都遵從這個序列進行修改操作 。因此,修改操作全域性的順序首先由 **master** 節點選擇 **lease** 的順序決定,然後由 **primary** 的分配的序列號決定 。
設定 **primary** 的 `目的` :為了最小化 **master** 的管理負擔 。
> **split-brain** 問題:
>
> 假設 **S1** 是一個 **chunk** 的 **primary**,並且 **master** 和 **S1** 之間的網路連線已斷開,**master** 發現此 **S1** 無響應後將確立 **S2** 為 **primary** 。此時 **client** 可以和 **S1**、**S1** 兩個 **primary** 連線 ,這就是 **split-brain** 問題 。
為解決 **split-brain** 問題,將 **lease** 的超時設定為 **60s** 。**master** 發現 **S1** 沒有響應,則會等 **lease** 過期後,分配新的 **lease** ,即 **S2** 只有在 **S1** 到期後才有可能被設為 **primary** 。
只要 **chunk** 被修改了,**primary** 就可以申請更長的租期,得到 **master** 的確認並收到租約延長的時間 。這些租約延長請求和批准的資訊通常附加在 **master** 和 **chunkserver** 之間的 *HeatBeat* 來傳遞 。
***
## 4. master
### 4.1 元資料
**master** 主要存放 **3** 型別的 `元資料` :檔案和 **chunk** 的名稱空間、檔案和 **chunk** 的對應關係、每個 **chunk** 副本的存放地址 。
元資料細節:
```
namespace (a lookup table mapping full pathnames to metadata) (nv)
filename -> array of chunk handles (nv)
chunk handle -> version num (nv)
list of chunkservers (v)
primary (v)
lease time (v)
// nv 表示非易失的,即儲存於記憶體與磁碟;v 表示存於記憶體
```
**master** 伺服器可以在後臺週期性掃描自己儲存的全部狀態資訊 。
### 4.2 chunk 管理
`chunk` 建立的時候,**master** 會給每個 **chunk** 分配一個唯一的識別符號 `chunk handler` 。
**master** 伺服器在啟動時,或者有新的 **chunkserver** 加入時,向各個 **chunkserver** 輪詢它們所儲存的 **chunk** 資訊 ( **chunk** 位置等 )。
**master** 會為 **chunk** 的一個副本建立 **lease** 。
**master** 使用 `HeatBeat` 資訊週期性地和每個 **chunkserver** 通訊,傳送指令到各個 **chunkserver** 並接收 **chunkserver** 的狀態資訊 。
**master** 會對自己儲存的元資料進行週期性掃描,這種週期性的狀態掃描也用於實現 **chunk** 垃圾收集、在 **chunkserver** 失效的時重新複製資料、通過 **chunk** 的遷移實現跨 **chunkserver** 的負載均衡以及磁碟使用狀況統計等功能 。
### 4.3 操作日誌
操作日誌包含了關鍵的 `元資料變更歷史記錄` ,操作日誌不僅是元資料唯一的持久化儲存記錄,也作為判斷同步操作順序的邏輯時間基線 。
會把日誌複製到多臺遠端機器,並且只有把相應的日誌記錄寫入到本地以及遠端機器的硬碟之後,才響應客戶端的操作請求 。
### 4.4 快照
快照操作幾乎可以瞬間完成對一個 `檔案或目錄樹 (源)` 做一個拷貝,而且幾乎不會對正在進行的其他操作造成任何干擾 。
使用 `copy-on-write` ( 寫時複製 )技術實現快照 :
- 當 **master** 接收到一個快照請求,首先取消需要快照的檔案的所有 **chunk lease**
- **lease** 取消或過期後,**master** 將此操作記錄到日誌,並通過複製源的元資料將此日誌記錄反映到記憶體中( 此時該 **chunk** 的引用計數加一,但並不真實地複製 **chunk** )
- 直到 **client** 要寫入資料到此 **chunk** 時,發請求到 **master** 詢問 **primary**
- **master** 注意到該 **chunk** 引用計數大於一,就要求所有擁有該 **chunk** 副本的 **chunkserver** 建立拷貝,對這三個新拷貝中的一個設定 **lease** ,返回給 **client**
- **client** 得到回覆後就可以正常寫這個 **chunk** ,而源 **chunk** 此時就儲存成為快照
> 之所以要先取消需要快照的檔案的 **chunk** ,是因為當 **client** 與 **chunkserver** 通訊,找不到 **primary** 時,必須去詢問 **master** 誰是 **primary**,這就相當於給了 **master** 一個觸發條件,讓它去發現建立快照的需求( 即 **chunk** 的引用計數大於一 ),並建立 **chunk** 新拷貝 。
> 使用寫時複製的原因:這樣可以減少不必要的複製,因為建立快照時,並不是所有的 **chunk** 都被修改過( 相較於上一次建立快照 ),所以直到一個 **chunk** 被修改時才真正複製它 。
### 4.5 名稱空間鎖
通過使用 `名稱空間鎖` 來保證 **master** 併發操作的正確順序 。
每個 **master** 的操作在開始之前都要獲得一系列的鎖。通常情況下,如果一個操作涉及 `/d1/d2/…/dn/leaf` ,那麼操作首先要獲得目錄 `/d1`, `/d1/d2` ,…, `/d1/d2/…/dn` 的讀鎖,以及 `/d1/d2/…/dn/leaf` 的讀寫鎖 。根據操作的不同, `leaf` 可以是一個檔案,也可以是一個目錄。
***
## 5. Client
**GFS** **client** 程式碼以庫的形式被連結在客戶程式裡,**client** 程式碼實現了 **GFS** 檔案系統的 *API* 介面函式、應用程式與 **master** 和 **chunkserver** 通訊、以及對資料進行讀寫操作。
**client** 和 **master** 節點的通訊只獲取元資料,它向 **master** 詢問應該聯絡的 **chunkserver** ,客戶端將這些元資料資訊快取一段時間,後續的操作將直接和 **chunkserver** 進行資料讀寫操作 。
***
## 6. 系統互動
### 6.1 讀
藉助系統架構圖描述 `讀` 的過程:
![](https://img2020.cnblogs.com/blog/2035097/202007/2035097-20200704130807589-561634329.png)
- **client** 傳送 **filename** 和根據位元組偏移量計算得出的 **chunk index** (當前位元組偏移量/64MB ) 給 **master**
- **master** 返回 **chunk handle** 和 **chunk** 的副本位置(僅包含最近 **version** 的)資訊給 **client**
- **client** 以 **filename** 和 **chunk index** 為 *key* 快取這些資料
- **client** 傳送請求到 **chunkserver** (一般選擇最近的),請求包括 **chunk handle** 和位元組範圍
- **chunkserver** 讀取檔案,返回資料
- **client** 接收到資料,通過 **checksum** 去除填充資料以及通過 **unique id** 去重(如果有需要的話)
> 對於填充資料和重複資料的問題,有的任務並不介意這些,比如搜尋引擎返回了兩個一樣的連結並無大礙 。對於介意這兩個問題的任務,則使用 **checksum** 去除填充資料,使用 **unique id** 去重 。
> **unique id** 去重:**client** 上應用程式檢查資料的 **id** ,若此資料 **id** 與之前收到的資料的 **id** 一樣,則丟棄它 。
> **GFS** 提供了處理填充資料和重複資料的庫 。
**client** 快取資訊到期或檔案被重新開啟前,**client** 不必再與 **master** 通訊 。
**client** 通常會在一次請求中查詢多個 **chunk** 資訊 。
> 上述流程主要用於大規模的流式讀取,如果是小規模的隨機讀取,通常做法是把小規模的隨機讀取合併並排序,之後按順序批量讀取 。
### 6.2 寫
![](https://img2020.cnblogs.com/blog/2035097/202007/2035097-20200704130816480-960095778.png)
1. **client** 向 **master** 詢問 **chunk** 的哪個副本是 **primary** ,以及該 **chunk** 的位置 。若沒有 **primary** :
- 若所有的 **chunkserver** 都沒有最近的版本號,返回錯誤
- **master** 在擁有最近版本的副本中選擇 **primary**
- 版本號遞增,並寫入硬碟中的 **log**
- 告訴 **primary** 和 **secondary** 它們的身份和新版本號,並將新版本號寫入 **chunkserver** 的硬碟
2. **master** 將 **primary** 的識別符號和 **secondary** 的位置返回給 **cient** ,**client** 快取這些資料後續使用 。只有 **primary** 不可用或 **lease** 已到期,**client** 才會再跟 **master** 進行聯絡 。
3. **client** 將資料推送到所有的副本上( 資料以管道的方式,順序沿著一個 **chunkserver** 鏈進行傳送,優先選擇最近的 **chunkserver** ),**chunkserver** 接收到資料並儲存在 *LRU* 快取中。
4. 所有副本確認接收到資料後,**client** 傳送寫請求到 **primary** 。這個請求標識了早前推送到所有副本的資料,**primary** 為接收到的所有操作分配連續的序列號( 這些操作可能來自不同的 **client** )。**primary** 以序列號的順序將操作在本地執行 。
5. **primary** 將寫請求傳遞到所有的 **secondary** ,**secondary** 依照相同的序列號執行操作 。
6. 所有 **secondary** 回覆 **primary** 已完成操作 。
7. **primary** 回覆 **client** 。若返回錯誤,**client** 重複發起操作請求 。
> 若應用程式一次寫入的資料量很大,或資料跨越了多個 **chunk** ,**client** 會將它們拆分為多個寫操作 。
> 通過將 **資料流和控制流分開** 的方式,充分利用每臺機器的頻寬,避免網路瓶頸和高延時的連線,最小化推送所有資料的延時 。
### 6.3 追加
**GFS** 提供了原子性的 `記錄追加` ,使用記錄追加,**client** 只需要指定要寫入的資料,**GFS** 保證有至少一次原子的 ( **atomically at least once** ) 寫入操作成功執行( 即寫入一個順序的 *byte* 流 ),寫入的資料追加到 **GFS** 指定的偏移位置上,之後 **GFS** 返回這個偏移量給**client** 。
> 傳統方式的寫入操作需要 **client** 指定資料寫入的偏移量,對一個 *region* 並行寫入時,*region* 尾部可能包含不同 **client** 寫入的資料片段 。
追加操作流程與寫流程( 本文 **6.3** )基本一致,區別在於:
- 步驟 **4** 、**5** 、**6**
- **client** 推送資料到檔案最後一個 **chunk** 所有副本之後,傳送請求給 **primary** 。**primary** 檢查追加資料是否超過 **64MB** ,若超過,**primary** 將當前 **chunk** 填充到最大尺寸,通知 **secondary** 執行相同操作,最後回覆 **client** 讓其對下一個 **chunk** 重新進行追加請求 。
- 步驟 **7**
- 若追加操作在任何一個副本上失敗了,**client** 重新請求追加,此時已經追加成功的副本還要多進行一次追加,就產生了記錄的重複 ( **GFS** 不保證 **chunk** 的所有副本在位元組級別是完全一致的,它只保證資料 `原子性地至少一次 (atomically at least once)` 地追加 )。
***
## 7. 一致性模型
**GFS** 支援一個寬鬆的一致性模型 。
> **GFS** 的弱一致性:
>
> 系統和一致性之間存在 *tradeoff* ,更好的一致性往往意味著更復雜的系統設計以及更多的機器間通訊。**GFS** 採用的較弱的一致性來降低系統複雜度,提高效能 。所以,它適用於對與不一致讀的問題不太敏感的任務,例如使用搜索引擎搜尋某個關鍵詞,即使顯示的幾萬條結果裡有少數幾條缺失、或順序不對,我們並不會意識到這些問題,說明 **GFS** 服務於搜尋引擎這種任務是可行的;而像銀行資料儲存這種對一致性和準確性有較高要求的任務,則不適合使用 **GFS** 。
檔案名稱空間的修改( 如:檔案建立 )是原子性的,它僅由 **master** 控制:名稱空間鎖提供了原子性和正確性、**master** 操作日誌定義了這些操作在全域性的順序 。
### 7.1 讀、寫、追加的一致性
對於資料修改後的檔案 `region` ,首先有兩個定義:
- **一致的 (consistent)**:對於一個 **chunk** ,所有 **client** 看到的所有副本內容都是一樣的
- **定義的 (defined)**:資料修改後是一致的,且 **client** 可以看到寫入操作的全部內容( 換句話說,可以看到每步操作修改後的內容 )
對於不同型別修改的 **region** 狀態如下圖所示:
![](https://img2020.cnblogs.com/blog/2035097/202007/2035097-20200704130825780-1065388484.png)
當一個數據寫操作成功執行,且沒有併發寫入,那麼影響的 **region** 就是 **defined** :所有 **client** 都能看到寫入的內容。( 隱含了 **consistent** )
當並行修改寫完成之後,**region** 處於 **consistent but undefined** 狀態:所有 **client** 看到同樣的資料,但是無法讀到任何一次寫入操作寫入的資料 ( 因為可能有並行寫操作覆蓋了同一區域 )。
失敗的寫操作導致 **region** 處於 **inconsistent** 狀態( 同時也是 **undifined** 的 ):不同 **client** 在不同時間會看到不同的資料 。
當對檔案進行追加操作,若追加操作成功,那麼 **region** 處於 **defined and consistent** 狀態;若某次追加操作失敗,由本文 **6.3** 可知,**client** 重新請求後會導致資料填充和重複資料的情況,此時 **region** 處於 **defined but inconsistent** 狀態 。
> 某次追加失敗過程:
>
> **C1** 向副本 **S1** 、**S2** 中追加 *a* ,若向 **S2** 中追加 *a* 時失敗,修改後結果為:
>
> **S1** - | a |
> **S2** - | |
>
> 此時 **C2** 併發地向 **S1** 、**S2** 中追加 *b* ,在兩副本相同偏移位置追加,執行成功,修改後結果為:
>
> **S1** - | a | b |
> **S2** - | | b |
>
> 之後 **C1** 由於有副本追加失敗,重新發起追加 *a* 的請求,此次追加成功,修改後結果為:
>
> **S1** - | a | b | a |
> **S2** - | | b | a |
>
> 可以看到,重複請求使得 **S1** 中有了重複記錄,使得 **S2** 中有了填充資料( 那個空白 ),這就導致了這塊 **region** 是 **defined**( 每步修改都能看到 ),但是 **inconsistent**( 不同副本資料不一樣 )
***
## 8. 垃圾回收
**GFS** 在檔案刪除後不會立刻進行回收可用的物理空間,**GFS** 空間回收採用惰性的策略,只在檔案和 **chunk** 級的常規垃圾收集時進行 。
當一個檔案被應用程式刪除時,**master** 立即把刪除操作記錄到日誌 。**master** 並不立馬回收資源,而是把檔名改為一個包含 `刪除時間戳` 的隱藏名字 。當 **master** 對名稱空間做 `常規掃描` 的時候,會刪除三天前的隱藏檔案 ,此檔案相關元資料也被刪除 。
在 **master** 對 **chunk** 名稱空間做常規掃描時,若發現 `孤兒 chunk` ( 即不被任何檔案包含的 **chunk** ),會提示 **chunkserver** 刪除這些 **chunk** 的副本 。
隱藏檔案在真正被刪除前,還可以用新的名字讀取,也可以將其改為正常檔名以恢復 。
**這種垃圾回收方式的優勢**:
- 對於元件失效是常態的大規模分散式系統上,這種垃圾回收方式簡單可靠
- 垃圾回收把儲存空間的回收操作合併到 **master** 節點規律性的後臺活動中( 如 **master** 與 **chunkserver** 的握手 ),操作被批量執行,開銷被分散
- 延快取儲空間回收為意外的、不可逆轉的刪除操作提供了安全保障
***
## 9. 容錯與診斷
### 9.1 過期失效的副本檢測
**過期副本**:當 **chunkserver** 失效時,**chunk** 的副本可能因為錯失了一些修改而失效 。
**master** 儲存了每個 **chunk** 的版本號用來區分當前副本和過期副本 。
只要 **master** 分配 **chunk** 一個 **lease** ,該 **chunk** 的版本號就會增加,然後通知最新的副本,**master** 和這些副本都將最新的版本號寫入硬碟儲存 。若某個副本所在的 **chunkserver** 處於失效狀態,他的版本號就不會增加 。之後這個 **chunkserver** 重啟,向 **master** 報告它擁有的 **chunk** 和對應版本號的時候,**master** 會檢測出過期 **chunk** 。
且當 **master** 回覆 **client** 關於 **primary** 的資訊、或者是 **chunkserver** 從哪個 **chunkserver** 進行克隆時,訊息中會附帶了版本號,**client** 或 **chunkserver** 在執行操作時都會驗證版本號以確保總是訪問當前版本的資料 。
**master** 在例行的垃圾回收過程中移除所有過期失效的副本 。
### 9.2 快速恢復
不管 **master** 和 **chunkserver** 是如何關閉的,都在數秒內恢復狀態並重新啟動 。不區分正常關閉和異常關閉 。
**master** 在災難恢復時,從磁碟上讀取最近的快照,以及重演此快照後的有限個日誌檔案就能夠恢復系統 。
**chunkserver** 重啟後會傳送 **chunk** 狀態以及版本號給 **master** 。
### 9.3 chunk複製
每個 **chunk** 都被複制到不同機架的不同伺服器上,使用者可為檔案名稱空間的不同部分設定不同的複製級別 。
當有 **chunkserver** 離線了,或者通過 **checksum** 校驗發現了已損壞的資料,**master** 通過克隆已有的副本保證每個 **chunk** 都被完整複製 。
### 9.4 master 的複製
**master** 所有操作日誌和快照檔案都被複制到多臺機器的硬碟中 。若 **master** 程序所在的機器或硬碟失效了,處於 **GFS** 系統外部的監控程序會在其他的存有完整操作日誌的機器上啟動一個新的 **master** 程序 。
> 論文中沒有詳述這個 “外部的監控程序”,據 **MIT** 的 **Robert Morris** 教授解釋,切換到新的 **master** 需要人為進行 。
還有 **shadow master** 在 **master** 宕機時提供檔案系統的只讀訪問 。它啟動的時候也會從 **chunkserver** 輪詢得到資料、定期和 **chunkserver** 握手來獲取狀態 。在 **master** 因建立和刪除副本導致副本位置資訊更新時,**shadow master** 才和 **master** 通訊來更新自身狀態 。
### 9.5 資料完整性
每個 **chunkserver** 都獨立維護 **checksum** 來檢查儲存的資料的完整性 。**checksum** 儲存在記憶體和硬碟上,也記錄在操作日誌中 。
對於讀操作來說:
- 在把資料返回給 **client** 或者其它的 **chunkserver** 之前, **chunkserver** 會校驗讀取操作涉及的範圍內的塊的 **checksum**
- 若某個副本的 **checksum** 不正確,**chunkserver** 返回請求者一個錯誤訊息,並通知 **master** 這個錯誤訊息
- 作為迴應,請求者從其他副本讀取資料,**master** 伺服器從其他副本克隆資料進行恢復
- 新副本就緒後,**master** 通知 **chunkserver** 刪掉錯誤的副本
**chunkserver** 空閒的時候,也會掃描和校驗不活動 **chunk** 的內容 。一旦發現數據損壞,**master** 建立新的正確的副本,且把損壞的副本刪除掉 。
### 9.6 診斷工具
**GFS** 伺服器會產生大量日誌,記錄大量關鍵的事件( 如 **chunkserver** 的啟動和關閉 )以及所有 **RPC** 的請求和回覆 。
可以通過重演所有訊息互動來診斷問題。日誌還可以用來跟蹤負載測試和效能分析 。
***
## 10. GFS 的優點
- **master** 和 **chunkserver** 的設計,將檔案管理和檔案儲存分離
- 將檔案分割成 **chunk** 儲存,可併發訪問,吞吐量較大
- 修改資料時控制流和資料流分離,充分利用每臺機器的頻寬
- 使用 **lease** 降低 **master** 工作負載,防止 **split-brain** 問題
- 對檔案追加和順序讀的功能有優化
- 好的容錯性
***
## 11. GFS 的缺點
- 只有一個 **master** ,元資料過多的話可能記憶體不夠用
- **client** 量很大的話,一個 **master** 負載過大
- **master** 不能出錯自動重啟,出故障後人工切換 **master** 比較耗時
- **master** 通過瀏覽所有的 **chunk** 進行垃圾回收效率太低
- 不擅長處理隨機寫問題、海量小檔案儲存
- 一致性過鬆,無法處理對一致性要求高的任務
- **GFS** 被設計用於執行在單個數據中心的系