The Google File System中文版

摘要

我們設計並實現了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設計預期

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

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

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. 客戶機向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日誌記錄,我們可以重演所有的消息交互來診斷問題。日誌還用來跟蹤負載測試和性能分析。

日誌對性能的影響很小(遠小於它帶來的好處),因爲這些日誌的寫入方式是順序的、異步的。最近發生的事件日誌保存在內存中,可用於持續不斷的在線監控。

6. 度量

本節中,我們將使用一些小規模基準測試來展現GFS系統架構和實現上的一些固有瓶頸,還有些來自Google內部使用的真實的GFS集羣的基準數據。

6.1 小規模基準測試

我們在一個包含1臺Master服務器,2臺Master服務器複製節點,16臺Chunk服務器和16個客戶機組成的GFS集羣上測量性能。注意,採用這樣的集羣配置方案只是爲了易於測試。典型的GFS集羣有數百個Chunk服務器和數百個客戶機。

 

所有機器的配置都一樣:兩個PIII 1.4GHz處理器,2GB內存,兩個80G/5400rpm的硬盤,以及100Mbps全雙工以太網連接到一個HP2524交換機。GFS集羣中所有的19臺服務器都連接在一個交換機,所有16臺客戶機連接到另一個交換機上。兩個交換機之間使用1Gbps的線路連接。

 

6.1.1 讀取

N個客戶機從GFS文件系統同步讀取數據。每個客戶機從320GB的文件集合中隨機讀取4MB region的內容。讀取操作重複執行256次,因此,每個客戶機最終都讀取1GB的數據。所有的Chunk服務器加起來總共只有32GB的內存,因此,我們預期只有最多10%的讀取請求命中Linux的文件系統緩衝。我們的測試結果應該和一個在沒有文件系統緩存的情況下讀取測試的結果接近。
圖三:合計吞吐量:上邊的曲線顯示了我們網絡拓撲下的合計理論吞吐量上限。下邊的曲線顯示了觀測到的吞吐量。這個曲線有着95%的可靠性,因爲有時候測量會不夠精確。
圖3(a)顯示了N個客戶機整體的讀取速度以及這個速度的理論極限。當連接兩個交換機的1Gbps的鏈路飽和時,整體讀取速度達到理論的極限值是125MB/S,或者說每個客戶機配置的100Mbps網卡達到飽和時,每個客戶機讀取速度的理論極限值是12.5MB/s。實測結果是,當一個客戶機讀取的時候,讀取的速度是10MB/s,也就是說達到客戶機理論讀取速度極限值的80%。對於16個客戶機,整體的讀取速度達到了94MB/s,大約是理論整體讀取速度極限值的75%,也就是說每個客戶機的讀取速度是6MB/s。讀取效率從80%降低到了75%,主要的原因是當讀取的客戶機增加時,多個客戶機同時讀取一個Chunk服務器的機率也增加了,導致整體的讀取效率下降。

6.1.2 寫入

N個客戶機同時向N個不同的文件中寫入數據。每個客戶機以每次1MB的速度連續寫入1GB的數據。圖3(b)顯示了整體的寫入速度和它們理論上的極限值。理論上的極限值是67MB/s,因爲我們需要把每一byte寫入到16個Chunk服務器中的3個上,而每個Chunk服務器的輸入連接速度是12.5MB/s。
 
一個客戶機的寫入速度是6.3MB,大概是理論極限值的一半。導致這個結果的主要原因是我們的網絡協議棧。它與我們推送數據到Chunk服務器時採用的管道模式不相適應。從一個副本到另一個副本的數據傳輸延遲降低了整個的寫入速度。
 
16個客戶機整體的寫入速度達到了35MB/s(即每個客戶機2.2MB/s),大約只是理論極限值的一半。和多個客戶機讀取的情形很類型,隨着客戶機數量的增加,多個客戶機同時寫入同一個Chunk服務器的機率也增加了。而且,16個客戶機並行寫入可能引起的衝突比16個客戶機並行讀取要大得多,因爲每個寫入都會涉及三個不同的副本。
 
