1. 程式人生 > >The Google File System 中文版論文(轉載)

The Google File System 中文版論文(轉載)

肯定有很多人云亦云博友已經看過這篇論文的英文版,但如果有機會再看一遍中文版的話,估計會更理解GFS的精髓,原文地址,中文版地址,並在這裡謝謝譯者Alex,這個不是easy job。

摘要

我們設計並實現了Google GFS檔案系統,一個面向大規模資料密集型應用的、可伸縮的分散式檔案系統。GFS雖然執行在廉價的普遍硬體裝置上,但是它依然了提供災難冗餘的能力,為大量客戶機提供了高效能的服務。

雖然GFS的設計目標與許多傳統的分散式檔案系統有很多相同之處,但是,我們的設計還是以我們對自己的應用的負載情況和技術環境的分析為基礎 的,不管現在還是將來,GFS和早期的分散式檔案系統的設想都有明顯的不同。所以我們重新審視了傳統檔案系統在設計上的折衷選擇,衍生出了完全不同的設計思路。

GFS完全滿足了我們對儲存的需求。GFS作為儲存平臺已經被廣泛的部署在Google內部,儲存我們的服務產生和處理的資料,同時還用於那些需要大規模資料集的研究和開發工作。目前為止,最大的一個叢集利用數千臺機器的數千個硬碟,提供了數百TB的儲存空間,同時為數百個客戶機服務。

在本論文中,我們展示了能夠支援分散式應用的檔案系統介面的擴充套件,討論我們設計的許多方面,最後列出了小規模效能測試以及真實生產系統中效能相關資料。

分類和主題描述

D [4]: 3—D分佈檔案系統

常用術語

設計,可靠性,效能,測量

關鍵詞

容錯,可伸縮性,資料儲存,叢集儲存

1. 簡介

為了滿足Google迅速增長的資料處理需求,我們設計並實現了Google檔案系統(Google File System – GFS)。GFS與傳統的分散式檔案系統有著很多相同的設計目標,比如,效能、可伸縮性、可靠性以及可用性。但是,我們的設計還基於我們對我們自己的應用 的負載情況和技術環境的觀察的影響,不管現在還是將來,GFS和早期檔案系統的假設都有明顯的不同。所以我們重新審視了傳統檔案系統在設計上的折衷選擇, 衍生出了完全不同的設計思路。

首先,元件失效被認為是常態事件,而不是意外事件。GFS包括幾百甚至幾千臺普通的廉價裝置組裝的儲存機器,同時被相當數量的客戶機訪問。 GFS元件的數量和質量導致在事實上,任何給定時間內都有可能發生某些元件無法工作,某些元件無法從它們目前的失效狀態中恢復。我們遇到過各種各樣的問 題,比如應用程式bug、 作業系統的bug、人為失誤,甚至還有硬碟、記憶體、聯結器、網路以及電源失效等造成的問題。所以,持續的監控、錯誤偵測、災難冗餘以及自動恢復的機制必須 整合在GFS中。

其次,以通常的標準衡量,我們的檔案非常巨大。數GB的檔案非常普遍。每個檔案通常都包含許多應用程式物件,比如web文件。當我們經常需要處 理快速增長的、並且由數億個物件構成的、數以TB的資料集時,採用管理數億個KB大小的小檔案的方式是非常不明智的,儘管有些檔案系統支援這樣的管理方 式。因此,設計的假設條件和引數,比如I/O操作和Block的尺寸都需要重新考慮。

第三,絕大部分檔案的修改是採用在檔案尾部追加資料,而不是覆蓋原有資料的方式。對檔案的隨機寫入操作在實際中幾乎不存在。一旦寫完之後,對文 件的操作就只有讀,而且通常是按順序讀。大量的資料符合這些特性,比如:資料分析程式掃描的超大的資料集;正在執行的應用程式生成的連續的資料流;存檔的 資料;由一臺機器生成、另外一臺機器處理的中間資料,這些中間資料的處理可能是同時進行的、也可能是後續才處理的。對於這種針對海量檔案的訪問模式,客戶 端對資料塊快取是沒有意義的,資料的追加操作是效能優化和原子性保證的主要考量因素。

第四,應用程式和檔案系統API的協同設計提高了整個系統的靈活性。比如,我們放鬆了對GFS一致性模型的要求,這樣就減輕了檔案系統對應用程 序的苛刻要求,大大簡化了GFS的設計。我們引入了原子性的記錄追加操作,從而保證多個客戶端能夠同時進行追加操作,不需要額外的同步操作來保證資料的一 致性。本文後面還有對這些問題的細節的詳細討論。

Google已經針對不同的應用部署了多套GFS叢集。最大的一個叢集擁有超過1000個儲存節點,超過300TB的硬碟空間,被不同機器上的 數百個客戶端連續不斷的頻繁訪問。

2.設計概述

2.1設計預期

在設計滿足我們需求的檔案系統時候,我們的設計目標既有機會、又有挑戰。之前我們已經提到了一些需要關注的關鍵點,這裡我們將設計的預期目標的細節 展開討論。

  1.  
    1. 系統由許多廉價的普通元件組成,元件失效是一種常態。系統必須持續監控自身的狀態,它必須將元件失效作為一種常態,能夠迅速地偵測、冗餘並恢復 失效的元件。
    2. 系統儲存一定數量的大檔案。我們預期會有幾百萬檔案,檔案的大小通常在100MB或者以上。數個GB大小的檔案也是普遍存在,並且要能夠被有效 的管理。系統也必須支援小檔案,但是不需要針對小檔案做專門的優化。
    3. 系統的工作負載主要由兩種讀操作組成:大規模的流式讀取和小規模的隨機讀取。大規模的流式讀取通常一次讀取數百KB的資料,更常見的是一次讀取 1MB甚至更多的資料。來自同一個客戶機的連續操作通常是讀取同一個檔案中連續的一個區域。小規模的隨機讀取通常是在檔案某個隨機的位置讀取幾個KB數 據。如果應用程式對效能非常關注,通常的做法是把小規模的隨機讀取操作合併並排序,之後按順序批量讀取,這樣就避免了在檔案中前後來回的移動讀取位置。
    4. 系統的工作負載還包括許多大規模的、順序的、資料追加方式的寫操作。一般情況下,每次寫入的資料的大小和大規模讀類似。資料一旦被寫入後,檔案 就很少會被修改了。系統支援小規模的隨機位置寫入操作,但是可能效率不彰。
    5. 系統必須高效的、行為定義明確的(alex 注:well-defined)實現多 客戶端並行追加資料到同一個檔案裡的語意。我們的檔案通常被用於”生產者-消費者“佇列,或者其它多路檔案合併操作。通常會有數百個生產者,每個生產者進 程執行在一臺機器上,同時對一個檔案進行追加操作。使用最小的同步開銷來實現的原子的多路追加資料操作是必不可少的。檔案可以在稍後讀取,或者是消費者在 追加的操作的同時讀取檔案。
    6. 高效能的穩定網路頻寬遠比低延遲重要。我們的目標程式絕大部分要求能夠高速率的、大批量的處理資料,極少有程式對單一的讀寫操作有嚴格的響應時 間要求。

2.2 介面

GFS提供了一套類似傳統檔案系統的API介面函式,雖然並不是嚴格按照POSIX等標準API的形式實現的。檔案以分層目錄的形式組織,用路 徑名來標識。我們支援常用的操作,如建立新檔案、刪除檔案、開啟檔案、關閉檔案、讀和寫檔案。

