分佈式基礎學習(1)--分佈式文件系統

分佈式基礎學習html

所謂分佈式,在這裏,很狹義的指代以Google的三駕馬車,GFSMap/ReduceBigTable爲 框架核心的分佈式存儲和計算系統。一般如我同樣初學的人,會以Google這幾份經典的論文做爲開端的。它們勾勒出了分佈式存儲和計算的一個基本藍圖,已 可窺見其幾分風韻,但終究仍是因爲缺乏一些實現的代碼和示例,色彩有些斑駁,缺乏了點感性。幸虧咱們還有Open Source,還有Hadoop。Hadoop是 一個基於Java實現的,開源的,分佈式存儲和計算的項目。做爲這個領域最富盛名的開源項目之一,它的使用者也是大牌如雲,包括了 Yahoo,Amazon,Facebook等等(好吧,還可能有校內,不過這真的沒啥份量...)。Hadoop自己,實現的是分佈式的文件系統 HDFS,和分佈式的計算(Map/Reduce)框架,此外,它還不是一我的在戰鬥,Hadoop包含一系列擴展項目,包括了分佈式文件數據庫HBase(對應Google的BigTable),分佈式協同服務ZooKeeper(對應Google的Chubby),等等。。。java

 

如此,一個看上去不錯的黃金搭檔浮出水面,Google的論文 + Hadoop的實現,順着論文的框架看具體的實現,用實現來進一步理解論文的邏輯,看上去至少很美。網上有不少前輩們,作過Hadoop相關的源碼剖析工做,我關注最多的是這裏,目前博主已經完成了HDFS的剖析工做,Map/Reduce的剖析正火熱進行中,更新頻率之高,剖析之詳盡,都是可貴一見的,因此,走過路過必定不要錯過了。此外,還有不少Hadoop的關注者和使用者貼過相關的文章,好比:這裏這裏。也能夠去Hadoop的中文站點(不知是民間仍是官方...),蒐羅一些學習資料。。。node

 

我 我的從上述資料中受益不淺,而我本身要作的整理,與原始的源碼剖析有些不一樣,不是依照實現的模塊,而是基於論文的脈絡和實現這樣系統的基本脈絡來進行的, 也算,從另外一個角度給出一些東西吧。鑑於我的對於分佈式系統的理解很是的淺薄,缺乏足夠的實踐經驗,深刻的問題就不班門弄斧了,僅作梳理和解析,大牛至 此,可繞路而行了。。。算法

分佈式文件系統數據庫

分 布式文件系統,在整個分佈式系統體系中處於最低層最基礎的地位,存儲嘛,沒了數據,再好的計算平臺,再完善的數據庫系統,都成了無水之舟了。那麼,什麼是 分佈式文件系統,顧名思義,就是分佈式+文件系統。它包含這兩個方面的內涵,從文件系統的客戶使用的角度來看,它就是一個標準的文件系統,提供了一系列 API,由此進行文件或目錄的建立、移動、刪除,以及對文件的讀寫等操做。從內部實現來看,分佈式的系統則再也不和普通文件系統同樣負責管理本地磁盤,它的 文件內容和目錄結構都不是存儲在本地磁盤上,而是經過網絡傳輸到遠端系統上。而且,同一個文件存儲不僅是在一臺機器上,而是在一簇機器上分佈式存儲,協同 提供服務,正所謂分佈式。。。apache

 

因 此,考量一個分佈式文件系統的實現,其實不妨能夠從這兩方面來分別剖析,然後合二爲一。首先,看它如何去實現文件系統所需的基本增刪改查的功能。而後,看 它如何考慮分佈式系統的特色,提供更好的容錯性,負載平衡,等等之類的。這兩者合二爲一,就明白了一個分佈式文件系統,總體的實現模式。。。數組

I. 術語對照緩存

說 任何東西,都須要統一一下語言先,否則明明說的一個意思,卻容易被理解到另外一個地方去。Hadoop的分佈式文件系統HDFS,基本是按照Google論 文中的GFS的架構來實現的。可是,HDFS爲了彰顯其不走尋常路的本性,其中的大量術語,都與GFS大相徑庭。明明都是一個枝上長的土豆,它恰恰就要叫 山藥蛋,弄得水火不容的,苦了咱們看客。秉承老好人,誰也不得罪的方針,文中,既不採用GFS的叫法,也不採用Hadoop的稱謂,而是另闢蹊徑,自立門 戶,搞一套本身的中文翻譯,爲了不沒必要要的痛楚,特此先來一帖術語對照表,要不懂查一查,包治百病。。。安全

 

文中所用翻譯服務器

HDFS中的術語

GFS中的術語

術語解釋

主控服務器

NameNode

Master

整個文件系統的大腦,它提供整個文件系統的目錄信息,而且管理各個數據服務器。

數據服務器

DataNode

Chunk Server

分佈式文件系統中的每個文件,都被切分紅若干個數據塊,每個數據塊都被存儲在不一樣的服務器上,此服務器稱之爲數據服務器。

數據塊

Block

Chunk

每一個文件都會被切分紅若干個塊,每一塊都有連續的一段文件內容,是存儲的基恩單位,在這裏統一稱作數據塊。

數據包

Packet

客戶端寫文件的時候,不是一個字節一個字節寫入文件系統的,而是累計到必定數量後,往文件系統中寫入一次,每發送一次的數據,都稱爲一個數據包。