寫入的速度比我們想象的要慢。在實際應用中,這沒有成爲我們的主要問題,因爲即使在單個客戶機上能夠感受到延時,它也不會在有大量客戶機的時候對整體的寫入帶寬造成顯著的影響。

6.1.3 記錄追加

圖3(c)顯示了記錄追加操作的性能。N個客戶機同時追加數據到一個文件。記錄追加操作的性能受限於保存文件最後一個Chunk的Chunk服務器的帶寬,而與客戶機的數量無關。記錄追加的速度由一個客戶機的6.0MB/s開始,下降到16個客戶機的4.8MB/s爲止,速度的下降主要是由於不同客戶端的網絡擁塞以及網絡傳輸速度的不同而導致的。

我們的程序傾向於同時處理多個這樣的文件。換句話說,即N個客戶機同時追加數據到M個共享文件中,這裏N和M都是數十或者數百以上。所以,在我們的實際應用中,Chunk服務器的網絡擁塞並沒有成爲一個嚴重問題,如果Chunk服務器的某個文件正在寫入,客戶機會去寫另外一個文件。

6.2 實際應用中的集羣

我們現在來仔細評估一下Google內部正在使用的兩個集羣,它們具有一定的代表性。集羣A通常被上百個工程師用於研究和開發。典型的任務是被人工初始化後連續運行數個小時。它通常讀取數MB到數TB的數據,之後進行轉化或者分析,最後把結果寫回到集羣中。集羣B主要用於處理當前的生產數據。集羣B的任務持續的時間更長,在很少人工干預的情況下,持續的生成和處理數TB的數據集。在這兩個案例中,一個單獨的”任務”都是指運行在多個機器上的多個進程,它們同時讀取和寫入多個文件。
 

6.2.1 存儲

如上表前五行所描述的,兩個集羣都由上百臺Chunk服務器組成,支持數TB的硬盤空間;兩個集羣雖然都存儲了大量的數據,但是還有剩餘的空間。“已用空間”包含了所有的Chunk副本。實際上所有的文件都複製了三份。因此,集羣實際上各存儲了18TB和52TB的文件數據。

兩個集羣存儲的文件數量都差不多,但是集羣B上有大量的死文件。所謂“死文件”是指文件被刪除了或者是被新版本的文件替換了,但是存儲空間還沒有來得及被回收。由於集羣B存儲的文件較大,因此它的Chunk數量也比較多。

6.2.2 元數據

Chunk服務器總共保存了十幾GB的元數據,大多數是來自用戶數據的、64KB大小的塊的Checksum。保存在Chunk服務器上其它的元數據是Chunk的版本號信息,我們在4.5節描述過。

在Master服務器上保存的元數據就小的多了,大約只有數十MB,或者說平均每個文件100字節的元數據。這和我們設想的是一樣的,Master服務器的內存大小在實際應用中並不會成爲GFS系統容量的瓶頸。大多數文件的元數據都是以前綴壓縮模式存放的文件名。Master服務器上存放的其它元數據包括了文件的所有者和權限、文件到Chunk的映射關係,以及每一個Chunk的當前版本號。此外,針對每一個Chunk,我們都保存了當前的副本位置以及對它的引用計數,這個引用計數用於實現寫時拷貝(alex注:即COW,copy-on-write)。

對於每一個單獨的服務器,無論是Chunk服務器還是Master服務器,都只保存了50MB到100MB的元數據。因此,恢復服務器是非常快速的:在服務器響應客戶請求之前,只需要花幾秒鐘時間從磁盤上讀取這些數據就可以了。不過,Master服務器會持續顛簸一段時間–通常是30到60秒–直到它完成輪詢所有的Chunk服務器,並獲取到所有Chunk的位置信息。

6.2.3 讀寫速率

 
表三顯示了不同時段的讀寫速率。在測試的時候,這兩個集羣都運行了一週左右的時間。(這兩個集羣最近都因爲升級新版本的GFS重新啓動過了)。
 