另外,GFS提供了快照和記錄追加操作。快照以很低的成本建立一個檔案或者目錄樹的拷貝。記錄追加操作允許多個客戶端同時對一個檔案進行資料追 加操作,同時保證每個客戶端的追加操作都是原子性的。這對於實現多路結果合併,以及”生產者-消費者”佇列非常有用,多個客戶端可以在不需要額外的同步鎖 定的情況下,同時對一個檔案追加資料。我們發現這些型別的檔案對於構建大型分佈應用是非常重要的。快照和記錄追加操作將在3.4和3.3節分別討論。

2.3 架構

一個GFS叢集包含一個單獨的Master節點(alex注:這裡的一個單獨的Master節點 的含義是GFS系統中只存在一個邏輯上的Master元件。後面我們還會提到Master節點 複製,因此,為了理解方便,我們把Master節點視為一個邏輯上的概念,一個邏輯的Master節點包括兩臺物理主機,即兩臺Master伺服器)、多臺Chunk伺服器,並且同時被多個客戶端訪問,如圖1所示。所有的這些機器通常都是普通的Linux機器,運 行著使用者級別(user-level)的服務程序。我們可以很容易的把Chunk伺服器和客戶端都放在同一臺機器上,前提是機器資源允許,並且我們能夠接 受不可靠的應用程式程式碼帶來的穩定性降低的風險。

GFS儲存的檔案都被分割成固定大小的Chunk。在Chunk建立的時候,Master伺服器會給每個Chunk分配一個不變的、全球唯一的 64位的Chunk標識。Chunk伺服器把Chunk以linux檔案的形式儲存在本地硬碟上,並且根據指定的Chunk標識和位元組範圍來讀寫塊資料。 出於可靠性的考慮,每個塊都會複製到多個塊伺服器上。預設情況下,我們使用3個儲存複製節點,不過使用者可以為不同的檔案名稱空間設定不同的複製級別。

Master節點管理所有的檔案系統元資料。這些元資料包括名字空間、訪問控制資訊、檔案和Chunk的對映資訊、以及當前Chunk的位置信 息。Master節點還管理著系統範圍內的活動,比如,Chunk租用管理(alex注:BDB也有關於lease的描述,不知道是否相同)、孤兒Chunk(alex注:orphaned chunks)的回收、以及 Chunk在Chunk伺服器之間的遷移。Master節點使用心跳資訊週期地和每個Chunk伺服器通訊,傳送指令到各個Chunk伺服器並接收 Chunk伺服器的狀態資訊。

GFS客戶端程式碼以庫的形式被連結到客戶程式裡。客戶端程式碼實現了GFS檔案系統的API介面函式、應用程式與Master節點和Chunk服 務器通訊、以及對資料進行讀寫操作。客戶端和Master節點的通訊只獲取元資料,所有的資料操作都是由客戶端直接和Chunk伺服器進行互動的。我們不 提供POSIX標準的API的功能,因此,GFS API呼叫不需要深入到Linux vnode級別。

無論是客戶端還是Chunk伺服器都不需要快取檔案資料。客戶端快取資料幾乎沒有什麼用處,因為大部分程式要麼以流的方式讀取一個巨大檔案,要 麼工作集太大根本無法被快取。無需考慮快取相關的問題也簡化了客戶端和整個系統的設計和實現。(不過,客戶端會快取元資料。)Chunk伺服器不需要快取 檔案資料的原因是,Chunk以本地檔案的方式儲存,Linux作業系統的檔案系統快取會把經常訪問的資料快取在記憶體中。

2.4 單一Master節點

單一的Master節點的策略大大簡化了我們的設計。單一的Master節點可以通過全域性的資訊精確定位Chunk的位置以及進行復制決策。另 外,我們必須減少對Master節點的讀寫,避免Master節點成為系統的瓶頸。客戶端並不通過Master節點讀寫檔案資料。反之,客戶端向 Master節點詢問它應該聯絡的Chunk伺服器。客戶端將這些元資料資訊快取一段時間,後續的操作將直接和Chunk伺服器進行資料讀寫操作。

我們利用圖1解釋一下一次簡單讀取的流程。首先,客戶端把檔名和程式指定的位元組偏移,根據固定的Chunk大小,轉換成檔案的Chunk索 引。然後,它把檔名和Chunk索引發送給Master節點。Master節點將相應的Chunk標識和副本的位置資訊發還給客戶端。客戶端用檔名和 Chunk索引作為key快取這些資訊。

之後客戶端傳送請求到其中的一個副本處,一般會選擇最近的。請求資訊包含了Chunk的標識和位元組範圍。在對這個Chunk的後續讀取操作中, 客戶端不必再和Master節點通訊了,除非快取的元資料資訊過期或者檔案被重新開啟。實際上,客戶端通常會在一次請求中查詢多個Chunk信 息,Master節點的迴應也可能包含了緊跟著這些被請求的Chunk後面的Chunk的資訊。在實際應用中,這些額外的資訊在沒有任何代價的情況下,避 免了客戶端和Master節點未來可能會發生的幾次通訊。

2.5 Chunk尺寸

Chunk的大小是關鍵的設計引數之一。我們選擇了64MB,這個尺寸遠遠大於一般檔案系統的Block size。每個Chunk的副本都以普通Linux檔案的形式儲存在Chunk伺服器上,只有在需要的時候才擴大。惰性空間分配策略避免了因內部碎片造成 的空間浪費,內部碎片或許是對選擇這麼大的Chunk尺寸最具爭議一點。

選擇較大的Chunk尺寸有幾個重要的優點。首先,它減少了客戶端和Master節點通訊的需求,因為只需要一次和Mater節點的通 信就可以獲取Chunk的位置資訊,之後就可以對同一個Chunk進行多次的讀寫操作。這種方式對降低我們的工作負載來說效果顯著,因為我們的應用程式通 常是連續讀寫大檔案。即使是小規模的隨機讀取,採用較大的Chunk尺寸也帶來明顯的好處,客戶端可以輕鬆的快取一個數TB的工作資料集所有的Chunk 位置資訊。其次,採用較大的Chunk尺寸,客戶端能夠對一個塊進行多次操作,這樣就可以通過與Chunk伺服器保持較長時間的TCP連線來減少網 絡負載。第三,選用較大的Chunk尺寸減少了Master節點需要儲存的元資料的數量。這就允許我們把元資料全部放在記憶體中,在2.6.1節我們會討論 元資料全部放在記憶體中帶來的額外的好處。

另一方面,即使配合惰性空間分配,採用較大的Chunk尺寸也有其缺陷。小檔案包含較少的Chunk,甚至只有一個Chunk。當有許多的客戶 端對同一個小檔案進行多次的訪問時,儲存這些Chunk的Chunk伺服器就會變成熱點。在實際應用中,由於我們的程式通常是連續的讀取包含多個 Chunk的大檔案,熱點還不是主要的問題。

然而,當我們第一次把GFS用於批處理佇列系統的時候,熱點的問題還是產生了:一個可執行檔案在GFS上儲存為single-chunk檔案, 之後這個可執行檔案在數百臺機器上同時啟動。存放這個可執行檔案的幾個Chunk伺服器被數百個客戶端的併發請求訪問導致系統區域性過載。我們通過使用更大 的複製引數來儲存可執行檔案,以及錯開批處理佇列系統程式的啟動時間的方法解決了這個問題。一個可能的長效解決方案是,在這種的情況下,允許客戶端從其它 客戶端讀取資料。

2.6 元資料

Master伺服器(alex注:注意邏輯的 Master節點和物理的Master伺服器的區別。後續我們談的是每個Master伺服器的行為,如儲存、記憶體等等,因此我們將全部使用物理名稱)儲存3種主要型別的元資料,包括:檔案和Chunk的名稱空間、檔案和Chunk的對應關係、每個Chunk副本的存放地點。所有的元資料都儲存在 Master伺服器的記憶體中。前兩種型別的元資料(名稱空間、檔案和Chunk的對應關係)同時也會以記錄變更日誌的方式記錄在作業系統的系統日誌檔案 中,日誌檔案儲存在本地磁碟上,同時日誌會被複制到其它的遠端Master伺服器上。採用儲存變更日誌的方式,我們能夠簡單可靠的更新Master伺服器 的狀態,並且不用擔心Master伺服器崩潰導致資料不一致的風險。Master伺服器不會持久儲存Chunk位置資訊。Master伺服器在啟動時,或 者有新的Chunk伺服器加入時,向各個Chunk伺服器輪詢它們所儲存的Chunk的資訊。

