谷歌三大論文之the Google File System

The Google File System 中文版

摘要

我們設計並實現了Google文件系統,一個面向分佈式數據密集型應用的、可伸縮的分佈式文件系統。雖然運行在廉價的日用硬件設備上,但是它依然了提供容錯功能,爲大量客戶機提供了很高的總體性能。

雖然與很多之前的分佈式文件系統有很多相同目標,但是,我們的設計已經受應用的負載情況和技術環境影響,現在以及可預見的將來都反映出,我們的設計和早期的分佈式文件系統的設想有了顯著的分離。這讓我們重新審視了傳統文件系統在設計上的選擇,探索徹底不同的設計點。

GFS成功滿足了我們的存儲需求。其作爲存儲平臺被廣泛的部署在Google內部,該平臺用來產生和處理數據,這些數據被我們的服務以及需要大規模數據集的研究和開發工作使用。迄今爲止,最大的一個集羣利用一千多臺機器上的數千個硬盤,提供數百TB的存儲空間,同時被數百個客戶機訪問。

在本論文中,我們展示了設計用來支持分佈式應用的文件系統接口的擴展,討論我們設計的許多方面,最後對小規模基準測試和真實使用作了測量報告。

常用術語

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

關鍵詞

容錯,可伸縮性,數據存儲,集羣存儲

1.     簡介

爲了滿足Google迅速增長的數據處理需求,我們設計並實現了Google文件系統(Google File SystemGFS)GFS與之前的分佈式文件系統有着很多相同的目標,比如,性能、擴展性、可靠性以及可用性。但是,我們的設計還受對我們的應用的負載和技術環境的觀察的影響,現在以及可預見的將來都反映出,我們的設計和早期的分佈式文件系統的設想有了顯著的分離。這讓我們重新審視了傳統文件系統在設計上的選擇,在設計上探索了徹底不同的設計點。

首先,組件失效被認爲是常態事件,而不是意外事件。文件系統由幾百乃至數千臺由廉價的日常部件組裝成的存儲機器組成,同時被相當數量的客戶機訪問。部件的數量和質量事實保證了任意給定時間,一些部件無法工作,一些部件無法從它們目前的失效狀態中恢復。我們遇到過如下原因導致的問題,比如應用程序bug、操作系統的bug、人爲失誤,甚至還有硬盤、內存、連接器、網絡以及電源失效。因此,持久的監控、錯誤偵測、容錯以及自動恢復必須集成在系統中。

其次,以傳統的標準衡量,我們的文件非常巨大。數GB的文件非常普遍。每個文件通常包含許多應用程序對象,比如web文檔。當我們定期由數億個對象構成的快速增長的數TB的數據集時,即使文件系統支持,管理數十億KB大小的小文件也是不實用的。因此,設計的假設條件和參數,比如I/O 操作和Block的尺寸都不得不重新考慮。

第三,絕大部分文件的變更是採用在追加新數據,而不是重寫原有數據的方式。文件內部的隨機寫在實際中幾乎不存在。一旦寫完之後,文件只能讀,而且通常只能順序讀。各種數據符合這些特性,比如:一些可能組成了數據分析程序掃描的超大數據集;一些可能是正在運行的應用程序生成的連續數據流;一些可能是檔案數據;一些可能是由一臺機器生成、另外一臺機器處理的中間數據,同時處理或者稍後適時處理。考慮到這種針對海量文件的訪問模式,數據的追加是性能優化和原子性保證的焦點所在,客戶端對數據塊緩存毫無吸引力。

第四,通過增加靈活性,應用程序和文件系統API的協同設計對整個系統有益。比如,我們放鬆了對GFS一致性模型的要求,不用在應用程序中強加繁重負擔,大大簡化了文件系統。我們甚至引入了原子性的追加操作,這樣多個客戶端可以對一個文件同時進行追加操作,不需要他倆之間額外的同步機制。這些問題會在下文進行詳細討論。

當前針對不同目的部署了多重GFS集羣。最大的一個集羣擁有超過1000個存儲節點,超過300TB的硬盤存儲,被不同機器上的數百個客戶端連續不斷的頻繁訪問。

2.設計概述

2.1 假設

在設計滿足我們需求的文件系統時候,我們被這種現實指引着:我們的假設既有機遇、又有挑戰。我們之前提到一些關鍵關注點,現在更詳細地展示我們的假設。

系統由許多廉價的日用組件組成,組件失效是一種常態。系統必須持續監控自身狀態,探測,處理容錯並且迅速地恢復失效的組件。

系統存儲一定數量的大文件。我們預期會有幾百萬個文件,文件的大小通常在100MB或者以上。數個GB大小的文件也是普遍存在,並且應該被被有效的管理。系統也必須支持小文件,但是不需要針對小文件做優化。

系統的工作負載主要由兩種讀操作組成:大規模的流式讀取和小規模的隨機讀取。大規模的流式讀取中,單次操作通常讀取數百KB的數據,更常見的是一次讀取1MB甚至更多的數據。來自同一個客戶機的連續操作通常是讀取一個文件中一個連續區域。小規模的隨機讀取通常是在任意位移上讀取幾個KB數據。對性能敏感的應用程序通常把小規模的隨機讀取分批處理並排序,如此在文件中穩固前進而不是往復來去。

系統的工作負載還包括許多大規模的、順序的、對文件追加數據的寫操作。通常操作的大小和讀操作類似。數據一旦被寫入後,文件很少被再次變更。系統支持在文件任意位置寫入的小型操作,但是這種操作不必高效。

系統必須高效的、定義明確的(alex注:well-defined)實現多客戶端並行追加數據到同一個文件裏的語意。我們的文件通常作爲“生產者-消費者”隊列,或者爲多路合併使用。數百個生產者,每臺機器上運行一個,併發地對一個文件進行追加。最小同步開銷原子性是必要的。文件可以在稍後讀取,或者是消費者在追加同時讀取文件。

高可持續的網絡帶寬比低延遲更重要。我們的大多數目標程序很看重高速率大批量地處理數據,極少有程序對單一的讀寫操作有嚴格的響應時間要求。

2.2 接口

GFS 提供了一套常見的文件系統接口,儘管它沒有實現像POSIX的一套標準API。文件以目錄的形式分層組織,用路徑名標識。我們支持常用的操作,如創建、刪除、打開、關閉、讀寫文件。

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

2.3 架構

一個GFS 集羣包含一個單獨的masteralex注:這裏的一個單獨的master節點的含義是GFS系統中只存在一個邏輯上的master組件。後面我們還會提到master節點複製,因此,爲了理解方便,我們把master節點視爲一個邏輯上的概念,一個邏輯的master節點包括兩臺物理主機,即兩臺master服務器)和多臺chunkserver,並且被多個客戶端訪問,如圖1 所示。其中的每臺機器通常都是運行着用戶級別(user-level)的服務器進程的日常Linux機器。我們可以很容易地把chunk服務器和客戶端都運行在同一臺機器上,前提是機器資源允許,並且運行古怪應用程序代碼而引起的不可靠性是可以接受的。

文件都被分割成固定大小的塊。在創建塊的時候,master會對每個塊分配一個不可變的、全球唯一的64位的塊句柄來進行標識。Chunkserver將塊作爲linux 文件保存在本地硬盤上,並且根據指定的塊句柄和字節範圍來讀寫塊數據。爲了可靠,每個塊都會複製到多個chunkserver上。缺省情況下,我們存儲三個副本,不過用戶可以爲不同的文件命名空間區域指定不同的複製級別。

Master維護所有的文件系統元數據。這些元數據包括命名空間、訪問控制信息、文件到塊的映射信息、以及當前塊的位置。Master節點還控制着系統範圍內的活動,比如,塊租用管理(alex注:BDB也有關於lease的描述,不知道是否相同) 、孤立塊的垃圾回收、以及塊在Chunkserver間的遷移。Master用心跳信息週期地和每個Chunkserver通訊,向Chunkserver發送指令並收集其狀態。

鏈接到每個應用程序裏的GFS客戶端代碼實現了文件系統API,代表應用程序與masterChunkserver通訊以及讀寫數據。客戶端和master交互元數據操作,但是所有承載數據的通信都是直接和Chunkserver進行的。我們不提供POSIX標準的API,因此不需要在Linux vnode層建立鉤子程序。

無論是客戶端還是Chunkserver都不需要緩存文件數據。客戶端緩存收效甚微,因爲大多數程序要麼流式處理一個巨大文件,要麼工作集太大根本無法緩存。通過消除緩存相關的問題簡化了客戶端和整個系統。(然而,客戶端會緩存元數據。)Chunkserver不需要緩存文件數據的原因是,數據塊以本地文件的方式保存,Linux 緩衝區已經把經常訪問的數據緩存在了內存中。

2.4 單一Master節點