傳輸塊

Chunk

在每個數據包中,都會將數據切成更小的塊,每個塊配上一個奇偶校驗碼,這樣的塊,就是傳輸塊。

備份主控服務器

SecondaryNameNode

備用的主控服務器,在身後默默的拉取着主控服務器 的日誌,等待主控服務器犧牲後被扶正。

 

*注:本文采用的Hadoop是0.19.0版本。

II. 基本架構

1. 服務器介紹

與 單機的文件系統不一樣,分佈式文件系統不是將這些數據放在一塊磁盤上,由上層操做系統來管理。而是存放在一個服務器集羣上,由集羣中的服務器,各盡其責,通 力合做,提供整個文件系統的服務。其中重要的服務器包括:主控服務器(Master/NameNode),數據服務器(ChunkServer /DataNode),和客戶服務器。HDFS和GFS都是按照這個架構模式搭建的。我的以爲,其中設計的最核心內容是:文件的目錄結構獨立存儲在一個主 控服務器上,而具體文件數據,拆分紅若干塊,冗餘的存放在不一樣的數據服務器上。

 

存 儲目錄結構的主控服務器,在GFS中稱爲Master,在HDFS中稱爲NameNode。這兩個名字,叫得都有各自的理由,是瞎子摸象各表一面。 Master是之於數據服務器來叫的,它作爲數據服務器的領導同志存在,管理各個數據服務器,收集它們的信息,瞭解全部數據服務器的生存現狀,而後給它們 分配任務,指揮它們齊心合力爲系統服務;而NameNode是針對客戶端來叫的,對於客戶端而言,主控服務器上放着全部的文件目錄信息,要找一個文件,必 須問問它,由此而的此名。。。

 

主 控服務器在整個集羣中,同時提供服務的只存在一個,若是它不幸犧牲的話,會有後備軍馬上前赴後繼的跟上,但,同一時刻,須要保持一山不容二虎的態勢。這種 設計策略,避免了多臺服務器間即時同步數據的代價,而同時,它也使得主控服務器極可能成爲整個架構的瓶頸所在。所以,儘可能爲主控服務器減負,否則它作太多 的事情,就天然而然的晉升成了一個分佈式文件系統的設計要求。。。

 

每 一個文件的具體數據,被切分紅若干個數據塊,冗餘的存放在數據服務器。一般的配置,每個數據塊的大小爲64M,在三個數據服務器上冗餘存放(這個 64M,不是隨便得來的,而是通過反覆實踐獲得的。由於若是太大,容易形成熱點的堆疊,大量的操做集中在一臺數據服務器上,而若是過小的話,附加的控制信 息傳輸成本,又過高了。所以沒有比較特定的業務需求,能夠考慮維持此配置...)。數據服務器是典型的四肢發達頭腦簡單的苦力,其主要的工做模式就是按期 向主控服務器彙報其情況,而後等待並處理命令,更快更安全的存放好數據。。。

 

此 外,整個分佈式文件系統還有一個重要角色是客戶端。它不和主控服務和數據服務同樣,在一個獨立的進程中提供服務,它只是以一個類庫(包)的模式存在,爲用 戶提供了文件讀寫、目錄操做等APIs。當用戶須要使用分佈式文件系統進行文件讀寫的時候,把客戶端相關包給配置上,就能夠經過它來享受分佈式文件系統提 供的服務了。。。

2. 數據分佈

一 個文件系統中,最重要的數據,其實就是整個文件系統的目錄結構和具體每一個文件的數據。具體的文件數據被切分紅數據塊,存放在數據服務器上。每個文件數據 塊,在數據服務器上都表徵爲出雙入隊的一對文件(這是普通的Linux文件),一個是數據文件,一個是附加信息的元文件,在這裏,不妨把這對文件簡稱爲數 據塊文件。數據塊文件存放在數據目錄下,它有一個名爲current的根目錄,而後裏面有若干個數據塊文件和從dir0-dir63的最多64個的子目 錄,子目錄內部結構等同於current目錄,依次類推(更詳細的描述,參見這裏)。我的以爲,這樣的架構,有利於控制同一目錄下文件的數量,加快檢索速度。。。

 

這 是磁盤上的物理結構,與之對應的,是內存中的數據結構,用以表徵這樣的磁盤結構,方便讀寫操做的進行。Block類用於表示數據塊,而FSDataset 類是數據服務器管理文件塊的數據結構,其中,FSDataset.FSDir對應着數據塊文件和目錄,FSDataset.FSVolume對應着一個數 據目錄,FSDataset.FSVolumeSet是FSVolume的集合,每個FSDataset有一個FSVolumeSet。多個數據目錄, 能夠放在不一樣的磁盤上,這樣有利於加快磁盤操做的速度。相關的類圖,能夠參看這裏 。。。

 

此 外,與FSVolume對應的,還有一個數據結構,就是DataStorage,它是Storage的子類,提供了升級、回滾等支持。但與 FSVolume不同,它不須要了解數據塊文件的具體內容,它只知道有這麼一堆文件放這裏,會有不一樣版本的升級需求,它會處理怎麼把它們升級回滾之類的 業務(關於Storage,能夠參見這裏)。而FSVolume提供的接口,都基本上是和Block相關的。。。

 