集羣重新啓動後,平均寫入速率小於30MB/s。當我們提取性能數據的時候,集羣B正進行大量的寫入操作,寫入速度達到了100MB/s,並且因爲每個Chunk都有三個副本的原因,網絡負載達到了300MB/s。
讀取速率要比寫入速率高的多。正如我們設想的那樣,總的工作負載中,讀取的比例遠遠高於寫入的比例。兩個集羣都進行着繁重的讀取操作。特別是,集羣A在一週時間內都維持了580MB/s的讀取速度。集羣A的網絡配置可以支持750MB/s的速度,顯然,它有效的利用了資源。集羣B支持的峯值讀取速度是1300MB/s,但是它的應用只用到了380MB/s。 

6.2.4 Master服務器的負載

表3的數據顯示了發送到Master服務器的操作請求大概是每秒鐘200到500個。Master服務器可以輕鬆的應付這個請求速度,所以Master服務器的處理能力不是系統的瓶頸。
 
在早期版本的GFS中,Master服務器偶爾會成爲瓶頸。它大多數時間裏都在順序掃描某個很大的目錄(包含數萬個文件)去查找某個特定的文件。因此我們修改了Master服務器的數據結構,通過對名字空間進行二分查找來提高效率。現在Master服務器可以輕鬆的每秒鐘進行數千次文件訪問。如果有需要的話,我們可以通過在名稱空間數據結構之前設置名稱查詢緩衝的方式進一步提高速度。

6.2.5 恢復時間

當某個Chunk服務器失效了,一些Chunk副本的數量可能會低於複製因子指定的數量,我們必須通過克隆副本使Chunk副本數量達到複製因子指定的數量。恢復所有Chunk副本所花費的時間取決於資源的數量。在我們的試驗中,我們把集羣B上的一個Chunk服務器Kill掉。這個Chunk服務器上大約有15000個Chunk,共計600GB的數據。爲了減小克隆操作對正在運行的應用程序的影響,以及爲GFS調度決策提供修正空間,我們缺省的把集羣中併發克隆操作的數量設置爲91個(Chunk服務器的數量的40%),每個克隆操作最多允許使用的帶寬是6.25MB/s(50mbps)。所有的Chunk在23.2分鐘內恢復了,複製的速度高達440MB/s。

 

在另外一個測試中,我們Kill掉了兩個Chunk服務器,每個Chunk服務器大約有16000個Chunk,共計660GB的數據。這兩個故障導致了266個Chunk只有單個副本。這266個Chunk被GFS優先調度進行復制,在2分鐘內恢復到至少有兩個副本;現在集羣被帶入到另外一個狀態,在這個狀態下,系統可以容忍另外一個Chunk服務器失效而不丟失數據。

6.3 工作負荷分析(Workload Breakdown)

本節中,我們展示了對兩個GFS集羣工作負載情況的詳細分析,這兩個集羣和6.2節中的類似,但是不完全相同。集羣X用於研究和開發,集羣Y用於生產數據處理。 

6.3.1 方法論和注意事項

本章節列出的這些結果數據只包括客戶機發起的原始請求,因此,這些結果能夠反映我們的應用程序對GFS文件系統產生的全部工作負載。它們不包含那些爲了實現客戶端請求而在服務器間交互的請求,也不包含GFS內部的後臺活動相關的請求,比如前向轉發的寫操作,或者重新負載均衡等操作。

 

我們從GFS服務器記錄的真實的RPC請求日誌中推導重建出關於IO操作的統計信息。例如,GFS客戶程序可能會把一個讀操作分成幾個RPC請求來提高並行度,我們可以通過這些RPC請求推導出原始的讀操作。因爲我們的訪問模式是高度程式化,所以我們認爲任何不符合的數據都是誤差(alex注:Since our access patterns are highly stylized, we expect any error to be in the noise)。應用程序如果能夠記錄更詳盡的日誌,就有可能提供更準確的診斷數據;但是爲了這個目的去重新編譯和重新啓動數千個正在運行的客戶機是不現實的,而且從那麼多客戶機上收集結果也是個繁重的工作。

 

應該避免從我們的工作負荷數據中過度的歸納出普遍的結論(alex注:即不要把本節的數據作爲基礎的指導性數據)。因爲Google完全控制着GFS和使用GFS的應用程序,所以,應用程序都針對GFS做了優化,同時,GFS也是爲了這些應用程序而設計的。這樣的相互作用也可能存在於一般程序和文件系統中,但是在我們的案例中這樣的作用影響可能更顯著。

