谷歌文件系統(The Google File System譯)(第3章)

3. 系統交互

我們是出於最大限度地減少master在所有操作中的參與度來設計系統的。在此背景之下,現在我們再來描述客戶端、master,以及chunkserver是如何進行交互以實現數據變更,原子性記錄追加和快照的操作

3.1 租約與更新順序

數據更新是指改變元數據或是塊的內容的操作,例如寫入或追加操作。每一個變更都會在塊的所有副本上執行。我們使用租約來維護副本間更新順序的一致性。master將塊的租約授予副本的其中一個,我們可以稱其爲主副本。主副本會爲所有在該塊上的更新操作選擇一個順序,其餘所有副本都會在執行時遵循這一順序。因此,全局變更順序首先是被master選擇的租約授予順序所確定的,然後纔是在租約中被主副本所分配的序列號來確定

租賃機制被設計用來最大限度地減輕master的管理負擔。一個租約的初始超時時間爲60秒,然而,只要塊被更新,主副本就可以無限制地向master請求延長租約,通常都會得到迴應。這些額外的請求和授權是在master和所有的chunkserver之間的定期交換的心跳消息中捎帶的。master有時可能會嘗試在到期前撤銷租約(例如,master想要禁止一個對文件重命名的更新操作)。即使master和主副本之間斷開通信,也可以在租約到期後將新的租約授予另一個副本

在圖2中,我們通過編號的寫入控制流來說明這一步驟
圖2

  1. 客戶端詢問master哪個chunkserver保存着塊當前的租約和其他的副本位置。如果都沒有擁有租約,則master將從副本中選擇一個來授予租約(未在圖中顯示)
  2. master給客戶端回覆主副本和其他(次要)副本的位置。客戶端會緩存這些數據用於近期的更新操作。只有當主副本無法訪問或是不再擁有租約時,客戶端才需要重新聯繫master
  3. 客戶端將數據推送給所有副本,順序並不固定。每一個chunkserver都會把數據保存在內部的LRU緩存中,直到數據被使用或是過期。通過將數據流與控制流分離,我們可以基於網絡拓撲來調度昂貴的數據流,從而提高性能,也不用關心哪一個是主副本。3.2節中對這一點進行了更深入的討論
  4. 一旦所有副本都確認收到了數據,客戶端就會向主副本發送寫請求,這個請求標識了之前推送給所有副本的數據。主副本會爲收到的所有數據變更操作(可能來自於多個客戶端)分配連續的序列號,這提供了必要的串行化機制,它會按照序列號的順序將更新應用於本地
  5. 主副本將寫請求發送給其他所有的次級副本,每一個次級副本都會按照主副本分配的同樣的序列號順序來執行數據變更
  6. 次級副本在完成操作後需要向主副本回復消息
  7. 主副本向客戶端返回應答。在任何副本上遇到的錯誤都會彙報給客戶端。出現錯誤時,該寫操作可能已經在主副本或其餘一部分次級副本上執行成功了(如果在主副本上發生錯誤,將不會進行序列號的分發),客戶端請求仍會被認爲是失敗的,並且被修改的區域將會處於不一致的狀態。我們的客戶端代碼會通過重試失敗的數據變更操作來處理這樣的錯誤,將會在完全退回重寫之前,先在第(3)步到第(7)步之間進行一些嘗試

如果應用程序的寫入操作過於龐大,或是超過了塊的邊界,GFS客戶端代碼將會將其分解爲多個寫入操作。它們都會遵循上述的寫入流程,但可能會被來自其他客戶端的併發操作交錯覆蓋。因此,共享文件區域最終可能包含來自於不同客戶端的片段,雖然這些副本是一致的,因爲各個操作都以相同的順序在所有副本上成功執行,這會讓文件區域保持一致但不確定的狀態,如2.7節所述

3.2 數據流

爲了更高效地使用網絡資源,我們將數據流從控制流中分離出來。控制流從客戶端到達主副本,再到其他所有次級副本的這一過程,數據是在認真挑選的chunkserver鏈上以流水線形式被線性推送的。我們的目的是充分利用每臺機器的網絡帶寬,來避免網絡瓶頸和高延遲的鏈路,同時最小化推送所有數據的延遲

爲了充分利用每臺機器的網絡帶寬,數據是沿着一條chunkserver鏈被線性推送的,而不是像其他拓撲結構的分佈那樣(例如樹型結構)。因此,每一臺機器全部的出站帶寬都被用來儘快地傳送數據,而不是用於爲多個接收者進行切分

爲了儘可能的避免網絡瓶頸和高延遲鏈路(例如,通常情況下的內部交換鏈路),每一臺機器都將數據轉發到網絡拓撲結構中尚未接收到數據的離他“最近”的機器。假設客戶端將數據發送給chunkserver S1到S4,首先會將數據發送到離它最近的機器,比如S1。S1接下來就將數據轉發到S2~S4中離它最近的機器,比如S2。類似地,S2再轉發給S3或S4,看哪一個離S2更近,等等。我們的網絡拓撲可以簡單到能夠從ip地址準確地估算“距離”