相 比數據服務器,主控服務器的數據量不大,但邏輯更爲複雜。主控服務器主要有三類數據:文件系統的目錄結構數據,各個文件的分塊信息,數據塊的位置信息(就 數據塊放置在哪些數據服務器上...)。在GFS和HDFS的架構中,只有文件的目錄結構和分塊信息纔會被持久化到本地磁盤上,而數據塊的位置信息則是通 過動態彙總過來的,僅僅存活在內存數據結構中,機器掛了,就灰飛煙滅了。每個數據服務器啓動後,都會向主控服務器發送註冊消息,將其上數據塊的情況都告 知於主控服務器。俗話說,簡單就是美,根據DRY原則,保存的冗餘信息越少,出現不一致的可能性越低,付出一點點時間的代價,換取了一大把邏輯上的簡單 性,絕對應該是一個包賺不賠的買賣。。。

 

在 HDFS中,FSNamespacesystem類就負責保管文件系統的目錄結構以及每一個文件的分塊情況的,其中,前者是由FSDirectory類來負 責,後者是各個INodeFile自己維護。在INodeFile裏面,有一個BlockInfo的數組,保存着與該文件相關的全部數據塊信 息,BlockInfo中包含了從數據塊到數據服務器的映射,INodeFile只須要知道一個偏移量,就能夠提供相關的數據塊,和數據塊存放的數據服務 器信息。。。

三、服務器間協議

在 Hadoop的實現中,部署了一套RPC機制,以此來實現各服務間的通訊協議。在Hadoop中,每一對服務器間的通訊協議,都定義成爲一個接口。服務端 的類實現該接口,而且創建RPC服務,監聽相關的接口,在獨立的線程處理RPC請求。客戶端則能夠實例化一個該接口的代理對象,調用該接口的相應方法,執 行一次同步的通訊,傳入相應參數,接收相應的返回值。基於此RPC的通訊模式,是一個消息拉取的流程,RPC服務器等待RPC客戶端的調用,而不會先發制 人主動把相關信息推送到RPC客戶端去。。。

其 實RPC的模式和原理,實在是沒啥好說的,之因此說,是由於能夠經過把握好這個,完全理順Hadoop各服務器間的通訊模式。Hadoop會定義一些列的 RPC接口,只須要看誰實現,誰調用,就能夠知道誰和誰通訊,都作些啥事情,圖中服務器的基本架構、各服務所使用的協議、調用方向、以及協議中的基本內 容。。。

 

III. 基本的文件操做

基 本的文件操做,能夠分紅兩類,一個是對文件目錄結構的操做,好比文件和目錄的建立、刪除、移動、改名等等;另外一個是對文件數據流的操做,包括讀取和寫入文 件數據。固然,文件讀和寫,是有本質區別的,尤爲是在數據冗餘的狀況下,所以,當成兩類操做也不足爲過。此外,要具體到讀寫的類別,也是能夠再繼續分類下 去的。在GFS的論文中,對於分佈式文件系統的讀寫場景有一個重要的假定(實際上是從實際業務角度得來的...):就是文件的讀取是由大數據量的連續讀取和 小數據量的隨機讀取組成,文件的寫入則基本上都是批量的追加寫,和偶爾的插入寫(GFS中還有大量的假設,它們構成了分佈式文件系統架構設計的基石。每一 個系統架構都是搭建在必定假設上的,這些假設有些來自於實際業務的情況,有些是由於天生的條件約束,不基於假設理解設計,確定會有失偏頗...)。在 GFS中,對文件的寫入分紅追加寫和插入寫都有所支持,可是,在HDFS中僅僅支持追加寫,這大大下降了複雜性。關於HDFS與GFS的一些不一樣,能夠參 看這裏。。。

1. 文件和目錄的操做

文 件目錄的信息,所有囤積在主控服務器上,所以,全部對文件目錄的操做,只會直接涉及到客戶端和主控服務器。整個目錄相關的操做流程基本都是這樣的:客戶端 DFSClient調用ClientProtocol定義的相關函數,該操做經過RPC傳送到其實現者主控服務器NameNode那裏,NameNode 作相關的處理後(不多...),調用FSNamesystem的相關函數。在FSNamesystem中,每每是作一些驗證和租約操做,具體的目錄結構操 做交由FSDirectory的相應函數來操做。最後,依次返回,經由RPC傳送回客戶端。具體各操做涉及到的函數和具體步驟,參見下表:

相關操做

ClientProtocol / NameNode

FSNamesystem

FSDirectory

關鍵步驟

建立文件

create

startFile

addFile

1. 檢查是否有寫權限;
2. 檢查是否已經存在此文件,若是是覆寫,則先進行刪除操做;
3. 在指定路徑下添加INodeFileUnderConstruction的文件實例;
4. 寫日誌;
5. 簽定租約。

建立目錄

mkdirs

mkdirs

mkdirs

1. 檢查指定目錄是不是目錄;
2. 檢查是否有相關權限;
3. 在指定路徑的INode下,添加子節點;
4. 寫日誌。

更名操做

rename

renameTo

renameTo

1. 檢查相關路徑的權限;
2. 從老路徑下移除,在新路徑下添加;
3. 修改相關父路徑的修改時間;
4. 寫日誌;
5. 將租約從老路徑移動到新路徑下。

刪除操做

delete

delete

delete