6.3.2 Chunk服務器工作負荷

表4顯示了操作按涉及的數據量大小的分佈情況。讀取操作按操作涉及的數據量大小呈現了雙峯分佈。小的讀取操作(小於64KB)一般是由查找操作的客戶端發起的,目的在於從巨大的文件中查找小塊的數據。大的讀取操作(大於512KB)一般是從頭到尾順序的讀取整個文件。

在集羣Y上,有相當數量的讀操作沒有返回任何的數據。在我們的應用中,尤其是在生產系統中,經常使用文件作爲生產者-消費者隊列。生產者並行的向文件中追加數據,同時,消費者從文件的尾部讀取數據。某些情況下,消費者讀取的速度超過了生產者寫入的速度,這就會導致沒有讀到任何數據的情況。集羣X通常用於短暫的數據分析任務,而不是長時間運行的分佈式應用,因此,集羣X很少出現這種情況。

寫操作按數據量大小也同樣呈現爲雙峯分佈。大的寫操作(超過256KB)通常是由於Writer使用了緩存機制導致的。Writer緩存較小的數據,通過頻繁的Checkpoint或者同步操作,或者只是簡單的統計小的寫入(小於64KB)的數據量(alex注:即彙集多次小的寫入操作,當數據量達到一個閾值,一次寫入),之後批量寫入

再來觀察一下記錄追加操作。我們可以看到集羣Y中大的記錄追加操作所佔比例比集羣X多的多,這是因爲集羣Y用於我們的生產系統,針對GFS做了更全面的調優。

 
表5顯示了按操作涉及的數據量的大小統計出來的總數據傳輸量。在所有的操作中,大的操作(超過256KB)佔據了主要的傳輸量。小的讀取(小於64KB)雖然傳輸的數據量比較少,但是在讀取的數據量中仍佔了相當的比例,這是因爲在文件中隨機Seek的工作負荷而導致的。

 

6.3.3 記錄追加 vs. 寫操作

記錄追加操作在我們的生產系統中大量使用。對於集羣X,記錄追加操作和普通寫操作的比例按照字節比是108:1,按照操作次數比是8:1。對於作爲我們的生產系統的集羣Y來說,這兩個比例分別是3.7:1和2.5:1。更進一步,這一組數據說明在我們的兩個集羣上,記錄追加操作所佔比例都要比寫操作要大。對於集羣X,在整個測量過程中,記錄追加操作所佔比率都比較低,因此結果會受到一兩個使用某些特定大小的buffer的應用程序的影響。
 

如同我們所預期的,我們的數據修改操作主要是記錄追加操作而不是覆蓋方式的寫操作。我們測量了第一個副本的數據覆蓋寫的情況。這近似於一個客戶機故意覆蓋剛剛寫入的數據,而不是增加新的數據。對於集羣X,覆蓋寫操作在寫操作所佔據字節上的比例小於0.0001%,在所佔據操作數量上的比例小於0.0003%。對於集羣Y,這兩個比率都是0.05%。雖然這只是某一片斷的情況,但是仍然高於我們的預期。這是由於這些覆蓋寫的操作,大部分是由於客戶端在發生錯誤或者超時以後重試的情況。這在本質上應該不算作工作負荷的一部分,而是重試機制產生的結果。

6.3.4 Master的工作負荷

表6顯示了Master服務器上的請求按類型區分的明細表。大部分的請求都是讀取操作查詢Chunk位置信息(FindLocation)、以及修改操作查詢lease持有者的信息(FindLease-Locker)。
 
集羣X和Y在刪除請求的數量上有着明顯的不同,因爲集羣Y存儲了生產數據,一般會重新生成數據以及用新版本的數據替換舊有的數據。數量上的差異也被隱藏在了Open請求中,因爲舊版本的文件可能在以重新寫入的模式打開時,隱式的被刪除了(類似UNIX的open函數中的“w”模式)。
 