2.6.1 記憶體中的資料結構

因為元資料儲存在記憶體中,所以Master伺服器的操作速度非常快。並且,Master伺服器可以在後臺簡單而高效的週期性掃描自己儲存的全部 狀態資訊。這種週期性的狀態掃描也用於實現Chunk垃圾收集、在Chunk伺服器失效的時重新複製資料、通過Chunk的遷移實現跨Chunk伺服器的 負載均衡以及磁碟使用狀況統計等功能。4.3和4.4章節將深入討論這些行為。

將元資料全部儲存在記憶體中的方法有潛在問題:Chunk的數量以及整個系統的承載能力都受限於Master伺服器所擁有的記憶體大小。但是在實際 應用中,這並不是一個嚴重的問題。Master伺服器只需要不到64個位元組的元資料就能夠管理一個64MB的Chunk。由於大多數檔案都包含多個 Chunk,因此絕大多數Chunk都是滿的,除了檔案的最後一個Chunk是部分填充的。同樣的,每個檔案的在名稱空間中的資料大小通常在64位元組以 下,因為儲存的檔名是用字首壓縮演算法壓縮過的。

即便是需要支援更大的檔案系統,為Master伺服器增加額外記憶體的費用是很少的,而通過增加有限的費用,我們就能夠把元資料全部儲存在記憶體 裡,增強了系統的簡潔性、可靠性、高效能和靈活性。

2.6.2 Chunk位置資訊

Master伺服器並不儲存持久化儲存哪個Chunk伺服器存有指定Chunk的副本的資訊。Master伺服器只是在啟動的時候輪詢Chunk服 務器以獲取這些資訊。Master伺服器能夠保證它持有的資訊始終是最新的,因為它控制了所有的Chunk位置的分配,而且通過週期性的心跳資訊監控 Chunk伺服器的狀態。

最初設計時,我們試圖把Chunk的位置資訊持久的儲存在Master伺服器上,但是後來我們發現在啟動的時候輪詢Chunk伺服器,之後定期輪詢 更新的方式更簡單。這種設計簡化了在有Chunk伺服器加入叢集、離開叢集、更名、失效、以及重啟的時候,Master伺服器和Chunk伺服器資料同步 的問題。在一個擁有數百臺伺服器的叢集中,這類事件會頻繁的發生。

可以從另外一個角度去理解這個設計決策:只有Chunk伺服器才能最終確定一個Chunk是否在它的硬碟上。我們從沒有考慮過在Master伺服器 上維護一個這些資訊的全域性檢視,因為Chunk伺服器的錯誤可能會導致Chunk自動消失(比如,硬碟損壞了或者無法訪問了),亦或者操作人員可能會重命 名一個Chunk伺服器。

2.6.3 操作日誌

操作日誌包含了關鍵的元資料變更歷史記錄。這對GFS非常重要。這不僅僅是因為操作日誌是元資料唯一的持久化儲存記錄,它也作為判斷同步操作順序的 邏輯時間基線(alex注:也就是通過邏輯日誌的序號作為操作發生 的邏輯時間,類似於事務系統中的LSN)。檔案和Chunk,連同它們的版本(參考4.5節),都由它們建立的邏輯時間唯一的、 永久的標識。

操作日誌非常重要,我們必須確保日誌檔案的完整,確保只有在元資料的變化被持久化後,日誌才對客戶端是可見的。否則,即使Chunk本身沒有出現任 何問題,我們仍有可能丟失整個檔案系統,或者丟失客戶端最近的操作。所以,我們會把日誌複製到多臺遠端機器,並且只有把相應的日誌記錄寫入到本地以及遠端 機器的硬碟後,才會響應客戶端的操作請求。Master伺服器會收集多個日誌記錄後批量處理,以減少寫入磁碟和複製對系統整體效能的影響。

Master伺服器在災難恢復時,通過重演操作日誌把檔案系統恢復到最近的狀態。為了縮短Master啟動的時間,我們必須使日誌足夠小(alex注:即重演系統操作的日誌量儘量的少)。Master 伺服器在日誌增長到一定量時對系統狀態做一次Checkpoint(alex注:Checkpoint是一種行為,一種對資料庫狀態作一次快照的行為),將 所有的狀態資料寫入一個Checkpoint文 件(alex注:並刪除之前的日誌檔案)。在災難恢復的時候,Master伺服器就通過從磁碟上讀取這個Checkpoint檔案,以及重演Checkpoint之後的有限個日誌檔案就能夠恢復系統。Checkpoint檔案以壓縮B-樹形勢的資料結構儲存,可以直 接對映到記憶體,在用於名稱空間查詢時無需額外的解析。這大大提高了恢復速度,增強了可用性。

由於建立一個Checkpoint檔案需要一定的時間,所以Master伺服器的內部狀態被組織為一種格式,這種格式要確保在Checkpoint 過程中不會阻塞正在進行的修改操作。Master伺服器使用獨立的執行緒切換到新的日誌檔案和建立新的Checkpoint檔案。新的Checkpoint 檔案包括切換前所有的修改。對於一個包含數百萬個檔案的叢集,建立一個Checkpoint檔案需要1分鐘左右的時間。建立完成後,Checkpoint 檔案會被寫入在本地和遠端的硬盤裡。

Master伺服器恢復只需要最新的Checkpoint檔案和後續的日誌檔案。舊的Checkpoint檔案和日誌檔案可以被刪除,但是為了應對 災難性的故障(alex注:catastrophes,資料備份相 關文件中經常會遇到這個詞,表示一種超出預期範圍的災難性事件),我們通常會多儲存一些歷史檔案。Checkpoint失敗不會 對正確性產生任何影響,因為恢復功能的程式碼可以檢測並跳過沒有完成的Checkpoint檔案。

2.7 一致性模型

GFS支援一個寬鬆的一致性模型,這個模型能夠很好的支撐我們的高度分佈的應用,同時還保持了相對簡單且容易實現的優點。本節我們討論GFS的一致 性的保障機制,以及對應用程式的意義。我們也著重描述了GFS如何管理這些一致性保障機制,但是實現的細節將在本論文的其它部分討論。

2.7.1 GFS一致性保障機制

檔案名稱空間的修改(例如,檔案建立)是原子性的。它們僅由Master節點的控制:名稱空間鎖提供了原子性和正確性(4.1章)的保 障;Master節點的操作日誌定義了這些操作在全域性的順序(2.6.3章)。

資料修改後檔案region(alex注:region這個詞用中文非常難以表達,我認為應該是修改操作所涉及的 檔案中的某個範圍)的狀態取決於操作的型別、成功與否、以及是否同步修改。表1總結了各種操作的結果。如果所有客戶端,無論從哪 個副本讀取,讀到的資料都一樣,那麼我們認為檔案region是“一致的”;如果對檔案的資料修改之後,region是一致的,並且客戶端能夠看到寫入操 作全部的內容,那麼這個region是“已定義的”。當一個數據修改操作成功執行,並且沒有受到同時執行的其它寫入操作的干擾,那麼影響的region就 是已定義的(隱含了一致性):所有的客戶端都可以看到寫入的內容。並行修改操作成功完成之後,region處於一致的、未定義的狀態:所有的客戶端看到同 樣的資料,但是無法讀到任何一次寫入操作寫入的資料。通常情況下,檔案region內包含了來自多個修改操作的、混雜的資料片段。失敗的修改操作導致一個 region處於不一致狀態(同時也是未定義的):不同的客戶在不同的時間會看到不同的資料。後面我們將描述應用如何區分已定義和未定義的region。 應用程式沒有必要再去細分未定義region的不同型別。