1. 若是不是遞歸刪除,確認指定路徑是不是空目錄;
2. 檢查相關權限;
3. 在目錄結構上移除相關INode;
4. 修改父路徑的修改時間;
5. 將相關的數據塊,放入到廢棄隊列中去,等待處理;
6. 寫日誌;
7. 廢棄相關路徑的租約。

設置權限

setPermission

setPermission

setPermission

1. 檢查owner判斷是否有操做權限;
2. 修改指定路徑下INode的權限;
3. 寫日誌。

設置用戶

setOwner

setOwner

setOwner

1. 檢查是否有操做權限;
2. 修改指定路徑下INode的權限;
3. 寫日誌。

設置時間

setTimes

setTimes

setTimes

1. 檢查是否有寫權限;
2. 修改指定路徑INode的時間信息;
3. 寫日誌。

 

從 上表能夠看到,其實有的操做本質上仍是涉及到了數據服務器,好比文件建立和刪除操做。可是,以前提到,主控服務器只於數據服務器是一個等待拉取的地位,它 們不會主動聯繫數據服務器,將指令傳輸給它們,而是放到相應的數據結構中,等待數據服務器來取。這樣的設計,能夠減小通訊的次數,加快操做的執行速 度。。。

另,上述步驟中,有些日誌和租約相關的操做,從概念上來講,和目錄操做其實沒有任何聯繫,可是,爲了知足分佈式系統的需求,這些操做是很是有必要的,在此,按下不表。。。

二、文件的讀取

不管是文件讀取,仍是文件的寫入,主控服務器扮演的都是中介的角色。客戶端把本身的需求提交給主控服務器,主控服務器挑選合適的數據服務器,介紹給客戶端,讓客戶端和數據服務器單聊,要讀要寫隨大家便。這種策略相似於DMA,下降了主控服務器的負載,提升了效率。。。

 

因 此,在文件讀寫操做中,最主要的通訊,發生在客戶端與數據服務器之間。它們之間跑的協議是ClientDatanodeProtocol。從這個協議中 間,你沒法看到和讀寫相關的接口,由於,在Hadoop中,讀寫操做是不走RPC機制的,而是另立門戶,獨立搭了一套通訊框架。在數據服務器一 端,DataNode類中有一個DataXceiverServer類的實例,它在一個單獨的線程等待請求,一旦接到,就啓動一個DataXceiver 的線程,處理這次請求。一個請求一個線程,對於數據服務器來講,邏輯上很簡單。當下,DataXceiver支持的請求類型有六種,具體的請求包和回覆包 格式,請參見這裏這裏這裏。在Hadoop的實現中,並無用類來封裝這些請求,而是按流的次序寫下來,這給代碼閱讀帶來挺多的麻煩,也對代碼的維護帶來必定的困難,不知道是出於何種考慮。。。

 

相 比於寫,文件的讀取實在是一個簡單的過程。在客戶端DFSClient中,有一個DFSClient.DFSInputStream類。當須要讀取一個文 件的時候,會生成一個DFSInputStream的實例。它會先調用ClientProtocol定義getBlockLocations接口,提供給 NameNode文件路徑、讀取位置、讀取長度信息,從中取得一個LocatedBlocks類的對象,這個對象包含一組LocatedBlock,那裏 面有所規定位置中包含的全部數據塊信息,以及數據塊對應的全部數據服務器的位置信息。當讀取開始後,DFSInputStream會先嚐試從某個數據塊對 應的一組數據服務器中選出一個,進行鏈接。這個選取算法,在當下的實現中,很是簡單,就是選出第一個未掛的數據服務器,並無加入客戶端與數據服務器相對 位置的考量。讀取的請求,發送到數據服務器後,天然會有DataXceiver來處理,數據被一個包一個包發送回客戶端,等到整個數據塊的數據都被讀取完 了,就會斷開此連接,嘗試鏈接下一個數據塊對應的數據服務器,整個流程,依次如此反覆,直到全部想讀的都讀取完了爲止。。。

三、文件的寫入

文件讀取是一個一對一的過程,一個客戶端,只須要與一個數據服務器聯繫,就能夠得到所需的內容。可是,寫入操做,則是一個一對多的流程。一次寫入,須要在全部存放相關數據塊的數據服務器都保持同步的更新,有任何的差池,整個流程就告失敗。。。

 

在 分佈式系統中,一旦涉及到寫入操做,併發處理不免都會淪落成爲一個變了相的串行操做。由於,若是不一樣的客戶端若是是任意時序併發寫入的話,整個寫入的次序 沒法保證,可能你寫半條記錄我寫半條記錄,最後出來的結果亂七八糟不可估量。在HDFS中,併發寫入的次序控制,是由主控服務器來把握的。當建立、續寫一 個文件的時候,該文件的節點類,由INodeFile升級成爲 INodeFileUnderConstruction,INodeFileUnderConstruction是INodeFile的子類,它起到一個 鎖的做用。若是當一個客戶端想建立或續寫的文件是INodeFileUnderConstruction,會引起異常,由於這說明這個此處有爺,請另尋高 就,從而保持了併發寫入的次序性。同時,INodeFileUnderConstruction有包含了此時正在操做它的客戶端的信息以及最後一個數據塊 的數據服務器信息,當追加寫的時候能夠更快速的響應。。。

 