FindMatchingFiles是一個模式匹配請求,支持“ls”以及其它類似的文件系統操作。不同於Master服務器的其它請求,它可能會檢索namespace的大部分內容,因此是非常昂貴的操作。集羣Y的這類請求要多一些,因爲自動化數據處理的任務進程需要檢查文件系統的各個部分,以便從全局上了解應用程序的狀態。與之不同的是,集羣X的應用程序更加傾向於由單獨的用戶控制,通常預先知道自己所需要使用的全部文件的名稱。

7. 經驗

在建造和部署GFS的過程中,我們經歷了各種各樣的問題,有些是操作上的,有些是技術上的。
 
起初,GFS被設想爲我們的生產系統的後端文件系統。隨着時間推移,在GFS的使用中逐步的增加了對研究和開發任務的支持。我們開始增加一些小的功能,比如權限和配額,到了現在,GFS已經初步支持了這些功能。雖然我們生產系統是嚴格受控的,但是用戶層卻不總是這樣的。需要更多的基礎架構來防止用戶間的相互干擾。
 
我們最大的問題是磁盤以及和Linux相關的問題。很多磁盤都聲稱它們支持某個範圍內的Linux IDE硬盤驅動程序,但是實際應用中反映出來的情況卻不是這樣,它們只支持最新的驅動。因爲協議版本很接近,所以大部分磁盤都可以用,但是偶爾也會有由於協議不匹配,導致驅動和內核對於驅動器的狀態判斷失誤。這會導致數據因爲內核中的問題意外的被破壞了。這個問題促使我們使用Checksum來校驗數據,同時我們也修改內核來處理這些因爲協議不匹配帶來的問題。
 
較早的時候,我們在使用Linux 2.2內核時遇到了些問題,主要是fsync()的效率問題。它的效率與文件的大小而不是文件修改部分的大小有關。這在我們的操作日誌文件過大時給出了難題,尤其是在我們尚未實現Checkpoint的時候。我們費了很大的力氣用同步寫來解決這個問題,但是最後還是移植到了Linux2.4內核上。
 
另一個和Linux相關的問題是單個讀寫鎖的問題,也就是說,在某一個地址空間的任意一個線程都必須在從磁盤page in(讀鎖)的時候先hold住,或者在mmap()調用(寫鎖)的時候改寫地址空間。我們發現即使我們的系統負載很輕的情況下也會有偶爾的超時,我們花費了很多的精力去查找資源的瓶頸或者硬件的問題。最後我們終於發現這個單個鎖在磁盤線程交換以前映射的數據到磁盤的時候,鎖住了當前的網絡線程,阻止它把新數據映射到內存。由於我們的性能主要受限於網絡接口,而不是內存copy的帶寬,因此,我們用pread()替代mmap(),用了一個額外的copy動作來解決這個問題。
 
儘管偶爾還是有其它的問題,Linux的開放源代碼還是使我們能夠快速探究和理解系統的行爲。在適當的時候,我們會改進內核並且和公開源碼組織共享這些改動。

8. 相關工作

和其它的大型分佈式文件系統,比如AFS[5]類似,GFS提供了一個與位置無關的名字空間,這使得數據可以爲了負載均衡或者災難冗餘等目的在不同位置透明的遷移。不同於AFS的是,GFS把文件分佈存儲到不同的服務器上,這種方式更類似Xfs[1]和Swift[3],這是爲了提高整體性能以及災難冗餘的能力。
 
由於磁盤相對來說比較便宜,並且複製的方式比RAID[9]方法簡單的多,GFS目前只使用複製的方式來進行冗餘,因此要比xFS或者Swift佔用更多的裸存儲空間(alex注:Raw storage,裸盤的空間)。
 
與AFS、xFS、Frangipani[12]以及Intermezzo[6]等文件系統不同的是,GFS並沒有在文件系統層面提供任何Cache機制。我們主要的工作在單個應用程序執行的時候幾乎不會重複讀取數據,因爲它們的工作方式要麼是流式的讀取一個大型的數據集,要麼是在大型的數據集中隨機Seek到某個位置,之後每次讀取少量的數據。
 