單一的master節點極大地簡化了我們的設計。並且使master可以使用全局信息(knowledge)進行復雜的塊部署和副本決策。然而,我們必須使其與讀寫的相關性降到最小,以避免其成爲瓶頸。客戶端絕不通過master讀寫文件數據。相反,客戶端詢問master它應該聯繫哪一個Chunkserver。客戶端將這些信息緩存有限的時間,後續的操作直接和Chunkserver進行交互。

我們參考圖1解釋一次簡單讀取交互。首先,客戶端使用固定的塊大小將應用程序指定的文件名和字節偏移轉換成文件中的塊索引。然後,它將包含文件名和塊索引的請求發給masterMaster回覆相應的塊句柄和副本位置信息。客戶端使用文件名和Chunk索引作爲鍵緩存這些信息。

之後客戶端發送請求到其中的一個副本處,一般是最近的。請求信息指定了Chunk句柄和塊內字節範圍。對同一個塊的進一步讀取不再需要客戶端和master的交互了,知道緩存信息過期或者文件被重新打開。實際上,客戶端通常會在一次請求中請求多個塊,master也可能立刻包含了那些請求之後的塊信息。這些額外的信息在實際上沒有任何額外代價的情況下,迴避了客戶端和master未來一些可能的交互。

2.5 Chunk尺寸

塊的大小是關鍵的設計參數之一。我們選擇了64MB,這個尺寸遠遠大於通常文件系統的塊大小。每個塊副本都以普通Linux文件的形式保存在Chunkserver上,只有在需要的時候才擴大。惰性空間分配策略避免了由於內部碎片導致的空間浪費,也許這是對這麼大的塊尺寸最有爭議的地方。

大尺寸塊有幾個重要的優勢。首先,它減少了客戶端和master交互的需求,因爲對同一塊的讀寫只需要一次和mater的初始請求來獲取塊的位置信息。這種縮減我們的工作負載至關重要,因爲應用程序大都是連續讀寫大文件。即使是小規模的隨機讀取,客戶端可以輕鬆地爲一個數TB的工作集緩存所有的塊位置信息。其次,因爲採用較大塊,客戶端很可能對一個塊執行多次操作,這樣可以通過與Chunkserver在很長的時間內保持持久的TCP 連接來減少網絡負載。再者,它減少了在master上保存的元數據的大小。這就允許我們把元數據保存在內存中,這也帶來了其它優勢,我們將在在2.6.1節進行討論。

另一方面,即使配合惰性空間分配,大尺寸塊也有其缺點。小文件包含少量的塊,甚至只有一個塊。如果許多客戶端訪問同一個小文件,存儲這些塊的Chunkserver就會變成熱點。在實際中,由於我們的程序通常是連續的讀取包含多個塊的大文件,熱點還不是主要問題。

然而,當GFS第一次被批處理隊列系統使用的時候,熱點問題還是顯露了:一個可執行文件作爲一個單塊文件寫在了GFS上,之後同時在數百臺機器上啓動。存放這個可執行文件的幾個Chunkserver被數百個併發請求造成過載。我們通過使用更大的複製因子來保存這樣的可執行文件,並且讓批處理隊列系統錯開程序的啓動時間的方法解決了這個問題。一個可能的長效解決方案是,在這種的情況下允許客戶端從其它客戶端讀取數據(p2p?)。

2.6 元數據

Masteralex注:注意邏輯的master節點和物理的master服務器的區別。後續我們談的是每個master服務器的行爲,如存儲、內存等等,因此我們將全部使用物理名稱)存儲3種主要類型的元數據:文件和塊的命名空間、文件到塊k的映射、每個塊副本的位置。所有的元數據都保存在master的內存中。前兩種類型(命名空間、文件到塊的映射)同時也會通過記錄變更到操作日誌的方式持久存儲,該日誌存儲在master的本地磁盤上,並且在遠程機器上備份。使用日誌使我們能夠簡單可靠地更新master的狀態,並且不用冒着萬一master崩潰數據不一致的風險。Master不會持久存儲塊位置信息。相反,Master會在啓動,或者有新的Chunkserver加入集羣時,向各個Chunkserver詢問它們的塊信息。

2.6.1 內存中的數據結構

因爲元數據保存在內存中,master的操作非常快。並且,Master在後臺週期性掃描自己的整個狀態簡單高效。這種週期性的掃描用於實現塊垃圾收集、在Chunkserver失效時重新備份、通過塊遷移來均衡跨Chunkserver的負載及磁盤使用狀況。4.34.4章節將進一步討論這些行爲。

對於這種memory-only式的方法的一個潛在擔心在於:塊的數量亦即整個系統的容量受限於master所擁有的內存大小。這在實際應用中並不是一個嚴重的限制。Master爲每個64M數據塊維護少於64個字節的元數據就。由於大多數文件包含多個塊,因此大多數塊是滿的,除了最後一個塊是部分填充的。同樣,每個文件需要的文件命名空間數據通常少於64字節,因爲它使用前綴壓縮緊密地存儲文件名字。

如果有必要支持更大的文件系統,相比於通過在內存中存儲元數據而獲得的簡潔性、可靠性、高性能和靈活性而言,爲master增加額外內存的費用也是很少的。

2.6.2 Chunk 位置信息

Master不保存哪個Chunkserver擁有指定塊的副本的持久化記錄。Master只是在啓動的時候輪詢Chunkserver以獲取那些信息。Master可以保持自己是最新的(thereafter),因爲它控制了所有的塊的部署,而且通過定期心跳信息監控Chunkserver的狀態。

我們最初試圖把塊的位置信息持久地保存在master上,但是我們認定在啓動的時候從Chunkserver請求數據,之後定期輪詢更簡單。這樣消除了在Chunkserver加入、離開集羣、更名、失效、以及重啓等情況發生的時候,保持masterChunkserver同步的問題。在一個擁有數百臺服務器的集羣中,這類事件會頻繁的發生。

理解這個設計決策的另外一種方式就是認識到:對於塊是否在它的硬盤上,Chunkserver才說了算。試圖在master上維護一個這些信息的一致視圖是毫無意義的,因爲Chunkserver上的錯誤可能會導致塊不由自主地消失(比如,硬盤可能損壞不能用了),或者操作人員可能會重命名一個Chunkserver

2.6.3 操作日誌

操作日誌包含了關鍵的元數據變更歷史記錄。這對GFS 非常重要。不僅是因爲它是元數據的唯一持久化記錄,它也作爲定義同步操作順序的邏輯時間基線(alex注:也就是通過邏輯日誌的序號作爲操作發生的邏輯時間,類似於事務系統中的LSN)。文件和塊,連同它們的版本(參考4.5),都由它們創建時的邏輯時間唯一的、永久的標識。

由於操作日誌至關重要,我們必須對其可靠地存儲,並且只有在元數據的變化被持久化後,這種變化對客戶纔是可見的。否則,即使塊本身沒有問題,我們(effectively)有效地丟失整個文件系統或者最近的客戶操作。因此,我們會把日誌複製到多臺遠程機器,並且只有把相應的日誌記錄寫入到本地以及遠程硬盤之後,纔會響應客戶端的操作請求。Master會在寫入之前將一些日誌記錄分批處理,從而減少寫入和備份對整體系統吞吐量的影響。

Master通過重演操作日誌恢復它的文件系統狀態。爲了縮短啓動時間,我們必須保持日誌很小(alex注:即重演系統操作的日誌量儘量的少)。每當日誌增長到超過一定大小的時候master對系統狀態做一次檢查點,(shijinCheckpoint是一種行爲,一種對數據庫狀態作一次快照的行爲,將所有的狀態數據寫入一個Checkpoint文件,並刪除之前的日誌文件)如此一來,通過從本地磁盤加載最新檢查點,然後僅僅重演檢查點之後有限數目日誌記錄的方式,master即可恢復系統。檢查點是一個緊密的類B-樹格式,該格式可以直接映射到內存,可以在無需額外解析的情況下用於命名空間查詢。這進一步提高了恢復速度,增強了可用性。

由於創建一個檢查點需要一定的時間,所以master的內部狀態被組織爲這樣的形式,這種形式可以在不阻塞後續變更操作的同時創建新的檢查點。Master在一個獨立的線程切換到新的日誌文件並創建新的檢查點。新的檢查點包含切換前所有的變更。爲一個有數百萬文件的集羣創建一個檢查點大約需要1 分鐘的間。創建完成後,檢查點被寫入本地和遠程硬盤中。

Master只需要最新的檢查點和後續的日誌文件。舊的檢查點和日誌文件可以被自主刪除,但是爲了提防災難性故障(alex注:catastrophes,數據備份相關文檔中經常會遇到這個詞,表示一種超出預期範圍的災難性事件),我們還是會隨手保存一些。創建檢查點期間的失敗不會影響正確性,因爲恢復代碼檢測並跳過沒完成的檢查點。

2.7 一致性模型

GFS有一個寬鬆的一致性模型,這個模型很好地支撐我們的高度分佈的應用,但是卻依然相當簡單且可以高效實現。現在我們討論GFS的一致性保障及其對應用程序的意義。我們也強調了GFS 如何維護這些保障,但是實現的細節將在本論文的其它部分討論。