與 讀取相似,DFSClient也有一個DFSClient.DFSOutputStream類,寫入開始,會建立此類的實例。 DFSOutputStream會從NameNode上拿一個LocatedBlock,這裏面有最後一個數據塊的全部數據服務器的信息。這些數據服務器 每個都須要可以正常工做(對於讀取,只要還有一個能工做的就能夠實現...),它們會依照客戶端的位置被排列成一個有着最近物理距離和最小的序列(物理 距離,是根據機器的位置定下來的...),這個排序問題相似於著名旅行商問題,屬於NP複雜度,可是因爲服務器數量很少,因此用最粗暴的算法,也並不會看 上去不美。。。

 

文 件寫入,就是在這一組數據服務器上構形成數據流的雙向流水線。DFSOutputStream,會與序列的第一個數據服務器創建Socket鏈接,發送請 求頭,而後等待迴應。DataNode一樣是創建DataXceiver來處理寫消息,DataXceiver會依照包中傳過來的其餘服務器的信息,創建 與下一個服務器的鏈接,並生成相似的頭,發送給它,並等待回包。此流程依次延續,直到最後一級,它發送回包,反向着逐級傳遞,再次回到客戶端。若是一切順 利,那麼此時,流水線創建成功,開始正式發送數據。數據是分紅一個個數據包發送的,全部寫入的內容,被緩存在客戶端,當寫滿64K,會被封裝成 DFSOutputStream.Packet類實例,放入DFSOutputStream的dataQueue隊列。 DFSOutputStream.DataStreamer會時刻監聽這個隊列,一旦不爲空,則開始發送,將位於dataQueue隊首的包移動到 ackQueue隊列的隊尾,表示已發送但還沒有接受回覆的包隊列。同時啓動ResponseProcessor線程監聽回包,直到收到相應回包,纔將發送 包從ackQueue中移除,表示成功。每個數據服務器的DataXceiver收到了數據包,一邊寫入到本地文件中去,一邊轉發給下一級的數據服務 器,等待回包,同前面創建流水線的流程。。。

 

當一個數據塊寫滿了以後,客戶端須要向主控服務器申請追加新的數據塊。這個會引發一次數據塊的分配,成功後,會將新的數據服務器組返還給客戶端。而後從新回到上述流程,繼續前行。。。

關於寫入的流程,還能夠參見這裏。此外,寫入涉及到租約問題,後續會仔細的來講。。。

IV. 分佈式支持

如 果單機的文件系統是田裏勤懇的放牛娃,那麼分佈式文件系統就是刀尖上討飯吃的馬賊了。在分佈式環境中,有太多的意外,數據隨時傳輸錯誤,服務器時刻準備犧 牲,不少日常稱爲異常的現象,在這裏都須要按照日常事來對待。所以,對於分佈式文件系統而言,僅僅是知足了正常情況下文件系統各項服務還不夠,還須要保證 分佈式各類意外場景下健康持續的服務,不然,將一無可取。。。

一、服務器的錯誤恢復

在分佈式環境中,哪臺服務器犧牲都是常見的事情,犧牲不可怕,可怕的是你都沒有時刻準備好它們會犧牲。做爲一個合格的分佈式系統,HDFS固然時刻準備好了前赴後繼奮勇向前。HDFS有三類服務器,每一類服務器出錯了,都有相應的應急策略。。。

a. 客戶端

生 命最輕如鴻毛的童鞋,應該就是客戶端了。畢竟,作爲一個文件系統的使用者,在整個文件系統中的地位,不免有些歸於三流。而做爲客戶端,大部分時候,犧牲了 就犧牲了,沒人哀悼,無人同情,只有在在辛勤寫入的時候,不幸辭世(機器掛了,或者網絡斷了,諸如此類...),纔會引發些恐慌。由於,此時此刻,在主控 服務器上對應的文件,正做爲INodeFileUnderConstruction活着,僅僅爲佔有它的那個客戶端服務者,作爲一個專注的文件,它不容許 別的客戶端染指。這樣的話,一旦佔有它的客戶端服務者犧牲了,此客戶端會依然佔着茅坑不拉屎,讓如花似玉 INodeFileUnderConstruction孤孤單單守寡終身。這種事情固然沒法容忍,所以,必須有辦法解決這個問題,辦法就是:租約。。。

 

租約,顧名思義,就是當客戶端須要佔用某文件的時候,與主控服務器簽定的一個短時間合同。這個合同有一個期限,在這個期限內,客戶端能夠延長合同期限,一旦超過時限,主控服務器會強行終止此租約,將這個文件的享用權,分配給他人。。。

 

在 打開或建立一個文件,準備追加寫以前,會調用LeaseManager的addLease方法,在指定的路徑下與此客戶端簽定一份租約。客戶端會啓動 DFSClient.LeaseChecker線程,定時輪詢調用ClientProtocol的renewLease方法,續簽租約。在主控服務器一 端,有一個LeaseManager.Monitor線程,始終在輪詢檢查全部租約,查看是否有到期未續的租約。若是一切正常,該客戶端完成寫操做,會關 閉文件,中止租約,一旦有所意外,好比文件被刪除了,客戶端犧牲了,主控服務器都會剝奪此租約,如此,來避免因爲客戶端停機帶來的資源被長期霸佔的問 題。。。

b. 數據服務器