最後,我們通過在TCP連接上使用流水線來傳送數據以最小化延遲。一旦chunkserver接收到一部分數據,就會立刻開始轉發。流水線這裏對我們來說尤其有用,因爲我們使用了全雙工鏈路的交換網絡。立即發送數據並不會降低接收速率,在沒有網絡擁塞的情況下,將B個字節發送到R個副本上經過的時間理想情況下是B/R + RL,其中T是網絡的吞吐量, L是兩臺機器間傳送字節的延遲。我們的網絡鏈路一般是100Mbps(T),L遠低於1ms。因此,理想情況下,1MB的數據可以在80ms內完成分發

3.3 原子性的記錄追加

GFS提供了一個名爲record append的原子性追加操作。在傳統的寫操作中,客戶端指定數據寫入的位置。在同一區域的併發寫操作是不可串行化的:該區域最終可能包含來自多個客戶端的數據段。然而在一個記錄追加操作中,客戶端僅僅需要指定數據,GFS至少會將其原子性的(即,作爲一個連續的字節序列)追加到文件中至少一次,追加的位置由GFS選擇,並會向客戶端返回這個偏移量。這很像Unix中O_APPEND的文件打開模式,當多個寫入者併發操作時不會產生競爭條件

我們的分佈式應用程序大量使用了記錄追加操作,不同機器上的多個客戶端併發地向同一個文件中進行數據的追加。如果使用傳統的寫入操作,客戶端將會額外需要複雜且昂貴的同步開銷,例如分佈式鎖管理器。在我們的工作負載中,這樣的文件通常會作爲多生產者/單消費者隊列,或是不同客戶端的歸併結果

記錄追加是一種數據變更,除了一些主副本上的額外邏輯操作之外,依然會遵循3.1節中所述的控制流。客戶端將數據推送給文件最後一個塊的所有副本後,會向主副本發送請求,主副本會檢查當前塊記錄的追加操作是否導致塊超過了最大大小(64MB),如果超過了,就將塊填充到最大大小,並通知其餘副本也這麼做,然後告訴客戶端應在下一個塊上進行重試(追加的記錄被限制爲最大塊大小的1/4,以將最壞情況下的碎片控制在一個可接受的水平上)。一般情況下,記錄的大小都不會超過最大塊大小,如果在這種情況下,主副本所在節點會將數據追加到它的副本上,並讓其餘的副本節點將數據寫在它們控制的副本中確定的偏移量處,最後返回給客戶端一個成功應答

如果任何副本上的記錄追加操作失敗,客戶端會對操作進行重試。因此,同一個塊的副本上的數據可能包含了不同的數據,這些數據可能包含了一份同樣記錄的全部或部分的重複值。GFS不保證所有副本是字節層面一致的,它僅能保證數據能被作爲原子單位寫入至少一次。這個特性很容易從對那些成功響應的報告中簡單地觀察出來:數據一定被寫入到某些塊所有副本的相同偏移位置處,此外,所有副本至少和記錄結束的長度相同。因此,之後的任何記錄都會被分配到更大的偏移位置處,或是不同的塊上,即使有另一個不同的副本成爲了主副本。就我們的一致性保證而言,成功執行寫入數據的記錄追加操作的區域是已定義的(所以是一致的),而介於其間是不一致的(所以是未定義的)。我們的應用程序可以如2.7.2節所述中來處理這種不一致的區域

3.4 快照

快照操作幾乎可以在瞬間生成文件或目錄樹(“源文件”)的一份副本,同時會盡可能地避免中斷任何正在進行的數據變更。我們的用戶使用快照來迅速創建大型數據集的分支副本(通常是遞歸地拷貝這些副本),或是在調整實驗前爲當前狀態建立檢查點,以便可以在之後方便地進行提交或回滾

例如AFS[5],我們使用標準的copy-on-write(寫時複製)技術來實現快照。當master收到一份快照的請求時,會首先撤銷那些在即將進行快照的文件塊上的未完成的租約。這種行爲確保了這些塊上任何連續的寫操作都需要與主機交互,以找到租約的持有者,這首先給了master有一個創建塊副本的機會

在租約被撤銷或過期之後,master會將操作以日誌形式記錄到磁盤中。然後,master會通過拷貝源文件或目錄樹的元數據來將此日誌記錄應用到它的內存狀態中。新創建的快照文件會指向與源文件相同的塊

客戶端在快照生效後首次對塊C進行寫入時,會向master發送請求來尋找當前租約的持有者。master注意到塊C的引用計數大於1,會推遲對客戶端請求的響應,並選擇一個新的塊句柄C’。然後會讓擁有塊C副本的每一個chunkserver都創建一個新的塊,叫做C’。通過在與原塊相同的chunkserver上創建新塊,我們可以確保數據在本地進行拷貝,而不是通過網絡(我們磁盤的速度是100MB以太網鏈路的3倍左右)。從這一點來看,處理任何塊的請求都沒有什麼不同:master在新塊C’上爲其副本之一授予租約,並給客戶端回覆,使其可以正常地對塊進行寫入,而不知道這個塊是從已存在的塊剛創建出來的

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