2.7.1 GFS一致性保障機制

文件命名空間的變更(例如,文件創建)是原子性的。它們只能由master控制:命名空間鎖保證了原子性和正確性(4.1節);master的操作日誌定義了這些操作的一個全局完整的順序(2.6.3節)。

數據變更後一個文件域(alex注:region這個詞用中文非常難以表達,我認爲應該是變更操作所涉及的文件中的某個範圍)的狀態取決於操作的類型、成功與否、以及是否有同步變更。表1 彙總了結果。如果所有客戶端,無論從哪個副本讀取,總是看到相同的數據,那麼我們認爲文件域是“一致的”;在一個文件數據變更以後,如果文件域是一致的,並且客戶端能夠看到變更寫入的完整內容,那麼這個域是“已定義的”。當一個數據變更操作成功執行,沒有受到同步寫操作的干擾,那麼受影響的域就是已定義的(暗含了一致性):所有的客戶端總是可以看到變更寫入的內容。併發變更操作成功完成之後,域處於一致的、未定義的狀態:所有的客戶端看到同樣的數據,但是它無法反映任何一次變更寫入寫入的數據。通常情況下,文件域包含了來自多個變更的、混雜的數據片段。失敗的變更操作導致這個域不一致(因此也是未定義的):不同的客戶可能在不同的時間會看到不同的數據。下面我們描述了我們的應用程序如何區分已定義和未定義的域。應用程序沒必要進一步區分未定義域的不同類型。

數據變更可能是寫入或者記錄追加。寫入操作把數據寫在應用程序指定的文件偏移位置上。即使在併發變更面前,記錄追加操作至少把數據(記錄)原子性的追加一次,但是是在GFS 選擇的偏移上(3.3節)(alex注:這句話有點費解,其含義是所有的追加寫入都會成功,但是有可能被執行了多次,而且每次追加的文件偏移量由GFS自己計算)。(相比而言,通常的追加不過在這樣一個偏移位置寫,客戶認爲偏移位置是文件的當前的尾部。)GFS返回給客戶端一個偏移量,該偏移量標明瞭包含了記錄的、已定義的域的起點。另外,GFS 可能會在中間插入填充數據或者重複記錄。它們佔據被認定爲不一致的域,並且這些數據和用戶數據相比通常很小。

經過了一系列的成功的變更操作之後,GFS保證被變更的文件域是已定義的,並且包含最後一次變更操作寫入的數據。GFS 通過以下措施達成目的:(a  對塊的所有副本應用相同順序的變更(3.1節),(b)使用塊版本號來探測過期的副本,過期的副本是由於它所在的Chunkserver宕機(4.5章)期間錯過了變更而引起的。過期的副本不會涉及變更,也不會返回給向master請求塊位置信息的客戶端。它們優先被垃圾收集。

由於客戶端緩存塊位置信息,所以在信息刷新前,客戶端有可能從一個失效的副本讀取了數據。這個時間窗口受限於緩存條目的超時時間和文件文件下一次被打開的時間,文件的再次打開會從緩存中清除該文件的塊信息。並且,鑑於我們的大多數文件都是隻進行追加操作,一個失效的副本通常返回一個提前結束的塊而不是過期的數據。當一個Readeralex注:本文中將用到兩個專有名詞,ReaderWriter,分別表示執行GFS讀取和寫入操作的程序)重新嘗試並聯絡master時,它就會立刻得到當前的塊位置信息。

變更操作成功執行很長時間之後,組件的失效仍然可以損壞或者銷燬數據。GFS通過master和所有Chunkserver定期“握手”的方式來識別失效的Chunkserver,並且通過檢查檢驗和來校驗數據是否損壞(5.2節)。一旦問題浮出水面,數據要從效副本快速恢復(4.3節)。只有塊的所有副本在GFS作出反應前全部丟失,該塊纔會不可逆轉的丟失。GFS的反應時間(alex注:指master節點檢測到錯誤並採取應對措施)通常是幾分鐘。即使在這種情況下,塊變得不可用了,而不是損壞了:應用程序會收到清晰的錯誤信息而不是損壞的數據。

2.7.2 程序的實現

GFS 應用程序可以利用一些簡單技術適應這個寬鬆的一致性模型,這些技術已經滿足了其他目的的需要:依賴追加而不是重寫,檢查點,自驗證,自標識的記錄。

實際中,我們所有的應用通過追加而不是重寫的方式變更文件。一種典型的應用中,寫入程序從頭到尾地生成一個文件。寫完所有數據之後,程序原子性地將文件重命名爲一個永久的文件名,或者定期地對成功寫入了多少數據設置檢查點。檢查點也可以包含程序級別的檢驗和。Readers僅校驗並處理上一個檢查點之後的文件域,也就是人們知道的已定義狀態。不管一致性和併發問題的話,該方法對我們很適合。追加比隨機寫更有效率,對程序失敗有更彈性。檢查點允許Writer遞增地重啓,並且防止Reader成功處理從應用程序的角度看來並未完成的寫入的文件數據。

在另一種典型應用中。許多Writer爲了合併結果或者作爲生產者-消費者隊列併發地向一個文件追加數據。記錄追加的“至少追加一次”的語義維持了每個Writer的輸出。Reader使用下面的方法來處理偶然的填充和重複。Writer準備的每條記錄中都包含了類似檢驗和的額外信息,以便用來驗證它的有效性。Reader可以用檢驗和識別和丟棄額外的填充數據和記錄片段。如果偶爾的重複內容是不能容忍的(比如,如果這些重複數據將要觸發非冪等操作),可以用記錄的唯一標識來過濾它們,這些標識符也通常用於命名相應程序實體,例如web文檔。這些記錄I/O功能(除了剔除重複數據)都包含在我們程序共享的代碼庫(library code)中,並且適用於Google內部其它的文件接口實現。這樣,記錄的相同序列,加上些許重複數據,總是被分發到記錄Reader中。

3. 系統交互

我們設計這個系統力圖最小化master與所有操作的牽連。在這樣的背景下,我們現在描述客戶機、masterChunkserver如何交互以實現數據變更、原子記錄追加以及快照功能。

3.1 租約(lease)和變更順序

變更是改變塊內容或者塊元數據的操作,比如寫操作或者追加操作。每次變更在塊所有的副本上執行。我們使用租約(lease)來維護副本間的一致性變更順序。Master向其中一個副本授權一個塊租約,我們把這個副本叫做主副本。主副本爲對塊的所有變更選擇一個序列。應用變更的時候所有副本都遵照這個順序。這樣,全局變更順序首先由master選擇的租約授權順序規定,然後在租約內部由主副本分配的序列號規定。

設計租約機制的目的是爲了最小化master的管理開銷。租約的初始過期時間爲60秒。然而,只要塊正在變更,主副本就可以請求並且通常會得到master無限期的延長。這些延長請求和批准信息附在master和所有Chunkserver之間的定期交換的心跳消息中。Master有時可能試圖在到期前取消租約(例如,當master想令一個在一個重命名的文件上進行的修改失效)。即使master和主副本失去聯繫,它仍然可以安全地在舊的租約到期後和向另外一個副本授權新的租約。

 在圖2 中,我們根據寫操作的控制流程通過這些標號步驟圖示說明了這一過程。

1.客戶機詢問master哪一個Chunkserver持有該塊當前的租約,以及其它副本的位置。如果沒有chunkserver持有租約,master將租約授權給它選擇的副本(沒有展示)。

2master將主副本的標識符以及其它副本(次級副本)的位置返回給客戶機。客戶機爲將來的變更緩存這些數據。只有在主副本不可達,或者其迴應它已不再持有租約的時候,客戶機才需要再一次聯繫master

3.客戶機將數據推送到所有副本。客戶機可以以任意的順序推送數據。Chunkserver將數據存儲在內部LRU 緩存中,直到數據被使用或者過期。通過將數據流和控制流解耦,我們可以基於網絡拓撲而不管哪個Chunksever上有主副本,通過調度昂貴的數據流來提高系統性能。3.2章節會作進一步討論。

4.當所有的副本都確認接收到了數據,客戶機對主副本發送寫請求。這個請求標識了早前推送到所有副本的數據。主副本爲接收到的所有變更分配連續的序列號,由於變更可能來自多個客戶機,這就提供了必要的序列化。它以序列號的順序把變更應用到它自己的本地狀態中(alex注:也就是在本地執行這些操作,這句話按字面翻譯有點費解,也許應該翻譯爲“它順序執行這些操作,並更新自己的狀態”)。

5.主副本將寫請求轉發(forward)到所有的次級副本。每個次級副本依照主副本分配的序列順序應用變更

6.所有次級副本回復主副本並標明它們已經完成了操作。