當 然,會掛的不僅是客戶端,海量的數據服務器是一個更不穩定的因素。一旦某數據服務器犧牲了,而且主控服務器被蒙在鼓中,主控服務器就會變相的欺騙客戶端, 給它們沒法鏈接的讀寫服務器列表,致使它們到處碰壁沒法工做。所以,爲了整個系統的穩定,數據服務器必須時刻向主控服務器彙報,保持主控服務器對其的徹底 瞭解,這個機制,就是心跳消息。在HDFS中,主控服務器NameNode實現了DatanodeProtocol接口,數據服務器DataNode會在 主循環中,不停的調用該協議中的sendHeartbeat方法,向NameNode彙報情況。在此調用中,DataNode會將其總體運行情況告知 NameNode,好比:有多少可用空間、用了多大的空間,等等之類。NameNode會記住此DataNode的運行情況,做爲新的數據塊分配或是負載 均衡的依據。當NameNode處理完成此消息後,會將相關的指令封裝成一個DatanodeCommand對象,交還給DataNode,告訴數據服務 器什麼數據塊要刪除什麼數據塊要新增等等之類,數據服務器以此爲本身的行動依據。。。

 

但 是,sendHeartbeat並無提供本地的數據塊信息給NameNode,那麼主控服務器就沒法知道此數據服務器應該分配什麼數據塊應該刪除什麼數 據塊,那麼它是如何決定的呢?答案就是DatanodeProtocol定義的另外一個方法,blockReport。DataNode也是在主循環中定時 調用此方法,只是,其週期一般比調用sendHeartbeat的更長。它會提交本地的全部數據塊情況給NameNode,NameNode會和本地保存 的數據塊信息比較,決定什麼該刪除什麼該新增,並將相關結果緩存在本地對應的數據結構中,等待此服務器再發送sendHeartbeat消息過來的時候, 依照這些數據結構中的內容,作出相應的DatanodeCommand指令。blockReport方法一樣也會返回一個DatanodeCommand 給DataNode,但一般,只是爲空(只有出錯的時候不爲空),我想,增長緩存,也許是爲了確保每一個指令均可以重複發送並肯定被執行。。。

c. 主控服務器

固然,做爲整個系統的核心和單點,含辛茹苦的主控服務器含淚西去,整個分佈式文件服務集羣將完全癱瘓**。如何在主控服務器犧牲後,提拔新的主控服務器並迅速使其進入工做角色,就成了系統必須考慮的問題。解決策略就是:日誌。。。


其 實這並非啥新鮮東西,一看就知道是從數據庫那兒偷師而來的。在主控服務器上,全部對文件目錄操做的關鍵步驟(具體文件內容所處的數據服務器,是不會被寫 入日誌的,由於這些內容是動態創建的...),都會被寫入日誌。另外,主控服務器會在某些時刻,將當下的文件目錄完整的序列化到本地,這稱爲鏡像。一旦存 有鏡像,鏡像前期所寫的日誌和其餘鏡像,都純屬冗餘,其歷史使命已經完成,能夠報廢刪除了。在主控服務器不幸犧牲,或者是戰略性的停機修整結束,並從新啓 動後,主控服務器會根據最近的鏡像 + 鏡像以後的全部日誌,重建整個文件目錄,迅速將服務能力恢復到犧牲前的水準。。。

 

對於數據服務器而言,它們會經過一些手段,迅速得知頂頭上司的更迭消息。它們會馬上轉投新東家的名下,在新東家旗下注冊,並開始向其發送心跳消息,這個機制,可能用分佈式協同服務來實現,這裏不說也罷。。。

 

在HDFS的實現中,FSEditLog類是整個日誌體系的核心,提供了一大堆方便的日誌寫入API,以及日誌的恢復存儲等功能。目前,它支持若干種日誌類型,都冠以OP_XXX,並提供相關API,具體能夠參見這裏。 爲了保證日誌的安全性,FSEditLog提供了EditLogFileOutputStream類做爲寫入的承載類,它會同時開若干個本地文件,而後依 次寫入,防止日誌的損壞致使不可估量的後果。在FSEditLog上面,有一個FSImage類,存儲文件鏡像並調用FSEditLog對外提供相關的日 志功能。FSImage是Storage類的子類,若是對數據塊的講述有所印象的話,你能夠回憶起來,凡事今後類派生出來的東西,都具備版本性質,能夠進 行升級和回滾等等,以此,來實現產生鏡像是對原有日誌和鏡像處理的複雜邏輯。。。

 

目 前,在HDFS的日誌系統中,有些地方與GFS的描述有所不一樣。在HDFS中,全部日誌文件和鏡像文件都是本地文件,這就至關於,把日誌放在自家的保險箱 中,一旦主控服務器掛了,別的後繼而上的服務器也沒法拿到這些日誌和鏡像,用於重振雄風。所以,在HDFS中,運行着一個 SecondaryNameNode服務器,它作爲主控服務器的替補,隱忍厚積薄發爲篡位作好準備,其中,核心內容就是:按期下載並處理日誌和鏡像。 SecondaryNameNode看上去像客戶端同樣,與NameNode之間,走着NamenodeProtocol協議。它會不停的查看主控服務器 上面累計日誌的大小,當達到閾值後,調用doCheckpoint函數,此函數的主要步驟包括:

 

首先是調用startCheckpoint作一些本地的初始化工做;