某些分佈式文件系統,比如Frangipani、xFS、Minnesota’s GFS[11]、GPFS[10],去掉了中心服務器,只依賴於分佈式算法來保證一致性和可管理性。我們選擇了中心服務器的方法,目的是爲了簡化設計,增加可靠性,能夠靈活擴展。特別值得一提的是,由於處於中心位置的Master服務器保存有幾乎所有的Chunk相關信息,並且控制着Chunk的所有變更,因此,它極大地簡化了原本非常複雜的Chunk分配和複製策略的實現方法。我們通過減少Master服務器保存的狀態信息的數量,以及將Master服務器的狀態複製到其它節點來保證系統的災難冗餘能力。擴展能力和高可用性(對於讀取)目前是通過我們的影子Master服務器機制來保證的。對Master服務器狀態更改是通過預寫日誌的方式實現持久化。爲此,我們可以調整爲使用類似Harp[7]中的primary-copy方案,從而提供比我們現在的方案更嚴格的一致性保證。
 
我們解決了一個難題,這個難題類似Lustre[8]在如何在有大量客戶端時保障系統整體性能遇到的問題。不過,我們通過只關注我們的應用程序的需求,而不是提供一個兼容POSIX的文件系統,從而達到了簡化問題的目的。此外,GFS設計預期是使用大量的不可靠節點組建集羣,因此,災難冗餘方案是我們設計的核心。
 
GFS很類似NASD架構[4]。NASD架構是基於網絡磁盤的,而GFS使用的是普通計算機作爲Chunk服務器,就像NASD原形中方案一樣。所不同的是,我們的Chunk服務器採用惰性分配固定大小的Chunk的方式,而不是分配變長的對象存儲空間。此外,GFS實現了諸如重新負載均衡、複製、恢復機制等等在生產環境中需要的特性。
 
不同於與Minnesota’s GFS和NASD,我們並不改變存儲設備的Model(alex注:對這兩個文件系統不瞭解,因爲不太明白改變存儲設備的Model用來做什麼,這不明白這個model是模型、還是型號)。我們只關注用普通的設備來解決非常複雜的分佈式系統日常的數據處理。
 
我們通過原子的記錄追加操作實現了生產者-消費者隊列,這個問題類似River[2]中的分佈式隊列。River使用的是跨主機的、基於內存的分佈式隊列,爲了實現這個隊列,必須仔細控制數據流;而GFS採用可以被生產者併發追加記錄的持久化的文件的方式實現。River模式支持m-到-n的分佈式隊列,但是缺少由持久化存儲提供的容錯機制,GFS只支持m-到-1的隊列。多個消費者可以同時讀取一個文件,但是它們輸入流的區間必須是對齊的。

9. 結束語

Google文件系統展示了一個使用普通硬件支持大規模數據處理的系統的特質。雖然一些設計要點都是針對我們的特殊的需要定製的,但是還是有很多特性適用於類似規模的和成本的數據處理任務。
 
首先,我們根據我們當前的和可預期的將來的應用規模和技術環境來評估傳統的文件系統的特性。我們的評估結果將我們引導到一個使用完全不同於傳統的設計思路上。根據我們的設計思路,我們認爲組件失效是常態而不是異常,針對採用追加方式(有可能是併發追加)寫入、然後再讀取(通常序列化讀取)的大文件進行優化,以及擴展標準文件系統接口、放鬆接口限制來改進整個系統。
 
我們系統通過持續監控,複製關鍵數據,快速和自動恢復提供災難冗餘。Chunk複製使得我們可以對Chunk服務器的失效進行容錯。高頻率的組件失效要求系統具備在線修復機制,能夠週期性的、透明的修復損壞的數據,也能夠第一時間重新建立丟失的副本。此外,我們使用Checksum在磁盤或者IDE子系統級別檢測數據損壞,在這樣磁盤數量驚人的大系統中,損壞率是相當高的。
 
我們的設計保證了在有大量的併發讀寫操作時能夠提供很高的合計吞吐量。我們通過分離控制流和數據流來實現這個目標,控制流在Master服務器處理,而數據流在Chunk服務器和客戶端處理。當一般的操作涉及到Master服務器時,由於GFS選擇的Chunk尺寸較大(alex注:從而減小了元數據的大小)以及通過Chunk Lease將控制權限移交給主副本,這些措施將Master服務器的負擔降到最低。這使得一個簡單、中心的Master不會成爲成爲瓶頸。我們相信我們對網絡協議棧的優化可以提升當前對於每客戶端的寫入吞吐量限制。
 