資料修改操作分為寫入或者記錄追加兩種。寫入操作把資料寫在應用程式指定的檔案偏移位置上。即使有多個修改操作並行執行時,記錄追加操作至少可 以把資料原子性的追加到檔案中一次,但是偏移位置是由GFS選擇的(3.3章)(alex注:這句話有點費解,其含義是所有的追加寫入都會成功,但是有可能被執行了多次,而且每次追加的檔案偏移量由GFS自己計算)。(相 比而言,通常說的追加操作寫的偏移位置是檔案的尾部。)GFS返回給客戶端一個偏移量,表示了包含了寫入記錄的、已定義的region的起點。另 外,GFS可能會在檔案中間插入填充資料或者重複記錄。這些資料佔據的檔案region被認定是不一致的,這些資料通常比使用者資料小的多。

經過了一系列的成功的修改操作之後,GFS確保被修改的檔案region是已定義的,並且包含最後一次修改操作寫入的資料。GFS通過以下措施確保 上述行為:(a)對Chunk的所有副本的修改操作順序一致(3.1章),(b)使用Chunk的版本號來檢測副本是否因為它所在的Chunk伺服器宕機(4.5章)而錯 過了修改操作而導致其失效。失效的副本不會再進行任何修改操作,Master伺服器也不再返回這個Chunk副本的位置資訊給客戶端。它們會被垃圾收集系 統儘快回收。

由於Chunk位置資訊會被客戶端快取,所以在資訊重新整理前,客戶端有可能從一個失效的副本讀取了資料。在快取的超時時間和檔案下一次被開啟的時 間之間存在一個時間窗,檔案再次被開啟後會清除快取中與該檔案有關的所有Chunk位置資訊。而且,由於我們的檔案大多數都是隻進行追加操作的,所以,一 個失效的副本通常返回一個提前結束的Chunk而不是過期的資料。當一個Reader(alex注:本文中將用到兩個專有名詞,Reader和Writer,分別表示執行GFS讀取和寫入操作的程式)重新嘗試並聯絡Master伺服器時,它就會立刻得到最新的Chunk位置資訊。

即使在修改操作成功執行很長時間之後,元件的失效也可能損壞或者刪除資料。GFS通過Master伺服器和所有Chunk伺服器的定期“握手” 來找到失效的Chunk伺服器,並且使用Checksum來校驗資料是否損壞(5.2章)。一旦發現問題,資料要儘快利用有效的副本進行恢復(4.3 章)。只有當一個Chunk的所有副本在GFS檢測到錯誤並採取應對措施之前全部丟失,這個Chunk才會不可逆轉的丟失。在一般情況下GFS的反應時間(alex注:指Master節點檢測到錯誤並採取應對措施)是 幾分鐘。即使在這種情況下,Chunk也只是不可用了,而不是損壞了:應用程式會收到明確的錯誤資訊而不是損壞的資料。

2.7.2 程式的實現

使用GFS的應用程式可以利用一些簡單技術實現這個寬鬆的一致性模型,這些技術也用來實現一些其它的目標功能,包括:儘量採用追加寫入而不是覆 蓋,Checkpoint,自驗證的寫入操作,自標識的記錄。

在實際應用中,我們所有的應用程式對檔案的寫入操作都是儘量採用資料追加方式,而不是覆蓋方式。一種典型的應用,應用程式從頭到尾寫入資料,生 成了一個檔案。寫入所有資料之後,應用程式自動將檔案改名為一個永久儲存的檔名,或者週期性的作Checkpoint,記錄成功寫入了多少資料。 Checkpoint檔案可以包含程式級別的校驗和。Readers僅校驗並處理上個Checkpoint之後產生的檔案region,這些檔案 region的狀態一定是已定義的。這個方法滿足了我們一致性和併發處理的要求。追加寫入比隨機位置寫入更加有效率,對應用程式的失敗處理更具有彈性。 Checkpoint可以讓Writer以漸進的方式重新開始,並且可以防止Reader處理已經被成功寫入,但是從應用程式的角度來看還並未完成的數 據。

我們再來分析另一種典型的應用。許多應用程式並行的追加資料到同一個檔案,比如進行結果的合併或者是一個生產者-消費者佇列。記錄追加方式的 “至少一次追加”的特性保證了Writer的輸出。Readers使用下面的方法來處理偶然性的填充資料和重複內容。Writers在每條寫入的記錄中都 包含了額外的資訊,例如Checksum,用來驗證它的有效性。Reader可以利用Checksum識別和拋棄額外的填充資料和記錄片段。如果應用不能 容忍偶爾的重複內容(比如,如果這些重複資料觸發了非冪等操作),可以用記錄的唯一識別符號來過濾它們,這些唯一識別符號通常用於命名程式中處理的實體物件, 例如web文件。這些記錄I/O功能(alex注:These functionalities for record I/O)(除了剔除重複資料)都包含在我們的程式共享的庫中,並且適用 於Google內部的其它的檔案介面實現。所以,相同序列的記錄,加上一些偶爾出現的重複資料,都被分發到Reader了。

3. 系統互動

我們在設計這個系統時,一個重要的原則是最小化所有操作和Master節點的互動。帶著這樣的設計理念,我們現在描述一下客戶機、Master 伺服器和Chunk伺服器如何進行互動,以實現資料修改操作、原子的記錄追加操作以及快照功能。

3.1 租約(lease)和變更順序

(alex注:lease是資料庫中的一個術語)

變更是一個會改變Chunk內容或者元資料的操作,比如寫入操作或者記錄追加操作。變更操作會在Chunk的所有副本上執行。我們使用租約 (lease)機制來保持多個副本間變更順序的一致性。Master節點為Chunk的一個副本建立一個租約,我們把這個副本叫做主Chunk。主 Chunk對Chunk的所有更改操作進行序列化。所有的副本都遵從這個序列進行修改操作。因此,修改操作全域性的順序首先由Master節點選擇的租約的 順序決定,然後由租約中主Chunk分配的序列號決定。

設計租約機制的目的是為了最小化Master節點的管理負擔。租約的初始超時設定為60秒。不過,只要Chunk被修改了,主Chunk就可以 申請更長的租期,通常會得到Master節點的確認並收到租約延長的時間。這些租約延長請求和批准的資訊通常都是附加在Master節點和Chunk服務 器之間的心跳訊息中來傳遞。有時Master節點會試圖提前取消租約(例如,Master節點想取消在一個已經被改名的檔案上的修改操作)。即使 Master節點和主Chunk失去聯絡,它仍然可以安全地在舊的租約到期後和另外一個Chunk副本簽訂新的租約。