而後調用rollEditLog,將NameNode上此時操做的日誌文件從edit切到edit.new上來,這個操做瞬間完成,上層寫日誌的函數徹底感受不到差異;

接着,調用downloadCheckpointFiles,將主控服務器上的鏡像文件和日誌文件都下載到此候補主控服務器上來;

並調用doMerge,打開鏡像和日誌,將日誌生成新的鏡像,保存覆蓋;

下一步,調用putFSImage把新的鏡像上傳回NameNode;

再調用rollFsImage,將鏡像換成新的,在日誌從edit.new更名爲edit;

最後,調用endCheckpoint作收尾工做。

 

整 個算法涉及到NameNode和SecondaryNameNode兩個服務器,最終結果是NameNode和SecondaryNameNode都依照 算法進行前的日誌生成了鏡像。而兩個服務器上日誌文件的內容,前者是整個算法進行期間所寫的日誌,後者始終不會有任何日誌。當主控服務器犧牲的時候,運行 SecondaryNameNode的服務器馬上被扶正,在其上啓動主控服務,利用其日誌和鏡像,恢復文件目錄,並逐步接受各數據服務器的註冊,最終向外 提供穩定的文件服務。。。

同 樣的事情,GFS採用的多是另一個策略,就是在寫日誌的時候,並不侷限在本地,而是同時書寫網絡日誌,即在若干個遠程服務器上生成一樣的日誌。而後, 在某些時機,主控服務器本身,生成鏡像,下降日誌規模。當主控服務器犧牲,能夠在擁有網絡日誌的服務器上啓動主控服務,升級成爲主控服務器。。。

GFS與HDFS的策略相比較,前者是化整爲零,後者則是批量處理,一般咱們認爲,批量處理的平均效率更高一些,且相對而言,可能實現起來容易一些,可是,因爲有間歇期,會致使日誌的丟失,從而沒法100%的將備份主控服務器的狀態與主控服務器徹底同步。。。

二、數據的正確性保證

在 複雜紛繁的分佈式環境中,咱們堅決的相信,萬事皆有可能。哪怕各個服務器都舒舒服服的活着,也可能有各類各樣的狀況致使網絡傳輸中的數據丟失或者錯誤。並 且在分佈式文件系統中,同一份文件的數據,是存在大量冗餘備份的,系統必需要維護全部的數據塊內容徹底同步,不然,一人一言,不一樣客戶端讀同一個文件讀出 不一樣數據,用戶非得瘋了不可。。。

 

在 HDFS中,爲了保證數據的正確性和同一份數據的一致性,作了大量的工做。首先,每個數據塊,都有一個版本標識,在Block類中,用一個長整型的數 generationStamp來表示版本信息(Block類是全部表示數據塊的數據結構的基類),一旦數據塊上的數據有所變化,此版本號將向前增長。在 主控服務器上,保存有此時每一個數據塊的版本,一旦出現數據服務器上相關數據塊版本與其不一致,將會觸發相關的恢復流程。這樣的機制保證了各個數據服務器器 上的數據塊,在基本大方向上都是一致的。可是,因爲網絡的複雜性,簡單的版本信息沒法保證具體內容的一致性(由於此版本信息與內容無關,可能會出現版本相 同,但內容不一樣的情況)。所以,爲了保證數據內容上的一致,必需要依照內容,做出簽名。。。

 

當 客戶端向數據服務器追加寫入數據包時,每個數據包的數據,都會切分紅512字節大小的段,做爲簽名驗證的基本單位,在HDFS中,把這個數據段稱爲 Chunk,即傳輸塊(注意,在GFS中,Chunk表達的是數據塊...)。在每個數據包中,都包含若干個傳輸塊以及每個傳輸塊的簽名,當下,這個 簽名是根據Java SDK提供的CRC算法算得的,其實就是一個奇偶校驗。當數據包傳輸到流水線的最後一級,數據服務器會對其進行驗證(想想,爲何 只在最後一級作驗證,而不是每級都作...),一旦發現當前的傳輸塊簽名與在客戶端中的簽名不一致,整個數據包的寫入被視爲無 效,Lease Recover(租約恢復)算法被觸發。。。

 

從 基本原理上看,這個算法很簡單,就是取全部數據服務器上此數據塊的最小長度看成正確內容的長度,將其餘數據服務器上此數據塊超出此長度的部分切除。從正確 性上看,此算法無疑是正確的,由於至少有一個數據服務器會發現此錯誤,並拒絕寫入,那麼,若是寫入了的,都是正確的;從效率上看,此算法也是高效的,由於 它避免了重複的傳輸和複雜的驗證,僅僅是各自刪除尾部的一些內容便可。但從具體實現上來看,此算法稍微有些繞,由於,爲了下降本已不堪重負的主控服務器的 負擔,此算法不是由主控服務器這個大腦發起的,而是經過選舉一個數據服務器做爲Primary,由Primary發起,經過調用與其餘各數據服務器間的 InterDatanodeProtocol協議,最終完成的。具體的算法流程,參見LeaseManager類上面的註釋。須要說明的是此算法的觸發時 機和發起者。此算法能夠由客戶端或者是主控服務器發起,當客戶端在寫入一個數據包失敗後,會發起租約恢復。由於,一次寫入失敗,不管是何種緣由,頗有可能 就會致使流水線上有的服務器寫了,有的沒寫,從而形成不統一。而主控服務器發起的時機,則是在佔有租約的客戶端超出必定時限沒有續簽,這說明客戶端可能掛 了,在臨死前可能幹過不利於數據塊統一的事情,做爲監督者,主控服務器須要發起一場恢復運動,確保一切正確。。。