GFS成功的實現了我們對存儲的需求,在Google內部,無論是作爲研究和開發的存儲平臺,還是作爲生產系統的數據處理平臺,都得到了廣泛的應用。它是我們持續創新和處理整個WEB範圍內的難題的一個重要工具。

致謝

We wish to thankt he following people for their contributions to the system or the paper. Brain Bershad (our shepherd) and the anonymous reviewers gave us valuable comments and suggestions. Anurag Acharya, Jeff Dean, and David des-Jardins contributed to the early design. Fay Chang worked on comparison of replicas across chunkservers. Guy Edjlali worked on storage quota. Markus Gutschke worked on a testing frameworkan d security enhancements. David
Kramer worked on performance enhancements. Fay Chang, Urs Hoelzle, Max Ibel, Sharon Perl, Rob Pike, and Debby Wallach commented on earlier drafts of the paper. Many of our colleagues at Google bravely trusted their data to a new file system and gave us useful feedback. Yoshka helped with early testing.

參考

[1] Thomas Anderson, Michael Dahlin, Jeanna Neefe, David Patterson, Drew Roselli, and Randolph Wang. Serverless networkfil e systems. In Proceedings of the 15th ACM Symposium on Operating System Principles, pages 109–126, Copper Mountain Resort, Colorado, December 1995.
[2] Remzi H. Arpaci-Dusseau, Eric Anderson, Noah Treuhaft, David E. Culler, Joseph M. Hellerstein, David Patterson, and Kathy Yelick. Cluster I/O with River: Making the fast case common. In Proceedings of the Sixth Workshop on Input/Output in Parallel and Distributed Systems (IOPADS ’99), pages 10–22, Atlanta, Georgia, May 1999.
[3] Luis-Felipe Cabrera and Darrell D. E. Long. Swift: Using distributed disks triping to provide high I/O data rates. Computer Systems, 4(4):405–436, 1991.


[4] Garth A. Gibson, David F. Nagle, Khalil Amiri, Jeff Butler, Fay W. Chang, Howard Gobioff, Charles Hardin, ErikR iedel, David Rochberg, and Jim Zelenka. A cost-effective, high-bandwidth storage architecture. In Proceedings of the 8th Architectural Support for Programming Languages and Operating Systems, pages 92–103, San Jose, California, October 1998.

 
[5] John Howard, Michael Kazar, Sherri Menees, David Nichols, Mahadev Satyanarayanan, Robert Sidebotham, and Michael West. Scale and performance in a distributed file system. ACM Transactions on Computer Systems, 6(1):51–81, February 1988.
[6] InterMezzo. http://www.inter-mezzo.org, 2003.
 
[7] Barbara Liskov, Sanjay Ghemawat, Robert Gruber, Paul Johnson, Liuba Shrira, and Michael Williams. Replication in the Harp file system. In 13th Symposium on Operating System Principles, pages 226–238, Pacific Grove, CA, October 1991.
[8] Lustre. http://www.lustreorg, 2003.
 
[9] David A. Patterson, Garth A. Gibson, and Randy H. Katz. A case for redundant arrays of inexpensive disks (RAID). In Proceedings of the 1988 ACM SIGMOD International Conference on Management of Data, pages 109–116, Chicago, Illinois, September 1988.
[10] FrankS chmuck and Roger Haskin. GPFS: A shared-diskfi le system for large computing clusters. In Proceedings of the First USENIX Conference on File and Storage Technologies, pages 231–244, Monterey, California, January 2002.
 
[11] Steven R. Soltis, Thomas M. Ruwart, and Matthew T.O’Keefe. The Gobal File System. In Proceedings of the Fifth NASA Goddard Space Flight Center Conference on Mass Storage Systems and Technologies, College Park, Maryland, September 1996.
[12] Chandramohan A. Thekkath, Timothy Mann, and Edward K. Lee. Frangipani: A scalable distributed file system. In Proceedings of the 16th ACM Symposium on Operating System Principles, pages 224–237, Saint-Malo, France, October 1997
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章