在圖2中,我們依據步驟編號,展現寫入操作的控制流程。

  1.  
    1. 客戶機向Master節點詢問哪一個Chunk伺服器持有當前的租約,以及其它副本的位置。如果沒有一個Chunk持有租約,Master節點 就選擇其中一個副本建立一個租約(這個步驟在圖上沒有顯示)。
    2. Master節點將主Chunk的識別符號以及其它副本(又稱為secondary副本、二級副本)的位置返回給客戶機。客戶機快取這些資料以便 後續的操作。只有在主Chunk不可用,或者主Chunk回覆資訊表明它已不再持有租約的時候,客戶機才需要重新跟Master節點聯絡。
    3. 客戶機把資料推送到所有的副本上。客戶機可以以任意的順序推送資料。Chunk伺服器接收到資料並儲存在它的內部LRU快取中,一直到資料被使 用或者過期交換出去。由於資料流的網路傳輸負載非常高,通過分離資料流和控制流,我們可以基於網路拓撲情況對資料流進行規劃,提高系統性能,而不用去理會 哪個Chunk伺服器儲存了主Chunk。3.2章節會進一步討論這點。
    4. 當所有的副本都確認接收到了資料,客戶機發送寫請求到主Chunk伺服器。這個請求標識了早前推送到所有副本的資料。主Chunk為接收到的所 有操作分配連續的序列號,這些操作可能來自不同的客戶機,序列號保證了操作順序執行。它以序列號的順序把操作應用到它自己的本地狀態中(alex注:也就是在本地執行這些操作,這句話按字面翻譯有點費解,也許應該翻譯為 “它順序執行這些操作,並更新自己的狀態”)。
    5. 主Chunk把寫請求傳遞到所有的二級副本。每個二級副本依照主Chunk分配的序列號以相同的順序執行這些操作。
    6. 所有的二級副本回復主Chunk,它們已經完成了操作。
    7. 主Chunk伺服器(alex注:即主Chunk所在的 Chunk伺服器)回覆客戶機。任何副本產生的任何錯誤都會返回給客戶機。在出現錯誤的情況下,寫入操作可能在主Chunk和一 些二級副本執行成功。(如果操作在主Chunk上失敗了,操作就不會被分配序列號,也不會被傳遞。)客戶端的請求被確認為失敗,被修改的region處於 不一致的狀態。我們的客戶機程式碼通過重複執行失敗的操作來處理這樣的錯誤。在從頭開始重複執行之前,客戶機會先從步驟(3)到步驟(7)做幾次嘗試。

如果應用程式一次寫入的資料量很大,或者資料跨越了多個Chunk,GFS客戶機程式碼會把它們分成多個寫操作。這些操作都遵循前面描述的控制流 程,但是可能會被其它客戶機上同時進行的操作打斷或者覆蓋。因此,共享的檔案region的尾部可能包含來自不同客戶機的資料片段,儘管如此,由於這些分 解後的寫入操作在所有的副本上都以相同的順序執行完成,Chunk的所有副本都是一致的。這使檔案region處於2.7節描述的一致的、但是未定義的狀 態。

3.2 資料流

為了提高網路效率,我們採取了把資料流和控制流分開的措施。在控制流從客戶機到主Chunk、然後再到所有二級副本的同時,資料以管道的方式, 順序的沿著一個精心選擇的Chunk伺服器鏈推送。我們的目標是充分利用每臺機器的頻寬,避免網路瓶頸和高延時的連線,最小化推送所有資料的延時。

為了充分利用每臺機器的頻寬,資料沿著一個Chunk伺服器鏈順序的推送,而不是以其它拓撲形式分散推送(例如,樹型拓撲結構)。線性推送模式 下,每臺機器所有的出口頻寬都用於以最快的速度傳輸資料,而不是在多個接受者之間分配頻寬。

為了儘可能的避免出現網路瓶頸和高延遲的連結(eg,inter-switch最有可能出現類似問題),每臺機器都儘量的在網路拓撲中選擇一臺還沒有接收到資料的、離自己最近的機器作為目標推送資料。假設客戶機把資料從Chunk伺服器 S1推送到S4。它把資料推送到最近的Chunk伺服器S1。S1把資料推送到S2,因為S2和S4中最接近的機器是S2。同樣的,S2把資料傳遞給S3 和S4之間更近的機器,依次類推推送下去。我們的網路拓撲非常簡單,通過IP地址就可以計算出節點的“距離”。

最後,我們利用基於TCP連 接的、管道式資料推送方式來最小化延遲。Chunk伺服器接收到資料後,馬上開始向前推送。管道方式的資料推送對我們幫助很大,因為我們採用全雙工的交換 網路。接收到資料後立刻向前推送不會降低接收的速度。在沒有網路擁塞的情況下,傳送B位元組的資料到R個副本的理想時間是 B/T+RL ,T是網路的吞吐量,L是在兩臺機器資料傳輸的延遲。通常情況下,我們的網路連線速度是100Mbps(T),L將遠小於1ms。因此,1MB的資料在理 想情況下80ms左右就能分發出去。

3.3 原子的記錄追加

GFS提供了一種原子的資料追加操作–記錄追加。傳統方式的寫入操作,客戶程式會指定資料寫入的偏移量。對同一個region的並行寫入操作不 是序列的:region尾部可能會包含多個不同客戶機寫入的資料片段。使用記錄追加,客戶機只需要指定要寫入的資料。GFS保證至少有一次原子的寫入操作 成功執行(即寫入一個順序的byte流), 寫入的資料追加到GFS指定的偏移位置上,之後GFS返回這個偏移量給客戶機。這類似於在Unix作業系統 程式設計環境中,對以O_APPEND模式開啟的檔案,多個併發寫操作在沒有競態條件時的行為。

記錄追加在我們的分佈應用中非常頻繁的使用,在這些分散式應用中,通常有很多的客戶機並行地對同一個檔案追加寫入資料。如果我們採用傳統方式的 檔案寫入操作,客戶機需要額外的複雜、昂貴的同步機制,例如使用一個分散式的鎖管理器。在我們的工作中,這樣的檔案通常用於多個生產者/單一消費者的佇列 系統,或者是合併了來自多個客戶機的資料的結果檔案。

記錄追加是一種修改操作,它也遵循3.1節描述的控制流程,除了在主Chunk有些額外的控制邏輯。客戶機把資料推送給檔案最後一個Chunk 的所有副本,之後傳送請求給主Chunk。主Chunk會檢查這次記錄追加操作是否會使Chunk超過最大尺寸(64MB)。如果超過了最大尺寸,主 Chunk首先將當前Chunk填充到最大尺寸,之後通知所有二級副本做同樣的操作,然後回覆客戶機要求其對下一個Chunk重新進行記錄追加操作。(記 錄追加的資料大小嚴格控制在Chunk最大尺寸的1/4,這樣即使在最壞情況下,資料碎片的數量仍然在可控的範圍。)通常情況下追加的記錄不超過 Chunk的最大尺寸,主Chunk把資料追加到自己的副本內,然後通知二級副本把資料寫在跟主Chunk一樣的位置上,最後回覆客戶機操作成功。

如果記錄追加操作在任何一個副本上失敗了,客戶端就需要重新進行操作。重新進行記錄追加的結果是,同一個Chunk的不同副本可能包含不同的數 據–重複包含一個記錄全部或者部分的資料。GFS並不保證Chunk的所有副本在位元組級別是完全一致的。它只保證資料作為一個整體原子的被至少寫入一次。 這個特性可以通過簡單觀察推匯出來:如果操作成功執行,資料一定已經寫入到Chunk的所有副本的相同偏移位置上。這之後,所有的副本至少都到了記錄尾部 的長度,任何後續的記錄都會追加到更大的偏移地址,或者是不同的Chunk上,即使其它的Chunk副本被Master節點選為了主Chunk。就我們的 一致性保障模型而言,記錄追加操作成功寫入資料的region是已定義的(因此也是一致的),反之則是不一致的(因此也就是未定義的)。正如我們在 2.7.2節討論的,我們的程式可以處理不一致的區域。

3.4 快照

(alex注:這一節非常難以理解,總的來說依次講述了 什麼是快照、快照使用的COW技術、快照如何不干擾當前操作)

快照操作幾乎可以瞬間完成對一個檔案或者目錄樹(“源”)做一個拷貝,並且幾乎不會對正在進行的其它操作造成任何干擾。我們的使用者可以使用快照 迅速的建立一個巨大的資料集的分支拷貝(而且經常是遞迴的拷貝拷貝),或者是在做實驗性的資料操作之前,使用快照操作備份當前狀態,這樣之後就可以輕鬆的 提交或者回滾到備份時的狀態。