7.主副本回復客戶機。任何副本遇到的任何錯誤都報告給客戶機。出錯的情況下,寫操作可能在主副本和次級副本的任意子集上執行成功。(如果在主副本失敗,就不會分配序列號和轉發。)客戶端請求被認定爲失敗,被修改的域處於不一致的狀態。我們的客戶機代碼通過重試失敗的變更來處理這樣的錯誤。在退到從頭開始重試之前,客戶機會將從步驟(3)到步驟(7)做幾次嘗試。

如果應用程序一次的寫入量很大,或者跨越了多個塊的範圍,GFS客戶端代碼把它分成多個寫操作。它們都遵照上面描述的控制流程,但是可能會被來自其它客戶機的併發操作造成交錯或者重寫。因此,共享文件域可能以包含來自不同客戶機的片段結尾,儘管如此,由於這些單個的操作在所有的副本上都以相同的順序完成,副本仍然會是完全相同的。這使文件域處於2.7節提出的一致但是未定義的狀態。

3.2 數據流

爲了有效地利用網絡,我們將數據流從控制流中解耦。在控制流從客戶機到主副本再到所有次級級副本的同時,數據以管道的方式,線性地的沿着一個精心挑選的Chunkserver鏈推送。我們的目標是充分使用每臺機器的網絡帶寬,避免網絡瓶頸和高延時的連接,最小化推送所有數據的延時。

爲了充分使用每臺機器的帶寬,數據線性地沿着一個Chunkserver鏈推送,而不是其它拓撲分佈(例如,樹)。這樣,每臺機器所有出口帶寬都用於儘快地傳輸數據,而不是在多個接受者之間分配帶寬。

爲了儘可能地避免網絡瓶頸和高延遲的鏈接(比如,交換機之間的鏈路inter-switch經常既是瓶頸又高延遲),每臺機器將數據轉發到網絡拓撲中離自己最近而又沒收到數據的機器。假設客戶機把數據推送到Chunkserver S1S4。它把數據推送到最近的Chunkserver,比如說S1S1將數轉轉發到從S2S4中離S1最近的Chunkserver,比如說S2,同樣的,S2轉發數據到S3或者S4中離S2更近的那個,諸如此類。我們的網絡拓撲足夠簡單,以至於從IP 地址就可以精確地估計“距離”。

最後,我們通過基於TCP連接的管道傳輸數據方式來最小化延遲。一旦Chunkserver接收到數據,它馬立即開始轉發。管道方式對我們幫助特別大,因爲我們採用全雙工連接的交換網絡。立即發送數據不會降低接收速度。在沒有網絡擁塞的情況下,傳輸B字節數據到R個副本的理想經過時間是B/T+RL,其中T是網絡的吞吐量,L是在兩臺機器間數據傳輸的延遲。我們的網絡連接通常是100MbpsT),L將遠小於1ms。因此,1MB的數據在理想情況下80ms左右就分發出去。

3.3 原子的記錄追加

GFS提供了一種叫做記錄追加的原子追加操作。傳統的寫操作中,客戶程序指定寫入數據的偏移量。對同一個域的並行寫不是串行的:域可能以包含來自多個客戶機的數據片段結尾。在記錄追加中,然而,客戶機只需指定數據。GFS將其原子地追加到文件中至少一次(例如,作爲一個連續的byte序列),數據追加到GFS選擇的偏移位置,然後將這個偏移量返回給給客戶機。這類似於在Unix中,對以O_APPEND模式打開的文件,多個併發寫操作在沒有競態條件時對文件的寫入。

記錄追加在我們的分佈應用中經常使用,其中很多在不同機器上的客戶程序併發對同一文件追加。如果我們採用傳統寫方式處理,客戶機將需要額外的複雜、昂貴的同步機制,例如通過一個分佈式鎖管理器。在我們的工作中,這樣的文件通常用於多生產者/單消費者隊列,或者是合併來自多個客戶機的結果。

記錄追加是一種變更,遵循3.1節的控制流,只主副本有些額外的控制邏輯。客戶機把數據推送給文件最後一個塊的所有副本,然後向主副本發送請求。主副本會檢查如果追加這條記錄會不會導致塊超過最大尺寸(64MB)。如果超過,將快填充到最大尺寸,通知次級副本做同樣的操作,然後回覆客戶機指出操作應該在下一個塊重試。(記錄追加限制在至多塊最大尺寸的1/4,這樣保證最壞情況下數據碎片的數量仍然在可控的範圍。)如果記錄在最大尺寸以內,這也是通常情況,主副本服務器將數據追加到自己的副本,通知次級副本將數據寫在它準確的位移上,最後回覆客戶機操作成功。

如果記錄追加在任何副本上失敗,客戶端重試操作。結果,同一個塊的副本可能包含不同的數據,可能包括一個記錄的全部或者部分重複。GFS並不保證所有副本在字節級別完全相同。它只保證數據作爲一個原子單元的至少被寫入一次。這個特性可以很容易地從簡單觀察中推斷出來:操作如果要報告成功,數據一定已經寫入到了一些塊的所有副本的相同偏移上。並且,至此以後,所有副本至少都和記錄尾部一樣長,並且將來的記錄會被分配到更高的偏移,或者不同的塊,即之後一個不同的副本成爲了主副本。就我們的一致性保障而言,記錄追加操作成功寫入數據的域是已定義的(因此也是一致的),然而中間域則是不一致的(因此也就是未定義的)。我們的程序可以像我們在2.7.2節討論的那樣處理不一致的域。

3.4 快照

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

快照操作幾乎瞬間完成對一個文件或者目錄樹(“源”)的拷貝,並且最小化對正在進行的變更的任何干擾。我們的用戶使用它快速地創建一個大數據集的分支拷貝(而且經常遞歸地拷貝拷貝),或者是在做修改實驗之前,對當前狀態做檢查點,這樣之後就可以輕鬆的提交或者回滾。

就像AFS alex注:AFS ,即Andrew File System ,一種分佈式文件系統),我們使用標準的寫時拷貝(copy-on-write)技術實現快照。當master收到快照請求,它首先取消在將要快照的文件中的塊的任何未解決的租約。這保證了後續對這些塊的寫操作都需要與master交互以找到租約持有者。這就給master一個率先創建塊拷貝的機會。

租約取消或者過期之後,master把這個操作以日誌的方式記錄到硬盤。然後,master通過複製源文件或者目錄樹的元數據的方式,把這條日誌記錄應用到內存狀態。新創建的快照文件和源文件指向相同的塊。