三、負載均衡

負 載的均衡,是分佈式系統中一個永恆的話題,要讓你們各盡其力齊心幹活,發揮各自獨特的優點,不能忙得忙死閒得閒死,影響戰鬥力。並且,負載均衡也是一個復 雜的問題,什麼是均衡,是一個很模糊的概念。好比,在分佈式文件系統中,總共三百個數據塊,平均分配到十個數據服務器上,就算均衡了麼?其實不必定,由於 每個數據塊須要若干個備份,各個備份的分佈應該充分考慮到機架的位置,同一個機架的服務器間通訊速度更快,而分佈在不一樣機架則更具備安全性,不會在一棵 樹上吊死。。。

 

在這裏說的負載均衡,是寬泛意義上的均衡過程,主要涵蓋兩個階段的事務,一個是在任務初始分配的時候儘量合理分配,另外一個是在過後時刻監督及時調整。。。

 

在 HDFS中,ReplicationTargetChooser類,是負責實現爲新分配的數據塊尋找婆家的。基本上來講,數據塊的分配工做和備份的數量、 申請的客戶端地址(也就是寫入者)、已註冊的數據服務器位置,密切相關。其算法基本思路是隻考量靜態位置信息,優先照顧寫入者的速度,讓多份備份分配到不 同的機架去。具體算法,自行參見源碼。此外,HDFS的Balancer類,是爲了實現動態的負載調整而存在的。Balancer類派生於Tool類,這 說明,它是以一個獨立的進程存在的,能夠獨立的運行和配置。它運行有NamenodeProtocol和ClientProtocol兩個協議,與主控服 務器進行通訊,獲取各個數據服務器的負載情況,從而進行調整。主要的調整其實就是一個操做,將一個數據塊從一個服務器搬遷到另外一個服務器上。 Balancer會向相關的目標數據服務器發出一個DataTransferProtocol.OP_REPLACE_BLOCK消息,接收到這個消息的 數據服務器,會將數據塊寫入本地,成功後,通知主控服務器,刪除早先的那個數據服務器上的同一塊數據塊。具體的算法請自行參考源碼。。。

四、垃圾回收

對 於垃圾,你們應該耳熟能詳了,在分佈式文件系統而言,沒有利用價值的數據塊備份,就是垃圾。在現實生活中,咱們提倡垃圾分類,爲了更好的理解分佈式文件系 統的垃圾收集,搞個分類也是頗有必要的。基本上,全部的垃圾均可以視爲兩類,一類是由系統正常邏輯產生的,好比某個文件被刪除了,全部相關的數據塊都淪爲 垃圾了,某個數據塊被負載均衡器移動了,原始數據塊也不幸成了垃圾了。此類垃圾最大的特色,就是主控服務器是生成垃圾的罪魁禍首,也就是說主控服務器徹底 瞭解有哪些垃圾須要處理。另外還有一類垃圾,是因爲系統的一些異常症狀產生的,好比某個數據服務器停機了一段,重啓以後發現其上的某個數據塊已經在其餘服 務器上從新增長了此數據塊的備份,它上面的那個備份過時了失去價值了,須要被看成垃圾來處理了。此類垃圾的特色偏偏相反,主控服務器沒法直接瞭解到垃圾狀 況,須要曲線救國。。。

 

在 HDFS中,第一類垃圾的斷定天然很容易,在一些正常的邏輯中產生的垃圾,所有被塞進了FSNamesystem的 recentInvalidateSets這個Map中。而第二類垃圾的斷定,則放在數據服務器發送其數據塊信息來的過程當中,通過與本地信息的比較,能夠 判定,此數據服務器上有哪些數據塊已經不幸淪爲垃圾。一樣,這些垃圾也被塞到recentInvalidateSets中去。在與數據服務器進行心跳交流 的過程當中,主控服務器會將它上面有哪些數據塊須要刪除,數據服務器對這些數據塊的態度是,直接物理刪除。在GFS的論文中,對如何刪除一個數據塊有着不一樣 的理解,它覺着應該先緩存起來,過幾天沒人想恢復它了再刪除。在HDFS的文檔中,則明確表示,在現行的應用場景中,沒有須要這個需求的地方,所以,直接 刪除就完了。這說明,理念是一切分歧的根本:)。。。

V. 總結整 個分佈式文件系統,計算系統,數據庫系統的設計理念,基本是一脈相承的。三類服務器、做爲單點存在的核心控制服務器、基於日誌的恢復機制、基於租約的保持 聯繫機制、等等,在後續分佈式計算系統和分佈式數據庫中均可以看到相似的影子,在分佈式文件系統這裏,我詳述了這些內容,可能在後續就會默認知道而說的比 較簡略了。而刨去這一些,分佈式文件系統中最大特色,就是文件塊的冗餘存儲,它直接致使了較爲複雜的寫入流程。固然,雖然說分佈式文件系統在分佈式計算和數 據庫中都有用到,但若是對其機理沒有興趣,只要把它當成是一個能夠在任何機器上使用的文件系統,就不會對其餘上層建築的理解產生障礙。。。

相關文章
相關標籤/搜索