就像AFS(alex注:AFS,即Andrew File System,一種分散式檔案系統),我們用標準的copy-on-write技術實現快照。當Master節點收到一個快照請 求,它首先取消作快照的檔案的所有Chunk的租約。這個措施保證了後續對這些Chunk的寫操作都必須與Master互動互動以找到租約持有者。這就給 Master節點一個率先建立Chunk的新拷貝的機會。

租約取消或者過期之後,Master節點把這個操作以日誌的方式記錄到硬碟上。然後,Master節點通過複製原始檔或者目錄的元資料的方式, 把這條日誌記錄的變化反映到儲存在記憶體的狀態中。新建立的快照檔案和原始檔指向完全相同的Chunk地址。

在快照操作之後,當客戶機第一次想寫入資料到Chunk C,它首先會發送一個請求到Master節點查詢當前的租約持有者。Master節點注意到Chunke C的引用計數超過了1(alex注:不太明白為什麼會大於1.難道是Snapshot沒有釋放引用計數?)。 Master節點不會馬上回復客戶機的請求,而是選擇一個新的Chunk控制代碼C`。之後,Master節點要求每個擁有Chunk C當前副本的Chunk伺服器建立一個叫做C`的新Chunk。通過在源Chunk所在Chunk伺服器上建立新的Chunk,我們確保資料在本地而不是 通過網路複製(我們的硬碟比我們的100Mb乙太網大約快3倍)。從這點來講,請求的處理方式和任何其它Chunk沒什麼不同:Master節點確保新 Chunk C`的一個副本擁有租約,之後回覆客戶機,客戶機得到回覆後就可以正常的寫這個Chunk,而不必理會它是從一個已存在的Chunk克隆出來的。

4. Master節點的操作

Master節點執行所有的名稱空間操作。此外,它還管理著整個系統裡所有Chunk的副本:它決定Chunk的儲存位置,建立新Chunk和 它的副本,協調各種各樣的系統活動以保證Chunk被完全複製,在所有的Chunk伺服器之間的進行負載均衡,回收不再使用的儲存空間。本節我們討論上述 的主題。

4.1 名稱空間管理和鎖

Master節點的很多操作會花費很長的時間:比如,快照操作必須取消Chunk伺服器上快照所涉及的所有的Chunk的租約。我們不希望在這 些操作的執行時,延緩了其它的Master節點的操作。因此,我們允許多個操作同時進行,使用名稱空間的region上的鎖來保證執行的正確順序。

不同於許多傳統檔案系統,GFS沒有針對每個目錄實現能夠列出目錄下所有檔案的資料結構。GFS也不支援檔案或者目錄的連結(即Unix術語中 的硬連結或者符號連結)。在邏輯上,GFS的名稱空間就是一個全路徑和元資料對映關係的查詢表。利用字首壓縮,這個表可以高效的儲存在記憶體中。在儲存名稱 空間的樹型結構上,每個節點(絕對路徑的檔名或絕對路徑的目錄名)都有一個關聯的讀寫鎖。

每個Master節點的操作在開始之前都要獲得一系列的鎖。通常情況下,如果一個操作涉及/d1/d2/…/dn/leaf,那麼操作首先要獲 得目錄/d1,/d1/d2,…,/d1/d2/…/dn的讀鎖,以及/d1/d2/…/dn/leaf的讀寫鎖。注意,根據操作的不同,leaf可以是 一個檔案,也可以是一個目錄。

現在,我們演示一下在/home/user 被快照到/save/user的時候,鎖機制如何防止建立檔案/home/user/foo。快照操作獲取/home和/save的讀取鎖,以及 /home/user和/save/user的寫入鎖。檔案建立操作獲得/home和/home/user的讀取鎖,以及/home/user/foo的 寫入鎖。這兩個操作要順序執行,因為它們試圖獲取的/home/user的鎖是相互衝突。檔案建立操作不需要獲取父目錄的寫入鎖,因為這裡沒有”目錄”, 或者類似inode等用來禁止修改的資料結構。檔名的讀取鎖足以防止父目錄被刪除。

採用這種鎖方案的優點是支援對同一目錄的並行操作。比如,可以再同一個目錄下同時建立多個檔案:每一個操作都獲取一個目錄名的上的讀取鎖和檔案 名上的寫入鎖。目錄名的讀取鎖足以的防止目錄被刪除、改名以及被快照。檔名的寫入鎖序列化檔案建立操作,確保不會多次建立同名的檔案。

因為名稱空間可能有很多節點,讀寫鎖採用惰性分配策略,在不再使用的時候立刻被刪除。同樣,鎖的獲取也要依據一個全域性一致的順序來避免死鎖:首先按 名稱空間的層次排序,在同一個層次內按字典順序排序。

4.2 副本的位置

GFS叢集是高度分佈的多層佈局結構,而不是平面結構。典型的拓撲結構是有數百個Chunk伺服器安裝在許多機架上。Chunk伺服器被來自同 一或者不同機架上的數百個客戶機輪流訪問。不同機架上的兩臺機器間的通訊可能跨越一個或多個網路交換機。另外,機架的出入頻寬可能比機架內所有機器加和在 一起的頻寬要小。多層分佈架構對資料的靈活性、可靠性以及可用性方面提出特有的挑戰。

Chunk副本位置選擇的策略服務兩大目標:最大化資料可靠性和可用性,最大化網路頻寬利用率。為了實現這兩個目的,僅僅是在多臺機器上分別存 儲這些副本是不夠的,這隻能預防硬碟損壞或者機器失效帶來的影響,以及最大化每臺機器的網路頻寬利用率。我們必須在多個機架間分佈儲存Chunk的副本。 這保證Chunk的一些副本在整個機架被破壞或掉線(比如,共享資源,如電源或者網路交換機造成的問題)的情況下依然存在且保持可用狀態。這還意味著在網 絡流量方面,尤其是針對Chunk的讀操作,能夠有效利用多個機架的整合頻寬。另一方面,寫操作必須和多個機架上的裝置進行網路通訊,但是這個代價是我們 願意付出的。

4.3 建立,重新複製,重新負載均衡

Chunk的副本有三個用途:Chunk建立,重新複製和重新負載均衡。

當Master節點建立一個Chunk時,它會選擇在哪裡放置初始的空的副本。Master節點會考慮幾個因素。(1)我們希望在低於平均硬碟 使用率的Chunk伺服器上儲存新的副本。這樣的做法最終能夠平衡Chunk伺服器之間的硬碟使用率。(2)我們希望限制在每個Chunk伺服器上”最 近”的Chunk建立操作的次數。雖然建立操作本身是廉價的,但是建立操作也意味著隨之會有大量的寫入資料的操作,因為Chunk在Writer真正寫入 資料的時候才被建立,而在我們的”追加一次,讀取多次”的工作模式下,Chunk一旦寫入成功之後就會變為只讀的了。(3)如上所述,我們希望把 Chunk的副本分佈在多個機架之間。

當Chunk的有效副本數量少於使用者指定的複製因數的時候,Master節點會重新複製它。這可能是由幾個原因引起的:一個Chunk伺服器不 可用了,Chunk伺服器報告它所儲存的一個副本損壞了,Chunk伺服器的一個磁碟因為錯誤不可用了,或者Chunk副本的複製因數提高了。每個需要被 重新複製的Chunk都會根據幾個因素進行排序。一個因素是Chunk現有副本數量和複製因數相差多少。例如,丟失兩個副本的Chunk比丟失一個副本的 Chunk有更高的優先順序。另外,我們優先重新複製活躍(live)檔案的Chunk而不是最近剛被刪除的檔案的Chunk(檢視4.4節)。最後,為了 最小化失效的Chunk對正在執行的應用程式的影響,我們提高會阻塞客戶機程式處理流程的Chunk的優先順序。

Master節點選擇優先順序最高的Chunk,然後命令某個Chunk伺服器直接從可用的副本”克隆”一個副本出來。選擇新副本的位置的策略和 建立時類似:平衡硬碟使用率、限制同一臺Chunk伺服器上的正在進行的克隆操作的數量、在機架間分佈副本。為了防止克隆產生的網路流量大大超過客戶機的 流量,Master節點對整個叢集和每個Chunk伺服器上的同時進行的克隆操作的數量都進行了限制。另外,Chunk伺服器通過調節它對源Chunk服 務器讀請求的頻率來限制它用於克隆操作的頻寬。

最後,Master伺服器週期性地對副本進行重新負載均衡:它檢查當前的副本分佈情況,然後移動副本以便更好的利用硬碟空間、更有效的進行負載 均衡。而且在這個過程中,Master伺服器逐漸的填滿一個新的Chunk伺服器,而不是在短時間內用新的Chunk填滿它,以至於過載。新副本的儲存位 置選擇策略和上面討論的相同。另外,Master節點必須選擇哪個副本要被移走。通常情況,Master節點移走那些剩餘空間低於平均值的Chunk服務 器上的副本,從而平衡系統整體的硬碟使用率。

4.4 垃圾回收

GFS在檔案刪除後不會立刻回收可用的物理空間。GFS空間回收採用惰性的策略,只在檔案和Chunk級的常規垃圾收集時進行。我們發現這個方 法使系統更簡單、更可靠。

4.4.1 機制

當一個檔案被應用程式刪除時,Master節點象對待其它修改操作一樣,立刻把刪除操作以日誌的方式記錄下來。但是,Master節點並不馬上 回收資源,而是把檔名改為一個包含刪除時間戳的、隱藏的名字。當Master節點對檔案系統名稱空間做常規掃描的時候,它會刪除所有三天前的隱藏檔案 (這個時間間隔是可以設定的)。直到檔案被真正刪除,它們仍舊可以用新的特殊的名字讀取,也可以通過把隱藏檔案改名為正常顯示的檔名的方式“反刪除”。 當隱藏檔案被從名稱空間中刪除,Master伺服器記憶體中儲存的這個檔案的相關元資料才會被刪除。這也有效的切斷了檔案和它包含的所有Chunk的連線(alex注:原文是This effectively severs its links to all its chunks)

在對Chunk名字空間做類似的常規掃描時,Master節點找到孤兒Chunk(不被任何檔案包含的Chunk)並刪除它們的元資料。 Chunk伺服器在和Master節點互動的心跳資訊中,報告它擁有的Chunk子集的資訊,Master節點回復Chunk伺服器哪些Chunk在 Master節點儲存的元資料中已經不存在了。Chunk伺服器可以任意刪除這些Chunk的副本。

4.4.2 討論

雖然分散式垃圾回收在程式語言領域是一個需要複雜的方案才能解決的難題,但是在GFS系統中是非常簡單的。我們可以輕易的得到Chunk的所有 引用:它們都只儲存在Master伺服器上的檔案到塊的對映表中。我們也可以很輕易的得到所有Chunk的副本:它們都以Linux檔案的形式儲存在 Chunk伺服器的指定目錄下。所有Master節點不能識別的副本都是”垃圾”。

垃圾回收在空間回收方面相比直接刪除有幾個優勢。首先,對於元件失效是常態的大規模分散式系統,垃圾回收方式簡單可靠。Chunk可能在某些 Chunk伺服器建立成功,某些Chunk伺服器上建立失敗,失敗的副本處於無法被Master節點識別的狀態。副本刪除訊息可能丟失,Master節點 必須重新發送失敗的刪除訊息,包括自身的和Chunk伺服器的(alex 注:自身的指刪除metadata的訊息)。垃圾回收提供了一致的、可靠的清除無用副本的方法。第二,垃圾回收把儲存空間的回收 操作合併到Master節點規律性的後臺活動中,比如,例行掃描和與Chunk伺服器握手等。因此,操作被批量的執行,開銷會被分散。另外,垃圾回收在 Master節點相對空閒的時候完成。這樣Master節點就可以給那些需要快速反應的客戶機請求提供更快捷的響應。第三,延快取儲空間回收為意外的、不 可逆轉的刪除操作提供了安全保障。

根據我們的使用經驗,延遲迴收空間的主要問題是,延遲迴收會阻礙使用者調優儲存空間的使用,特別是當儲存空間比較緊缺的時候。當應用程式重複建立 和刪除臨時檔案時,釋放的儲存空間不能馬上重用。我們通過顯式的再次刪除一個已經被刪除的檔案的方式加速空間回收的速度。我們允許使用者為名稱空間的不同部 分設定不同的複製和回收策略。例如,使用者可以指定某些目錄樹下面的檔案不做複製,刪除的檔案被即時的、不可恢復的從檔案系統移除。

4.5 過期失效的副本檢測

當Chunk伺服器失效時,Chunk的副本有可能因錯失了一些修改操作而過期失效。Master節點儲存了每個Chunk的版本號,用來區分 當前的副本和過期副本。

無論何時,只要Master節點和Chunk簽訂一個新的租約,它就增加Chunk的版本號,然後通知最新的副本。Master節點和這些副本 都把新的版本號記錄在它們持久化儲存的狀態資訊中。這個動作發生在任何客戶機得到通知以前,因此也是對這個Chunk開始寫之前。如果某個副本所在的 Chunk伺服器正好處於失效狀態,那麼副本的版本號就不會被增加。Master節點在這個Chunk伺服器重新啟動,並且向Master節點報告它擁有 的Chunk的集合以及相應的版本號的時候,就會檢測出它包含過期的Chunk。如果Master節點看到一個比它記錄的版本號更高的版本 號,Master節點會認為它和Chunk伺服器簽訂租約的操作失敗了,因此會選擇更高的版本號作為當前的版本號。

Master節點在例行的垃圾回收過程中移除所有的過期失效副本。在此之前,Master節點在回覆客戶機的Chunk資訊請求的時候,簡單的 認為那些過期的塊根本就不存在。另外一重保障措施是,Master節點在通知客戶機哪個Chunk伺服器持有租約、或者指示Chunk伺服器從哪個 Chunk伺服器進行克隆時,訊息中都附帶了Chunk的版本號。客戶機或者Chunk伺服器在執行操作時都會驗證版本號以確保總是訪問當前版本的資料。

5. 容錯和診斷

我們在設計GFS時遇到的最大挑戰之一是如何處理頻繁發生的元件失效。元件的數量和質量讓這些問題出現的頻率遠遠超過一般系統意外發生的頻率: 我們不能完全依賴機器的穩定性,也不能完全相信硬碟的可靠性。元件的失效可能造成系統不可用,更糟糕的是,還可能產生不完整的資料。我們討論我們如何面對這些挑戰,以及當元件失效不可避免的發生時,用GFS自帶工具診斷系統故障。

5.1 高可用性

在GFS叢集的數百個伺服器之中,在任何給定的時間必定會有些伺服器是不可用的。我們使用兩條簡單但是有效的策略保證整個系統的高可用性:快速 恢復和複製。

5.1.1 快速恢復

不管Master伺服器和Chunk伺服器是如何關閉的,它們都被設計為可以在數秒鐘內恢復它們的狀態並重新啟動。事實上,我們並不區分正常關 閉和異常關閉;通常,我們通過直接kill掉程序來關閉伺服器。客戶機和其它的伺服器會感覺到系統有點顛簸(alex注:a minor hiccup),正在發出的請 求會超時,需要重新連線到重啟後的伺服器,然後重試這個請求。6.6.2章節記錄了實測的啟動時間。

5.1.2 Chunk複製

正如之前討論的,每個Chunk都被複制到不同機架上的不同的Chunk伺服器上。使用者可以為檔案名稱空間的不同部分設定不同的複製級別。預設是 3。當有Chunk伺服器離線了,或者通過Chksum校驗(參考5.2節)發現了已經損壞的資料,Master節點通過克隆已有的副本保證每個 Chunk都被完整複製(alex注:即每個Chunk都有複製因 子制定的個數個副本,預設是3)。雖然Chunk複製策略對我們非常有效,但是我們也在尋找其它形式的跨伺服器的冗餘解決方案, 比如使用奇偶校驗、或者Erasure codes(alex注:Erasure codes用來解決連結層中不相關的錯誤,以及網路擁塞和buffer限制造成的丟包錯誤)來解決我們日益增長的只讀儲存需求。 我們的系統主要的工作負載是追加方式的寫入和讀取操作,很少有隨機的寫入操作,因此,我們認為在我們這個高度解耦合的系統架構下實現這些複雜的冗餘方案很 有挑戰性,但並非不可實現。

5.1.3 Master伺服器的複製

為了保證Master伺服器的可靠性,Master伺服器的狀態也要複製。Master伺服器所有的操作日誌和checkpoint檔案都被複 制到多臺機器上。對Master伺服器狀態的修改操作能夠提交成功的前提是,操作日誌寫入到Master伺服器的備節點和本機的磁碟。簡單說來,一個 Master服務程序負責所有的修改操作,包括後臺的服務,比如垃圾回收等改變系統內部狀態活動。當它失效的時,幾乎可以立刻重新啟動。如果Master 程序所在的機器或者磁碟失效了,處於GFS系統外部的監控程序會在其它的存有完整操作日誌的機器上啟動一個新的Master程序。客戶端使用規範的名字訪 問Master(比如gfs-test)節點,這個名字類似DNS別名,因此也就可以在Master程序轉到別的機器上執行時,通過更改別 名的實際指向訪問新的Master節點。

此外,GFS中還有些“影子”Master伺服器,這些“影子”伺服器在“主”Master伺服器宕機的時候提供檔案系統的只讀訪問。它們是影子, 而不是映象,所以它們的資料可能比“主”Master伺服器更新要慢,通常是不到1秒。對於那些不經常改變的檔案、或者那些允許獲取的資料有少量過期的應 用程式,“影子”Master伺服器能夠提高讀取的效率。事實上,因為檔案內容是從Chunk伺服器上讀取的,因此,應用程式不會發現過期的檔案內容。在 這個短暫的時間窗內,過期的可能是檔案的元資料,比如目錄的內容或者訪問控制資訊。

“影子”Master伺服器為了保持自身狀態是最新的,它會讀取一份當前正在進行的操作的日誌副本,並且依照和主Master伺服器完全相同的順序 來更改內部的資料結構。和主Master伺服器一樣,“影子”Master伺服器在啟動的時候也會從Chunk伺服器輪詢資料(之後定期拉資料),資料中 包括了Chunk副本的位置資訊;“影子”Master伺服器也會定期和Chunk伺服器“握手”來確定它們的狀態。在主Master伺服器因建立和刪除 副本導致副本位置資訊更新時,“影子”Master伺服器才和主Master伺服器通訊來更新自身狀態。

5.2 資料完整性

每個Chunk伺服器都使用Checksum來檢查儲存的資料是否損壞。考慮到一個GFS叢集通常都有好幾百臺機器、幾千塊硬碟,磁碟損壞導致資料 在讀寫過程中損壞或者丟失是非常常見的(第7節講了一個原因)。我們可以通過別的Chunk副本來解決資料損壞問題,但是跨越Chunk伺服器比較副本來 檢查資料是否損壞很不實際。另外,GFS允許有歧義的副本存在:GFS修改操作的語義,特別是早先討論過的原子紀錄追加的操作,並不保證副本完全相同(alex注:副本不是byte-wise 完全一致的)。因此,每個Chunk伺服器必須獨立維護Checksum來校驗自己的副本的完整性。

我們把每個Chunk都分成64KB大小的塊。每個塊都對應一個32位的Checksum。和其它元資料一樣,Checksum與其它的使用者資料是 分開的,並且儲存在記憶體和硬碟上,同時也記錄操作日誌。

對於讀操作來說,在把資料返回給客戶端或者其它的Chunk伺服器之前,Chunk伺服器會校驗讀取操作涉及的範圍內的塊的Checksum。因此 Chunk伺服器不會把錯誤資料傳遞到其它的機器上。如果發生某個塊的Checksum不正確,Chunk伺服器返回給請求者一個錯誤資訊,並且通知 Master伺服器這個錯誤。作為迴應,請求者應當從其它副本讀取資料,Master伺服器也會從其它副本克隆資料進行恢復。當一個新的副本就緒 後,Master伺服器通知副本錯誤的Chunk伺服器刪掉錯誤的副本。

Checksum對讀操作的效能影響很小,可以基於幾個原因來分析一下。因為大部分的讀操作都至少要讀取幾個塊,而我們只需要讀取一小部分額外的相 關資料進行校驗。GFS客戶端程式碼通過每次把讀取操作都對齊在Checksum block的邊界上,進一步減少了這些額外的讀取操作的負面影響。另外,在Chunk伺服器上,Chunksum的查詢和比較不需要I/O操 作,Checksum的計算可以和I/O操作同時進行。

Checksum的計算針對在Chunk尾部的追加寫入操作作了高度優化(與之對應的是覆蓋現有資料的寫入操作),因為這類操作在我們的工作中佔了 很大比例。我們只增量更新最後一個不完整的塊的Checksum,並且用所有的追加來的新Checksum塊來計算新的Checksum。即使是最後一個 不完整的Checksum塊已經損壞了,而且我們不能夠馬上檢查出來,由於新的Checksum和已有資料不吻合,在下次對這個塊進行讀取操作的時候,會 檢查出資料已經損壞了。

相比之下,如果寫操作覆蓋已經存在的一個範圍內的Chunk,我們必須讀取和校驗被覆蓋的第一個和最後一個塊,然後再執行寫操作;操作完成之後再重 新計算和寫入新的Checksum。如果我們不校驗第一個和最後一個被寫的塊,那麼新的Checksum可能會隱藏沒有被覆蓋區域內的資料錯誤。

在Chunk伺服器空閒的時候,它會掃描和校驗每個不活動的Chunk的內容。這使得我們能夠發現很少被讀取的Chunk是否完整。一旦發現有 Chunk的資料損壞,Master可以建立一個新的、正確的副本,然後把損壞的副本刪除掉。這個機制也避免了非活動的、已損壞的Chunk欺騙 Master節點,使Master節點認為它們已經有了足夠多的副本了。

5.3 診斷工具

詳盡的、深入細節的診斷日誌,在問題隔離、除錯、以及效能分析等方面給我們帶來無法估量的幫助,同時也只需要很小的開銷。沒有日誌的幫助,我們很難 理解短暫的、不重複的機器之間的訊息互動。GFS的伺服器會產生大量的日誌,記錄了大量關鍵的事件(比如,Chunk伺服器啟動和關閉)以及所有的RPC的請求和回 復。這些診斷日誌可以隨意刪除,對系統的正確執行不造成任何影響。然而,我們在儲存空間允許的情況下會盡量的儲存這些日誌。

RPC日誌包含了網路上發生的所有請求和響應的詳細記錄,但是不包括讀寫的檔案資料。通過匹配請求與迴應,以及收集不同機器上的RPC日誌記錄,我 們可以重演所有的訊息互動來診斷問題。日誌還用來跟蹤