在快照操作之後,當客戶機初次想寫入數據到塊C,它向master發送請求查詢當前的租約持有者。Master注意到塊C的引用計數比1大。Master延遲迴復客戶機請求,然後改爲選擇一個新的塊句柄C`。之後,master要求每個擁有C當前副本的Chunkserver創建一個叫做C`的新塊。通過在作爲原件的同一Chunkserver上創建新的塊,我們確保數據可以本地拷貝,不是通過網絡(我們的硬盤比我們100Mb以太網鏈接大約快3倍)。從這點來講,爲任何塊處理請求沒有區別:master爲其中一個副本授權新塊C`的租約,之後回覆客戶機,客戶機可以正常的寫這個塊,並不知曉它剛是一個現存的塊創建出來。

4. Master節點的操作

Master執行所有的命名空間操作。另外,它管理整個系統裏的塊副本:它制定部署策略,創建新的塊也就是副本,協調各種系統級活動以保證塊全面備份,在所有Chunkserver間平衡負載,回收閒置的存儲空間。本節我們分別討論這些主題。

4.1 命名空間管理和鎖

許多master操作會花費很長時間:比如,快照操作必須取消被快照覆蓋的所有塊上的Chunkserver租約。我們不想它們運行的時候耽擱其它master操作。因此,我們允許多個操作活躍,使用名稱空間域上的鎖來保證正確的串行化。

不同於許多傳統文件系統,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 上的衝突鎖。文件創建不需要父目錄的寫鎖,因爲這裏沒有“目錄”,或者類似內部節點的數據結構需要防止修改。文件名的讀鎖足以防止父目錄被刪除。

這種鎖機制的一個良好特性是支持對同一目錄的併發變更。比如,可以在同一個目錄下同時創建多個文件:每個都獲得一個目錄名的上的讀鎖和文件名上的寫鎖。目錄名的讀取鎖足以防止目錄被刪除、改名以及被快照。文件名的寫入鎖序列化地嘗試用同一個名字兩次創建文件。

因爲名稱空間可以有許多節點,讀寫鎖對象採用惰性分配,一旦不再使用立刻被刪除。同樣,鎖在一個一致性的全局順序中獲取來避免死鎖:首先按名稱空間樹中的層次排序,同層按字典順序排序。

4.2 副本的部署

GFS集羣高度分佈在多層,而不是一層。它通常有分佈在許多機櫃上的數百個Chunkserver。這些Chunkserver反過來被來自同一或者不同機櫃上的數百個客戶機訪問。不同機架上的兩臺機器間的通訊可能跨越一個或多個網絡交換機。另外,出入機櫃的帶寬可能比機櫃內部所有機器的總體帶寬要小。多層分佈架構對分佈式數據的擴展性、可靠性以及可用性提出了特有的挑戰。

塊副本部署策略滿足兩大目標:最大化數據可靠性和可用性,最大化網絡帶寬利用率。爲了實現這兩個目的,將副本跨機器分佈是不夠的,這隻能防止硬盤或機器失效以及全面使用每臺機器的網絡帶寬。我們必須也跨機櫃分佈塊副本。這保證塊的一些副本在即使整個機架被破壞或掉線(比如,因爲網絡交換機或者電源電路等共享資源的失效)的情況下依然倖存並保持可用。這還意味着在網絡流量方面,尤其對於讀,對於塊可以開發多個機櫃的整合帶寬。另一方面,寫通信量必須流經多個機櫃,這是我們自願的折衷。

4.3 創建,重新備份,重新平衡負載

創建塊副本的三個起因:塊創建,重新備份和重新平衡負載。

master創建一個塊,它選擇在哪裏放置初始的空副本。它考慮以下幾個因素:(1)我們想將新副本放在低於平均硬盤使用率的Chunkserver上。慢慢地這能平衡Chunkserver間的硬盤使用率。(2)我們想要限制在每個Chunkserver上“最近”創建的次數。雖然創建本身是廉價的,但是確實預示着臨近的大量寫入通信量,因爲當寫操作需要的時候才創建塊,而在我們的“追加一次,讀取多次”的工作模式下,塊一旦完全寫入通常實際上就變成了只讀。(3)如上所述,我們想要把塊副本分佈在多個機櫃上。

一旦有效副本數量跌到用戶指定的目標以下,master重新備份它。這可能由多種原因引起:一個Chunkserver不可用,它報告其副本可能損壞,它的其中一個硬盤因爲錯誤失效,或者副本數下限值提高了。每個需要被重新備份的塊都會根據一些因素優先化。一個是它與副本數下限值差多少。例如,我們給與丟失兩個副本的塊比只丟失一個副本的塊更高的優先級。另外,我們更喜歡優先重新備份活躍文件的塊而不是屬於最近被刪除的文件的塊(查看4.4節)。最後,爲了最小化失效對正在運行的應用程序的影響,我們增加阻塞客戶進程的任何塊的優先級。

Master選擇優先級最高的塊然後通過命令某些Chunkserver直接從現存有效的副本複製塊數據完成克隆。新副本的部署目標與創建時類似:平衡硬盤空間使用率、限制任意單個Chunkserver上正在進行的克隆操作數量、在機櫃間分佈副本。爲了防止克隆產生的網絡流量大大超過客戶機的流量,master對集羣和每個Chunkserver上正在進行的克隆操作的數量都進行了限制。另外,每個Chunkserver通過節流對源Chunkserver的讀請求來限制它用於每個克隆操作的帶寬。

最後,master週期性地重新平衡副本:它檢查當前的副本分佈,移動副本以便更好的利用硬盤空間和平衡負載。而且在這個過程中,master逐漸地填滿一個新的Chunkserver,而不是瞬時用新的塊以及隨之而來的擁擠的寫通信量使其陷入困境。新副本的部署標準和上面討論的類似。另外,master也必須選擇移除哪個現存副本。一般而言,master更喜歡移除那些空閒空間低於平均值的Chunkserver上的副本,從而平衡硬盤空間使用。

4.4 垃圾回收

GFS在文件刪除後不會立刻回收可用的物理空間。這隻在常規的文件及塊級垃圾回收期間懶惰地進行。我們發現這個方法使系統更簡單、更可靠。

4.4.1 機制

當一個文件被應用程序刪除時,master象對待其它變更一樣立刻把刪除操作錄入日誌。然而,與立即回收資源相反,文件只是被重命名爲包含刪除時間戳的隱藏的名字。在master對文件系統命名空間常規掃描期間,它會刪除所有這種超過了三天的隱藏文件(這個時間間隔是可設置的)。在這之前,文件仍舊可以用新的特殊的名字讀取,也可以通過重命名爲普通文件名的方式恢復。當隱藏文件被從命名空間中刪除,它的內存元數據被擦除。這有效地割斷了它與所有塊的連接。

在類似的塊命名空間常規掃描中,master識別孤立塊(也就是對任何文件不可達的那些)並擦除那些塊的元數據。在與master定期交換的心跳信息中,每個Chunkserver報告它擁有的塊的子集,master回覆識別出的已經不在master元數據中顯示的所有塊。Chunkserver可以任意刪除這種塊的副本。

4.4.2 討論

雖然分佈式垃圾回收在編程語言環境中是一個需要複雜的解決方案的難題,這在GFS中是相當簡單的。我們可以輕易地識別塊的所有引用:它們在master專有維護的文件-塊映射中。我們也可以輕鬆識別所有塊的副本:它們是每個Chunkserver指定目錄下的Linux文件。所有這種master不識別的副本都是“垃圾”。

垃圾回收方法對於空間回收相比迫切刪除提供了一些優勢。首先,在組件失效是常態的大規模分佈式系統中其既簡單又可靠。塊創建可能在某些Chunkserver上成功在另一些上失敗,留下了master不知道其存在的副本。副本刪除消息可能丟失,master不得不記得重新發送失敗的刪除消息,不僅是自身的還有Chunkserver的(alex注:自身的指刪除元數據的消息)。垃圾回收提供了一個統一的、可靠的清除無用副本的方法。第二,它將空間的回收合併到master常規後臺活動中,比如,命名空間的常規掃描和與Chunkserver的握手等。因此,它批量執行,成本也被攤銷。並且,它只在master相對空閒的時候進行。Master可以更快速地響應需要及時關注的客戶機請求。第三,延緩空間回收爲意外的、不可逆轉的刪除提供了安全網。

根據我們的經驗,主要缺點是,延遲有時會阻礙用戶在存儲空間緊缺時對微調使用做出的努力。重複創建和刪除臨時文件的應用程序不能立刻重用釋放的空間。如果一個已刪除的文件再次被明確刪除,我們通過加速空間回收的方式解決這些問題。我們允許用戶對命名空間的不同部分應用不同的備份和回收策略。例如,用戶可以指定某些目錄樹內的文件中的所有塊不備份存儲,任何已刪除的文件立刻不可恢復地從文件系統狀態中移除。

4.5 過期副本檢測

如果Chunkserver失效或者漏掉它失效期間對塊的變更,塊副本可能成爲過期副本。對每個塊,master維護一個塊版本號來區分最新副本和過期副本。

每當master在一個塊授權一個新的租約,它就增加塊的版本號,然後通知最新的副本。Master和這些副本都把這個新的版本號記錄在它們持久化狀態中。這發生在任何客戶機得到通知以前,因此也在對塊開始寫之前。如果另一個副本當前不可用,它的塊版本號就不會被增長。當Chunkserver重新啓動,並且向master報告它擁有的塊子集以及它們相聯繫的版本號的時候,master會探測該Chunkserver是否有過期的副本。如果master看到一個比它記錄的版本號更高的版本號,master假定它在授權租約的時候失敗了,因此選擇更高的版本號作爲最新的。

master在常規垃圾蒐集中移除過期副本。在此之前,master在回覆客戶機的塊信息請求的時候實際上認爲過期副本根本不存在。作爲另一種保護措施,在通知客戶機哪個Chunkserver持有一個塊的租約、或者在一個克隆操作中命令一個Chunkserver從另一個Chunkserver讀取塊時,master都包含了塊的版本號。客戶機或者Chunkserver在執行操作時都會驗證版本號,因此它總是訪問最新數據。

5. 容錯和診斷

我們設計系統中的最大挑戰之一就是如何處理頻繁的組件失效。組件的數量和質量合起來讓這些問題比例外更正常:我們不能完全相信機器,我們也不能完全相信硬盤。組件失效可能造成系統不可用,更糟糕的是,損壞數據。我們討論了我們如何面對這些挑戰,以及當它們不可避免的發生時,我們在系統中建立的診斷問題的工具。

5.1 高可用性

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

5.1.1 快速恢復

無論masterChunkserver是如何終止的,它們都被設計爲可以在數秒內恢復它們的狀態並重新啓動。事實上,我們並不區分正常和異常終止;服務器僅僅是通過直接殺掉進程的方式來常規地關閉服務器。當未解決的請求超時,重連到重啓的服務器並重試時,客戶機和其它的服務器會感受到系統輕微顛簸。6.6.2節報告了觀察的啓動時間。

5.1.2 塊備份

正如之前討論的,每個塊都備份到不同機櫃的不同Chunkserver上。用戶可以爲文件命名空間的不同部分指定不同的備份級別。默認是3。當Chunkserver離線或者通過檢驗和(參考5.2節)探測到損壞的副本,master根據需要克隆現存的副本來保證每個塊全複製(shijin注:即是否達到備份級別指定的數目)。雖然備份對我們非常有效,我們也在開發其它形式的跨服務器冗餘,比如奇偶校驗、或者Erasure codesalex注:Erasure codes用來解決鏈接層中不相關的錯誤,以及網絡擁塞和緩衝區限製造成的丟包錯誤)來解決我們日益增長的只讀存儲需求。我們認爲在我們鬆散耦合的系統中實現更復雜的冗餘機制是富有挑戰性但是可行的,因爲我們的通信量主要被追加和讀佔據,而不是小型的隨機寫。

5.1.3 master備份

Master爲了可靠性備份其狀態。Master日誌和檢查點都在多臺機器上備份。對狀態的變更被認爲是在日誌記錄寫入到本地硬盤和所有master備份之後才被提交。簡單說來,一個master進程保持主管所有變更操作以及比如垃圾回收這種從內部改變系統的後臺活動。當它失效時,它幾乎可以立刻重新啓動。如果它的機器或者硬盤失效了,GFS外部的監控設施會用備份的操作日誌在別處啓動一個新的master進程。客戶端只使用權威的master名字(比如gfs-test),這是一個DNS別名,如果master被重新分配到其他機器,該名字可以改變。

此外,“影子”master即使在主master宕機的時候依然提供文件系統的只讀訪問。它們是影子,不是鏡像,所以它們可能比主master稍微有些延遲,通常是不到1秒。對於那些不活躍變更的文件,或者不介意得到少量過期結果的應用程序而言,“影子”master提高了讀可用性。實際上,因爲文件內容是從Chunkserver讀取的,應用程序不會觀察到過期的文件內容。在這個短暫的時間窗內,過期的可能是文件的元數據,比如目錄內容或者訪問控制信息。

“影子”master爲了保持自身是最新的,它會讀取正在增長的操作日誌副本,並且完全依照主master對自己的數據結構應用同樣順序的修改。和主master一樣,它在啓動的時也會輪訓Chunkserver(並且從那以後很少發生),輪訓的目的是定位塊副本和與它們交換慣常的握手信息以監控狀態。僅因主master決定創建和刪除副本而導致副本位置信息更新時,它才依賴主master

5.2 數據完整性

每個Chunkserver都使用檢驗和來探測保存的數據是否損壞。考慮到一個GFS集羣通常有幾百臺機器上的數千塊硬盤,它會定期經歷在讀寫過程中導致數據損壞或者丟失的硬盤失效(第7節講了一個原因)。我們可以利用其它塊副本來恢復受損數據,但是在Chunkserver間比較副本來探測受損數據不切實際。並且,相異的副本可能是合法的:GFS變更的語義,特別是早先討論的原子紀錄追加的操作,並不保證副本完全相同(shijin注:副本不是字節級完全一致的) 。因此,每個Chunkserver必須通過維護檢驗和來獨立地驗證自己副本的完整性。

每個大塊被分成64KB的小塊。每個小塊對應一個32位的檢驗和。和其它元數據一樣,檢驗和被保存在內存並持久化存儲在日誌中,與其它的用戶數據是分開的。

對於讀操作來說,在把任意數據返回給請求者之前,無論請求者是客戶端或者其它ChunkserverChunkserver會驗證與讀範圍重疊的數據塊的檢驗和。因此Chunkserver不會把損壞傳播到其它機器。如果一個塊與記錄的檢驗和不匹配,Chunkserver返回給請求者一個錯誤信息,並且報告master這個失配。作爲迴應,請求者會從其它副本讀取,同時master會從其它副本克隆塊。當一個有效的新副本就位後,master命令報告失配的Chunkserver刪除副本。

由於以下原因驗證檢驗和對讀操作的性能影響很小。因爲大多數的讀操作分佈在至少幾個塊上,我們只需要讀取和驗證相當小數量的額外據進行驗證。GFS 客戶端代碼通過嘗試將讀操作對齊到檢驗和塊的邊界上來進一步減少開銷。另外,Chunkserver上檢驗和的查找和比較不需要I/O即可完成,檢驗和的計算可以和I/O同時進行。

檢驗和的計算對在塊的尾部追加(相反的是重寫現存數據)的寫入操作作了高度優化,因爲這在工作量中佔統治地位。我們只是爲最後一個部分檢驗和塊遞增地更新檢驗和,並且爲通過追加填充的嶄新的檢驗和塊計算新的檢驗和。即使最後部分檢驗和塊已經損壞了並且我們沒有探測到,新的檢驗和值與存儲的數據會失配,在這個塊下次被讀的時候,損壞會被探測到。

與此相反,如果寫操作重寫了塊的現存範圍,我們必須讀取和驗證重寫範圍中的第一個和最後一個塊,然後再執行寫操作;最後計算並記錄新的檢驗和。如果我們在部分重寫之前不驗證第一個和最後一個塊,那麼新的檢驗和可能在沒有重寫的區域隱藏損壞。

空閒的時候,Chunkserver可以掃描和驗證不活動的塊的內容。這允許我們探測很少被讀取的塊中的損壞。一旦探測到有損壞,master可以創建一個新的、未損壞的副本,然後刪除損壞的副本。這也避免不活躍但是已損壞的塊副本欺騙master,使其認爲它有足夠的有效塊副本。

5.3 診斷工具

廣泛詳細的診斷日誌,在問題隔離、調試、以及性能分析方面帶來無盡的幫助,卻只引發很小開銷。沒有日誌,很難理解機器之間短暫的、不重複的交互。GFS服務器會產生記錄了大量關鍵事件的日誌(比如,Chunkserver的啓動和關閉)以及所有RPC的請求和回覆。這些診斷日誌可以在不影響系統正確性的情況下隨意刪除。然而,只要空間允許我們就試着隨手保存這些日誌。

RPC日誌包含寫操作上發送的準確的請求和響應,除了將要讀寫的文件數據。通過匹配請求與迴應,以及收集不同機器上的RPC日誌記錄,我們可以重構整個消息交互歷史來診斷問題。日誌也用來跟蹤負載測試和性能分析。

日誌對性能的影響很小(效益上更重要),因爲這些日誌的連續異步寫入。最近的事件保存在內存中,可用於連續的在線監控。

6. 測量

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

6.1 小規模基準測試

我們測量了一個GFS集羣的性能,該集羣由1master2master備份,16Chunkserver16個客戶機組成。注意,這種配置設置用來方便測試。典型的GFS集羣有數百個Chunkserver和數百個客戶機。

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

6.1.1 讀取

N 個客戶機從文件系統同時讀取數據。每個客戶機從320GB的文件集中讀取隨機選擇的4MB的域。該操作重複了256次以至於每個客戶機最終都讀取1GB的數據。Chunkserver加起來總共只有32GB的內存,因此,我們預期在Linux緩衝區中至多有10%的命中率。我們的結果應該非常接近於冷緩存(code cache)的結果。

圖三:合計吞吐量:上邊的曲線顯示了在我們的網絡拓撲影響下理論上限。下邊的曲線顯示了觀測到的吞吐量。它們有顯示置信區間爲95%的誤差棒,誤差棒在某些情況下不好辨認,因爲測量中很低的變化。

3a)顯示了N 個客戶機整體的讀取速度以及理論上限。當兩個交換機之間的1Gbps鏈路飽和時,達到整體125MB/S的極值,或者當每個客戶機應用的100Mbps網絡接口達到飽和時,它的速度12.5MB/s。觀察到的讀取速率是,當只有一個客戶機讀時,速度是10MB/s,或者達到每個客戶機上限速率的80%。對於16reader,整體的讀取速度達到了94MB/s,大約是125MB/s連接上限的75%,或者說每個客戶機6MB/s。效率從80%降低到了75%,是因爲隨着reader數目的增加,多個reader同時從同一Chunkserver讀取的可能性也增加了。

6.1.2 寫入

N個客戶機同時向N個不同的文件寫入。每個客戶機以一連串1MB的寫操作寫入1GB的數據。圖3b)顯示了整體寫入速度和理論上限。上限停滯在67MB/s是因爲我們需要把每個字節寫入到16Chunkserver中的3個上,而每個Chunkserver有一個12.5MB/s的輸入連接。(shijin注:16/3*12.5=66.6

一個客戶機的寫入速度是6.3MB/s,大概是上限的一半。導致這個結果的主要原因是我們的網絡協議棧。它與我們推送數據到塊副本採用的管道模式交互不好。從一個副本到另一個副本的傳播延遲降低了整體寫入速度。

16個客戶機的整體寫入速度達到了35MB/s(即每個客戶機2.2MB/s),大約只是理論上限的一半。與讀的情況一樣,隨着客戶機數量的增加,多個客戶機同時寫入同一個Chunkserver的可能性也變多了。並且,對16writer而言比16reader衝突的可能性更大,因爲每個寫涉及三個不同的副本。

寫比我們期望的要慢。實際中這沒有成爲主要問題,因爲即使個別客戶機看到它增加了延時,對大多數客戶機來說它不會對系統已經交付的整體寫帶寬帶來顯著影響。

6.1.3 記錄追加

3c)顯示了記錄追加的性能。N個客戶機同時追加到單個文件。性能受限於保存文件最後一個塊的Chunkserver的網絡帶寬,不受客戶機數量的約束。速度從一個客戶機的6.0MB/s開始,下降到16個客戶機的4.8MB/s爲止,很大程度上是由於網絡擁堵和不同客戶機網絡傳輸速度的不同。

我們的程序傾向於同時產生多個這樣的文件。換句話說,N個客戶機同時追加數據到M個共享文件,這裏NM都是數十或者數百。因此,實際中Chunkserver網絡擁堵並不是一個重大問題,因爲當Chunkserver寫一個文件忙碌,客戶機可以在寫另外一個文件上取得進展。

6.2 現實的集羣

我們現在檢查Google內部正在使用的兩個集羣,它們在與它們類似的集羣中具有代表性。集羣A經常被一百多個工程師用於研究和開發。典型的任務是被人工初始化後運行數小時。它讀取從數MB到數TB的數據,轉換或者分析數據,然後把結果寫回集羣。集羣B主要用於處理生產數據。任務持續的時間更長,在只有偶爾認爲干預下,持續地生成和處理數TB的數據集。在這兩個案例中,一個單獨的“任務”包含多個機器上的多個進程,它們同時讀取和寫入多個文件。

6.2.1 存儲

如表中前五條展示的,兩個集羣都有數百臺Chunkserver,支持數TB的硬盤空間;並且差不多滿但是還沒滿。“已用空間”包括所有塊副本。事實上所有文件備份了三次。因此,集羣分別存儲了18TB52TB的文件數據。

兩個集羣有相似的文件數目,儘管B上有大部分的死文件。所謂“死文件”也就是文件被刪除或者被新版本替代,但是它佔的存儲空間還沒有被回收。由於其文件較大,它也有更多的塊。

6.2.2 元數據

Chunkserver總共存儲了數十GB的元數據,大多是用戶數據的64KB塊的檢驗和。保存在Chunkserver上僅有的其它元數據是4.5節討論的塊版本號。

保存在master上的元數據小的多,只有數十MB,或者說平均每個文件100 字節。這和我們設想的是一樣的,實際中master的內存大小並不限制系統容量。大多數每個文件的元數據都是以前綴壓縮的形式存儲的文件名。其它元數據包括文件所有權和許可、文件到塊的映射,以及每個塊的當前版本。此外,針對每個塊,我們都存儲了當前副本的位置和用於實現寫時拷貝(alex注:即COWcopy-on-write)的引用計數。。

每一個單個的服務器,無論是Chunkserver還是master,只有50MB100MB的元數據。因此恢復是很快的:在服務器應答查詢之前,只需要幾秒鐘從硬盤讀取這些元數據。然而,master會稍微顛簸一段時間-通常是3060-直到它從所有Chunkserver獲取塊位置信息。

6.2.3 讀寫速率

表三顯示了各種時段的讀寫速率。在測量開始的時候這兩個集羣都已經運行了大約一個星期。(集羣最近都因爲升級新版本的GFS重新啓動過)。

重啓後,平均寫入速率小於30MB/s。當我們測量的時候,B正處於一陣寫入活動中,正以100MB/s的速度生產數據,由於寫操作傳播到三個副本,該過程產生了300MB/s的網絡負載。

讀取速率要比寫入速率高的多。正如我們設想的那樣,總的工作量由更多的讀而不是寫組成。兩個集羣都處於繁重的讀活動中。特別是,A已經在之前的一週持續580MB/s的讀取速度。它的網絡配置可以支持750MB/s,所以它有效的使用了資源。集羣B支持1300MB/s的峯值讀取速度,但是它的應用僅僅用了380MB/s

6.2.4 Master負載

3的也顯示了發送到master的操作的速度大約是每秒200500個。Master可以輕鬆地跟上這個速度,因此這部分工作負載不是系統瓶頸。

GFS的早期版本中,master偶爾會成爲一些工作負載的瓶頸。它花費大部分時間從頭到尾順序掃描很大的目錄(包含數十萬個文件)查找某個特定的文件。我們已經修改了master的數據結構以便允許對命名空間進行高效的二分查找。它現在可以輕鬆支持每秒數千次文件訪問。如果需要的話,我們可以通過在命名空間數據結構之前部署名稱查詢緩衝的方式進一步提高速度。

6.2.5 恢復時間

當一個Chunkserver失效,一些塊會變成等待複製(under-replicated),必須克隆以恢復備份級別。恢復所有這樣塊的時間取決於資源的數量。在一次實驗中,我們殺掉了集羣B上的一個Chunkserver。這個Chunkserver上大約有15000個塊包含600GB數據。爲了限制對正在運行的應用程序的影響,以及爲調度策略留有餘地leeway,我們的缺省參數限制集羣併發克隆的數量爲91Chunkserver的數量的40%),每個克隆操作允許消耗至多6.25MB/s50mbps)。所有的塊用23.2分鐘恢復,複製的速度高達440MB/s

另外一個實驗中,我們殺掉了兩個每個大約有16000個塊和660GB數據的Chunkserver。這個雙重故障使266個塊減少到只有單個副本。這266 個塊以更高的優先級克隆,並且都在在2分鐘內恢復到至少有兩個副本;如此將集羣置入了這樣一個狀態,其中系統可以容忍另外一個Chunkserver失效而不丟失數據。

6.3 工作負載分解

本節中,我們展示了兩個GFS集羣工作負載的詳細分解,這兩個集羣與6.2節中的具有可比性但又不完全相同。集羣X用於研究和開發,集羣Y用於生產數據處理。

6.3.1 方法論和警告

這些結果只包括客戶機原始請求,因此,它們反映我們的應用程序爲整個文件系統產生的負載。它們不包含爲了執行客戶請求而進行的服務器內部請求,也不包含內部後臺活動,比如轉發寫(forwarded writes)或者重新均衡負載。

IO操作方面的統計數據是基於啓發式重構的信息,這些信息來自GFS服務器記錄的實際RPC請求。例如,GFS 客戶端代碼可能會把一個讀操作分成多個RPC來提高並行度,從這些RPC中我們推導出原始的讀操作。因爲我們的訪問模式高度程式化,我們認爲任何錯誤都是誤差(Since ouraccess patterns are highly stylized, we expect any error to be in the noise)。應用程序作出的明確的記錄可能已經提供了更準確的數據;但是重新編譯和重新啓動數千個正在運行的客戶機邏輯上是不可能的,而且從如此多的機器上收集結果也很繁重。

應該小心不能從我們的工作負載中過度概括(alex注:即不要把本節的數據作爲基礎的指導性數據)。因爲Google完全控制着GFS和它的應用程序,應用程序趨向於對GFS 做了調節,同時GFS是爲了這些應用程序設計的。如此相互影響也可能存在於一般程序和文件系統中,但是在我們的案例中這樣的影響可能更顯著。

6.3.2 Chunkserver工作負載

4:根據大小對操作的分解(%)。對於讀操作,大小就是實際讀和傳送的數據量,而不是請求的量

4顯示了操作按大小的分佈。讀操作呈現了雙峯分佈。小的讀取操作(64KB以下)來自密集搜索型客戶端,這些客戶端在大文件中查找小片數據。大的讀取操作(超過512KB)來自貫穿整個文件的長連續讀。

集羣Y上有大量的讀操作根本沒有返回數據。我們的應用,尤其是那些在生產系統中的應用,經常將文件作爲生產者-消費者隊列使用。生產者併發地向文件追加的同時消費者讀取文件的尾部。偶爾,當消費者超過生產者的時候,沒有數據返回。集羣X中這種情況不那麼經常,因爲其通常用於短期的數據分析任務,而不是長期分佈式應用。

寫操作大小也呈現雙峯分佈。大的寫操作(超過256KB)通常起因於Writer內部大量的緩衝。緩衝了更少數據,更經常作檢查點或者同步,或者簡單產生更少數據的Writer負責小型寫操作(小於64KB)。

至於記錄追加。我們看到集羣Y中大的記錄追加操作所佔比例比集羣X更多,因爲我們的生產系統使用了集羣Y,爲GFS 做了更有進取心的調節。

5:根據操作大小(%)對傳輸字節的分解。對於讀操作,大小是實際讀取和傳送的數據量,而不是請求的量,如果讀操作試圖讀取超出文件尾的部分,二者可能不同,這在我們的工作負載中是故意爲之並不罕見。

5顯示了各個大小的操作的全部數據傳輸量。對各種各樣的操作,大的操作(超過256KB)通常負責大多數字節傳輸。小的讀操作(64KB以下)雖然傳輸的數據量比較少,但是確是讀取數據中的重要部分,因爲隨機搜索負載。

6.3.3 追加vs寫操作

記錄追加操作尤其在我們生產系統中使用頻繁。對於集羣X,按照傳輸的字節數寫操作和記錄追加的比率是108:1,按照操作次數比是8:1shijin:太詭異了,作者筆誤吧?貌似寫反了?)對於用於我們生產系統的集羣Y,比率分別是3.7:12.5:1。並且,這一比率說明對於這兩個集羣,記錄追加傾向於比寫操作大。然而對於集羣X,在測量期間記錄追加的整體使用相當低,因此結果可能可能被一兩個使用特定大小的緩衝區選擇的應用程序造成偏移。

不出所料,我們的數據變更負載主要被記錄追加佔據而不是重寫。我們測量了在主副本上的重寫數據量。這近似於一個客戶機故意重寫之前寫過的數據,而不是增加新的數據。對於集羣X,重寫的量低於字節變更的0.0001%,低於變更操作的0.0003%。對於集羣Y,這兩個比率都是0.05%。雖然這很微小,但是仍然高於我們的預期。這證明了,大多數重寫來自由於錯誤或超時引起的客戶端重試。這在不算工作負荷本身的一部分,而是重試機制的結果。

6.3.4 Master工作負載

6 顯示了對master請求類型的分解。大部分請求是爲讀取請求塊位置信息(FindLocation)以及爲數據變更請求租約持有者信息(FindLease-Locker)。

在集羣XY看出不同數量的刪除請求,因爲集羣Y存儲了生產數據集,這一數據集定期重新生成並被新版本替代。一些不同被進一步隱藏在了打開請求中,因爲文件的舊版本可能在爲擦除寫打開時,暗中被刪除了(類似UNIX的打開術語中的“w”模式)。

FindMatchingFiles是一個模式匹配請求,支持“ls”以及類似文件系統操作。不同於master的其它請求,它可能會處理大部分命名空間,因此可能非常昂貴。集羣Y中這類請求更常見,因爲自動化數據處理任務傾向於檢查部分文件系統來掌握全局應用程序狀態。相反,集羣X的應用程序在更明確的用戶控制之下,通常提前知道所有需要的文件的名稱。

7. 經驗

在建造和部署GFS 的過程中,我們經歷了各種各樣的問題,有些是操作上的,有些是技術上的。

起初,GFS 被設想爲我們的生產系統的後端文件系統。隨着時間推移,使用涉及了研究和開發任務。開始對許可和配額這類工作有很少的支持,但是現在包含了這些工作的基本形式。雖然生產系統是條理可控的,用戶有時卻不是。需要更多的基礎設施來防止用戶互相干擾。

我們最大的問題是磁盤以及和Linux相關的問題。很多磁盤聲稱擁有支持某個範圍內的IDE協議版本的Linux驅動,但是實際中反映出,只可靠地支持最新的。因爲協議版本非常類似,這些磁盤大都可用,但是偶爾失配會導致驅動和內核對於驅動狀態意見不一致。這會導致因爲內核中的問題而默默地損壞數據。這個問題激發了我們使用檢驗和來探測數據損壞,然而同時我們修改內核來處理這些協議失配。

早期我們在用Linux 2.2內核時有些問題,起因於fsync()的開銷。它的開銷與文件的大小而不是文件修改部分的大小成比例。這對我們的大型操作日誌來說是一個問題,尤其是在我們實現檢驗和之前。我們花了不少時間用同步寫來解決這個問題,但是最後還是移植到了Linux2.4內核上。

另一個和Linux問題是單個讀寫鎖問題,在一個地址空間的任意線程在從磁盤讀進頁(讀鎖)的時候都必須持有鎖,或者在mmap()調用(寫鎖)的時候修改地址空間。在輕負載下的系統中我們發現短暫超時,然後賣力尋找資源瓶頸或者零星硬件錯誤。最終我們發現在磁盤線程置換之前映射數據的頁時,單獨鎖阻塞了主網絡線程把新數據映射到內存。因爲我們主要受限於網絡接口而不是內存複製帶寬,我們以多一次複製爲代價,用pread()替代mmap()的方式來解決這個問題。

除了偶然的問題,Linux代碼的可用性爲我們節省了時間,並且再一次探究和理解系統的行爲。適當的時候,我們改進內核並且和開源代碼社區共享這些改動。

8. 相關工作

類似諸如AFS[5]的其它大型分佈式文件系統,GFS提供了一個與位置無關的命名空間,這使得數據可以爲均衡負載或者容錯透明地移動數據。不同於AFS 的是,GFS把文件數據分佈到存儲服務器,一種更類似Xfs[1]Swift[3]的方式,這是爲了實現整體性能和提高容錯能力。

由於磁盤相對便宜,並且複製比更復雜的RAID[9]方法簡單的多,GFS當前只使用備份進行冗餘,因此要比xFS或者Swift花費更多的原始數據存儲。

AFSxFSFrangipani[12]以及Intermezzo[6]系統相比,GFS並沒有在文件系統層面提供任何緩存機制。我們的目標工作負載在單個應用程序運行內部幾乎不會重複使用,因爲它們或者是流式的讀取一個大型數據集,要麼是在其中隨機搜索,每次讀取少量的數據。

某些分佈式文件系統,比如FrangipanixFSMinnesotas GFS[11]GPFS[10],去掉了中心服務器,依賴分佈式算法保證一致性和可管理性。我們選擇中心化的方法,目的是簡化設計,增加可靠性,獲得靈活性。特別的是,由於master已經擁有大多數相關信息,並且控制着它的改變,中心master使實現複雜的塊部署和備份策略更簡單。我們通過保持master狀態小型化在其它機器上對狀態全複製的方式處理容錯。擴展性和高可用性(對於讀取)當前通過我們的影子master機制提供。對master狀態的更新通過向預寫日誌追加的方式持久化。因此,我們可以適應類似Harp[7]中主複製機制,從而提供比我們當前機制更強一致性保證的高可用性。

我們在對大量用戶實現整體性能方面類似於Lustre[8]處理問題。然而,我們通過關注我們應用的需求,而不是建立一個兼容POSIX的文件系統的方式,顯著地簡化了這個問題。此外,GFS假定了大量不可靠組件,因此容錯是我們設計的核心。

GFS很類似NASD架構[4]。雖然NASD架構是基於網絡附屬磁盤驅動的,GFS使用日常機器作爲Chunkserver,就像NASD原形中做的那樣。與NASD工作不同的是,我們的Chunkserver使用惰性分配固定大小的塊,而不是分配變長對象。此外,GFS實現了諸如重新平衡負載、備份、恢復等在生產環境中需要的特性。

不同於與Minnesotas GFSNASD,我們並不謀求改變存儲設備模型。我們關注使用現存日常組件的複雜分佈式系統的日常數據處理需求。

原子記錄追加實現的生產者-消費者隊列解決了類似River[2]中分佈式隊列的問題。River使用跨機器分佈、基於內存的隊列,小心的數據流控制;然而GFS 使用可以被許多生產者併發追加記錄的持久化文件。River模型支持mn 的分佈式隊列,但是缺少伴隨持久化存儲的容錯機制,然而GFS只高效地支持m1的隊列。多個消費者可以讀取同一個文件,但是它們必須調整劃分將來的負載。

9. 結束語

Google文件系統展示了在日常硬件上支持大規模數據處理工作負載必需的品質。雖然一些設計決策是針對獨特設置指定的,許多決策可能應用到相似數量級和成本意識的數據處理任務中。

首先,我們根據我們當前和預期的工作負載和技術環境重新檢查傳統文件系統的假設。我們的觀測在設計領域導致了根本不同的觀點。我們將組件失效看作是常態而不是例外,優化通常先被追加(可能併發)然後再讀取(通常序列化讀取)的大文件,以及既擴展又放鬆標準文件系統接口來改進整個系統。

我們的系統通過持續監控,備份關鍵數據,快速和自動恢復的方式容錯。塊備份使得我們可以容忍Chunkserver失效。這些失效的頻率激發了一種新奇的在線修復機制,定期透明地修復受損數據,儘快補償丟失副本。此外,我們使用檢驗和在磁盤或者IDE子系統級別探測數據損壞,考慮到系統中磁盤的數量,這些情況是很常見的。

我們的設計對大量併發的執行各種任務的readerwriter實現了高合計吞吐量。我們通過將文件系統控制與數據傳輸分離實現這個目標,控制經過master,數據傳輸直接在Chunkserver和客戶機之間穿行。Master與一般操作的牽連被大塊尺寸和塊租約最小化,塊租約對主副本進行數據變更授權。這使得一個簡單、中心化的master不變成瓶頸有了可能。我們相信在網絡協議棧上的改進可以提升個別客戶端經歷的寫吞吐量限制。

GFS成功滿足了我們的存儲需求,並且在Google內部作爲存儲平臺,無論是用於研究和開發,還是作爲生產數據處理,都得到了廣泛應用。它是使我們持續創新和解決整個WEB範圍內的難題的一個重要工具。

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