天天看點

HDFS追本溯源:租約,讀寫過程的容錯處理及NN的主要資料結構

1.      lease 的機制:

hdfs支援write-once-read-many,也就是說不支援并行寫,那麼對讀寫的互斥同步就是靠lease實作的。lease說白了就是一個有時間限制的鎖。用戶端寫檔案時需要先申請一個lease,對應到namenode中的leasemanager,用戶端的client name就作為一個lease的holder,即租約持有者。leasemanager維護了檔案的path與lease的對應關系,還有clientname->lease的對應關系。leasemanager中有兩個時間限制:softlimitand hardlimit。

軟限制就是寫檔案時規定的租約逾時時間,硬限制則是考慮到檔案close時未來得及釋放lease的情況強制回收租約。

leasemanager中還有一個monitor線程來檢測lease是否超過hardlimit。而軟租約的逾時檢測則在dfsclient的leasechecker中進行。

當用戶端(dfsclient)create一個檔案的時候,會通過rpc 調用 namenode 的createfile方法來建立檔案。進而又調用fsnamesystem的startfile方法,又調用 leasemanager 的addlease方法為新建立的檔案添加一個lease。如果lease已存在,則更新該lease的lastupdate (最近更新時間)值,并将該檔案的path對應該lease上。之後dfsclient 将該檔案的path 添加 leasechecker中。檔案建立成功後,守護線程leasechecker會每隔一定時間間隔renew該dfsclient所擁有的lease。

leasemanagement是hdfs中的一個同步機制,用于保證同一時刻隻有一個client對一個檔案進行寫或建立操作。如當 建立一個檔案f時,client向namenode發起一個create請求,那麼leasemanager會想該client配置設定一個f檔案的 lease。client憑借該lease完成檔案的建立操作。此時其他client無法獲得f的當client長時間(預設為超過1min)不進行操作 時,發放的lease将被收回。

leasemanager主要完成兩部分工作:

檔案create,write,complete操作時,建立lease、更新時間戳、回收lease

一個背景線程定期檢查是否有過期的lease

leasemanager的代碼結構如下

其中lease表示一個租約,包括一個client(holder)所擁有的所有檔案鎖(paths)。

monitor是檢查是否有過期租約的線程。

leasemanager中有幾個主要資料結構:

leases(treemap<string, lease>):維護holder -> leased的映射集合

sortedleases (treeset): lease集合

sortedleasebypath(treemap<string, lease>): 維護paths->lease的映射集合

一、建立lease

當client向namenode發起create操作時,namenode.create()調用fsnamesystem.startfile()->fsnamesystem.startfileinternal(),該方法最終會調用 leasemanager.addlease(cons.clientname, src)來建立lease。

leaserecovery ——租約回收

leaserecovery時機

lease發放之後,在不用時會被回收,回收的産經除上述monitor線程檢測lease過期是回收外,還有:

namenode收到datanode的sync block command時

dfsclient主動關閉一個流時

建立檔案時,如果該dfsclient的lease超過soft limit時

1) namenode查找lease資訊

2) 對于lease中的每個檔案f,令b為f的最後一個block,作如下操作:

2.1) 擷取b所在的datanode清單

2.2) 令其中一個datanode作為primarydatanode p

2.3) p 從namenode擷取最新的時間戳

2.4) p 從每個datanode擷取block資訊

2.5) p 計算最小的block長度

2.6) p 用最小的block長度和最新的時間戳來更新具有有效時間戳的datanode

2.7) p 通知namenode更新結果

2.8) namenode更新blockinfo

2.9) namenode從lease中删除f,如果此時該lease中所有檔案都已被删除,将删除該lease

2.10) name送出修改的editlog

client續約 —— dfsclient.leasechecker

在namenode上的leasemanager.monitor線程負責檢查過期的lease,那麼client為了防止尚在使用的lease過期,需要定期想namenode發起續約請求。該任務有dfsclient中的leasechecker完成。

leasechecker結構如下:

其中pendingcreates是一個treemap<string, outputstream>用來維護src->目前正在寫入的檔案的dfsoutputstream的映射。

其核心是周期性(每個1s)調用run()方法來對租約過半的lease進行續約

namenode接收到renewlease請求後,調用fsnamesystem.renewlease()并最終調用leasemanager.renewlease()完成續約。

2.      機架感覺

hdfs機架感覺

        通常,大型 hadoop 叢集是以機架的形式來組織的,同一個機架上不同 節點間的網絡狀況比不同機架之間的更為理想。 另外, namenode 設法将 資料塊副本儲存在不同的機架上以提高容錯性。

         而 hdfs 不能夠自動判斷叢集中各個 datanode 的網絡拓撲情況 hadoop允 許叢集的管理者通過配置 dfs.network.script 參數來确定節點所處的機架。 檔案提供了 ip->rackid 的翻譯。 namenode 通過這個得到叢集中各個 datanode 機器的 rackid 。 如果 topology.script.file.name 沒有設定,則每個 ip 都會翻譯 成/ default-rack 。

        有了機架感覺, namenode 就可以畫出上圖所示的 datanode 網絡拓撲圖。d1,r1 都是交換機,最底層是 datanode 。 則 h1 的 rackid=/d1/r1/h1 , h1的 parent 是 r1 , r1 的是 d1 。 這些 rackid 資訊可以通過topology.script.file.name 配置。有了這些 rackid 資訊就可以計算出任意兩台datanode 之間的距離。

distance(/d1/r1/h1,/d1/r1/h1)=0  相同的 datanode

distance(/d1/r1/h1,/d1/r1/h2)=2  同一 rack 下的不同 datanode

distance(/d1/r1/h1,/d1/r1/h4)=4  同一 idc 下的不同 datanode

distance(/d1/r1/h1,/d2/r3/h7)=6   不同 idc 下的 datanode

3.     hdfs 檔案删除恢複機制

           當使用者或應用程式删除某個檔案時,這個檔案并沒有立刻從 hdfs 中删除。實際上, hdfs 會将這個檔案重命名轉移到 /trash 目錄。隻要檔案還在/trash 目錄中,該檔案就可以被迅速地恢複。檔案在 /trash 中儲存的時間是可 配置的,當超過這個時間時, namenode 就會将該檔案從名字空間中删除。 删除檔案會使得該檔案相關的資料塊被釋放。注意,從使用者删除檔案到 hdfs 空閑空間的增加之間會有一定時間的延遲。

            隻要被删除的檔案還在 /trash 目錄中,使用者就可以恢複這個檔案。如果 使用者想恢複被删除的檔案,他 / 她可以浏覽 /trash 目錄找回該檔案。 /trash 目 錄僅僅儲存被删除檔案的最後副本。 /trash 目錄與其他的目錄沒有什麼差別 ,除了一點:在該目錄上 hdfs 會應用一個特殊政策來自動删除檔案。目前 的預設政策是删除 /trash 中保留時間超過 6 小時的檔案。将來,這個政策可以 通過一個被良好定義的接口配置。

開啟資源回收筒

hdfs -site.xml

<configuration>

      <property>

              <name>fs.trash.interval</name>

               <value> 1440 </value>

               <description>number ofminutes betweentrash checkpoints.

                       if zero,the trashfeature is disabled.

               </description>

      </property>

</configuration>

1, fs.trash.interval 參數設定保留時間為 1440 秒 (1 天 )

2,   資源回收筒的位置:在 hdfs 上的  / user/$user/.trash/current/

4.     資料完整性

            從某個 datanode 擷取的資料塊有可能是損壞的,損壞可能是由datanode 的儲存設備錯誤、網絡錯誤或者軟體 bug 造成的。 hdfs 用戶端軟 件實作了對 hdfs 檔案内容的校驗和 (checksum) 檢查。當用戶端建立一個新 的hdfs 檔案,會計算這個檔案每個資料塊的校驗和,并将校驗和作為一個 單獨的隐藏檔案儲存在同一個 hdfs 名字空間下。當用戶端擷取檔案内容後 ,它會檢驗從datanode 擷取的資料跟相應的校驗和檔案中的校驗和是否匹 配,如果不比對,用戶端可以選擇從其他 datanode 擷取該資料塊的副本。

5.     修改副本數

1.叢集隻有三個datanode,hadoop系統replication=4時,會出現什麼情況?

       對于上傳檔案到hdfs上時,當時hadoop的副本系數是幾,這個檔案的塊數副本數就會有幾份,無論以後你怎麼更改系統副本系統,這個檔案的副本數都不 會改變,也就說上傳到分布式系統上的檔案副本數由當時的系統副本數決定,不會受replication的更改而變化,除非用指令來更改檔案的副本數。因為 dfs.replication實質上是client參數,在create檔案時可以指定具體replication,屬性dfs.replication是不指定具體replication時的采用預設備份數。檔案上傳後,備份數已定,修改dfs.replication是 不會影響以前的檔案的,也不會影響後面指定備份數的檔案。隻影響後面采用預設備份數的檔案。但可以利用hadoop提供的指令後期改某檔案的備份 數:hadoop fs-setrep -r 1。如果你是在hdfs-site.xml設定了dfs.replication,這并一定就得了,因為你可能沒把conf檔案夾加入到你的project的classpath裡,你的程式運作時取的dfs.replication可能是hdfs-default.xml裡的dfs.replication,預設是3。可能這個就是造成你為什麼dfs.replication老是3的原因。你可以試試在建立檔案時,顯式設定 replication。replication一般到3就可以了,大了意義也不大。

6.     hdfs的安全模式

          namenode 啟動後會進入一個稱為安全模式的特殊狀态。處于安全模式 的namenode 是不會進行資料塊的複制的。 namenode 從所有的 datanode 接收心跳信号和塊狀态報告。塊狀态報告包括了某個 datanode 所有的資料 塊清單。每個資料塊都有一個指定的最小副本數。當 namenode 檢測确認某 個資料塊的副本數目達到這個最小值,那麼該資料塊就會被認為是副本安全 (safely replicated) 的;在一定百分比(這個參數可配置)的資料塊被 namenode 檢測确認是安全之後(加上一個額外的 30 秒等待時間), namenode 将退出安全模式狀态。接下來它會确定還有哪些資料塊的副本沒 有達到指定數目,并将這些資料塊複制到其他 datanode上。

7.      讀過程分析

•使用hdfs提供的用戶端開發庫client,向遠端的namenode發起rpc請求;

• namenode會視情況傳回檔案的部分或者全部block清單,對于每個block,namenode都會傳回有該block拷貝的datanode位址;

•用戶端開發庫client會選取離用戶端最接近的datanode來讀取block;如果用戶端本身就是datanode,那麼将從本地直接擷取資料.

•讀取完目前block的資料後,關閉與目前的datanode連接配接,并為讀取下一個block尋找最佳的datanode;

•當讀完清單的block後,且檔案讀取還沒有結束,用戶端開發庫會繼續向namenode擷取下一批的block清單。

•讀取完一個block都會進行checksum驗證,如果讀取datanode時出現錯誤,用戶端會通知namenode,然後再從下一個擁有該block拷貝的datanode繼續讀。

8.     寫過程流程分析

•namenode會檢查要建立的檔案是否已經存在,建立者是否有權限進行操作,成功則會為檔案 建立一個記錄,否則會讓用戶端抛出異常;

•當用戶端開始寫入檔案的時候,會将檔案切分成多個packets,并在内部以資料隊列"data queue"的形式管理這些packets,并向namenode申請新的blocks,擷取用來存儲replicas的合适的datanodes清單, 清單的大小根據在namenode中對replication的設定而定。

•開始以pipeline(管道)的形式将packet寫入所有的replicas中。把packet以流的方式寫入第一個datanode, 該datanode把該packet存儲之後,再将其傳遞給在此pipeline中的下一個datanode,直到最後一個datanode,這種寫資料 的方式呈流水線的形式。

•最後一個datanode成功存儲之後會傳回一個ack packet,在pipeline裡傳遞至用戶端,在用戶端的開發庫内部維護着"ack queue",成功收到datanode傳回的ackpacket後會從"ackqueue"移除相應的packet。

•如果傳輸過程中,有某個datanode出現了故障,那麼目前的pipeline會被關閉,出現故障的datanode會從目前的pipeline中移除,剩餘的block會繼續剩下的datanode中繼續以pipeline的形式傳輸,同時namenode會配置設定一個新的datanode,保持replicas設定的數量。

流水線複制

              當用戶端向 hdfs 檔案寫入資料的時候,一開始是寫到本地臨時檔案中。假設該檔案的副 本系數設定為 3 ,當本地臨時檔案累積到一個資料塊的大小時,用戶端會從 namenode 擷取一個 datanode 清單用于存放副本。然後用戶端開始向第一個 datanode 傳輸資料,第一個 datanode 一小部分一小部分 (4 kb) 地接收資料,将每一部分寫入本地倉庫,并同時傳輸該部分到清單中 第二個 datanode節點。第二個 datanode 也是這樣,一小部分一小部分地接收資料,寫入本地 倉庫,并同時傳給第三個 datanode 。最後,第三個 datanode 接收資料并存儲在本地。是以, datanode 能流水線式地從前一個節點接收資料,并在同時轉發給下一個節點,資料以流水線的 方式從前一個 datanode 複制到下一個

更細節的原理

          用戶端建立檔案的請求其實并沒有立即發送給 namenode ,事實上,在剛開始階 段 hdfs 用戶端會先将檔案資料緩存到本地的一個臨時檔案。應用程式的寫操作被透 明地重定向到這個臨時檔案。當這個臨時檔案累積的資料量超過一個資料塊的大小 ,用戶端才會聯系 namenode 。 namenode 将檔案名插入檔案系統的層次結構中,并 且配置設定一個資料塊給它。然後傳回 datanode 的辨別符和目标資料塊給用戶端。接着 用戶端将這塊資料從本地臨時檔案上傳到指定的 datanode 上。當檔案關閉時,在臨 時檔案中剩餘的沒有上傳的資料也會傳輸到指定的 datanode 上。然後用戶端告訴 namenode 檔案已經關閉。此時 namenode 才将檔案建立操作送出到日志裡進行存儲 。如果 namenode 在檔案關閉前當機了,則該檔案将丢失。

整個寫流程如下:

        第一步,用戶端調用distributedfilesystem的create()方法,開始建立新檔案:distributedfilesystem建立dfsoutputstream,産生一個rpc調用,讓namenode在檔案系統的命名空間中建立這一新檔案;

        第二步,namenode接收到使用者的寫檔案的rpc請 求後,誰偶先要執行各種檢查,如客戶是否有相關的創佳權限和該檔案是否已存在等,檢查都通過後才會建立一個新檔案,并将操作記錄到編輯日志,然後distributedfilesystem會将dfsoutputstream對象包裝在fsdataoutstream執行個體中,傳回用戶端;否則檔案 建立失敗并且給用戶端抛ioexception。

        第三步,用戶端開始寫文 件:dfsoutputstream會将檔案分割成packets資料包,然後将這些packets寫到其内部的一個叫做dataqueue(資料隊列)。dataqueue會向namenode節點請求适合存儲資料副本的datanode節點的清單,然後這些datanode之前生成一個pipeline資料流管 道,我們假設副本集參數被設定為3,那麼這個資料流管道中就有三個datanode節點。

        第四步,首先dfsoutputstream會将packets向pipeline資料流管道中的第一個datanode節點寫資料,第一個datanode接收packets然後把packets寫向pipeline中的第二個節點,同理,第二個節點儲存接收到的資料然後将資料寫向pipeline中的第三個datanode節點。

        第五步,dfsoutputstream内部同樣維護另 外一個内部的寫資料确認隊列——ackqueue。當pipeline中的第三個datanode節點将packets成功儲存後,該節點回向第二個datanode傳回一個确認資料寫成功的 資訊,第二個datanode接收到該确認資訊後在目前節點資料寫成功後也會向pipeline中第一個datanode節點發送一個确認資料寫成功的信 息,然後第一個節點在收到該資訊後如果該節點的資料也寫成功後,會将packets從ackqueue中将資料删除。

        在寫資料的過程中,如果pipeline資料流管道中的一個datanode節點寫失敗了會發生什問題、需要做哪些内部處理呢?如果這種情況發生,那麼就會執行一些操作:

        首先,pipeline資料流管道會被關閉,ack queue中的packets會被添加到dataqueue的前面以確定不會發生packets資料包的丢失;

        接着,在正常的datanode節點上的以儲存好的block的id版本會更新——這樣發生故障的datanode節點上的block資料會在節點恢複正常後被删除,失效節點也會被從pipeline中删除;

        最後,剩下的資料會被寫入到pipeline資料流管道中的其他兩個節點中。

        如果pipeline中的多個節點在寫資料是發生失敗,那麼隻要寫成功的block的數量達到dfs.replication.min(預設為1),那麼就任務是寫成功的,然後namenode後通過一步的方式将block複制到其他節點,最後事資料副本達到dfs.replication參數配置的個數。

        第六步,,完成寫操作後,用戶端調用close()關閉寫操作,重新整理資料;

        第七步,,在資料重新整理完後namenode後關閉寫操作流。到此,整個寫操作完成。     

least recently used

9.      hdfs負載均衡

       hdfs的資料也許并不是非常均勻的分布在各個datanode中。一個常見的原因是在現有的叢集上經常會增添新的datanode節點。當新增一個 資料塊(一個檔案的資料被儲存在一系列的塊中)時,namenode在選擇datanode接收這個資料塊之前,會考慮到很多因素。其中的一些考慮的是:

•将資料塊的一個副本放在正在寫這個資料塊的節點上。

•盡量将資料塊的不同副本分布在不同的機架上,這樣叢集可在完全失去某一機架的情況下還能存活。

•一個副本通常被放置在和寫檔案的節點同一機架的某個節點上,這樣可以減少跨越機架的網絡i/o。

•盡量均勻地将hdfs資料分布在叢集的datanode中。

10.  基本資料結構

fsnamesystem是hdfs檔案系統實際執行的核心,提供各種增删改查檔案操作接口。其内部維護多個資料結構之間的關系:

fsname->block清單的映射

所有有效blocks集合

block與其所屬的datanodes之間的映射(該映射是通過block reports動态建構的,維護在namenode的記憶體中。每個datanode在啟動時向namenode報告其自身node上的block)

每個datanode與其上的blocklist的映射

采用心跳檢測根據lru算法更新的機器(datanode)清單

fsdirectory用于維護目前系統中的檔案樹。

其内部主要組成結構包括一個inodedirectorywithquota作為根目錄(rootdir)和一個fsimage來持久化檔案樹的修改操作。

hdfs中檔案樹用類似vfs中inode的方式建構,整個hdfs中檔案被表示為inodefile,目錄被表示為inodedirectory。inodediretorywithquota是inodedirectory的擴充類,即帶配額的檔案目錄

inodefile表示inode書中的一個檔案,擴充自inode,除了名字(name),父節點(parent)等之外,一個主要元素是blocks,一個blockinfo數組,表示該檔案對應的block資訊。

blocksmap用于維護block-> { inode, datanodes, self ref } 的映射 blocksmap結構比較簡單,實際上就是一個block到blockinfo的映射。

block是hdfs中的基本讀寫單元,主要包括:

blockid: 一個long類型的塊id

numbytes: 塊大小

generationstamp: 塊更新的時間戳

blockinfo擴充自block,除基本資訊外還包括一個inode引用,表示該block所屬的檔案;以及一個神奇的三元組數組object[] triplets,用來表示儲存該block的datanode資訊,假設系統中的備份數量為3。那麼這個數組結構如下:

dn1,dn2,dn3分别表示存有改block的三個datanode的引用(datanodedescriptor)

dn1-prev-blk表示在dn1上block清單中目前block的前置block引用

dn1-next-blk表示在dn1上block清單中目前block的後置block引用

dn2,dn3的prev-blk和next-blk類似。 hdfs采用這種結構存放block->datanodelist的資訊主要是為了節省記憶體空間,block->datanodelist之間的映射關系需要占用大量記憶體,如果同樣還要将datanode->blockslist的資訊儲存在記憶體中,同樣要占用大量記憶體。采用三元組這種方式能夠從其中一個block獲得到改 block所屬的datanode上的所有block清單。

fsimage用于持久化檔案樹的變更以及系統啟動時加載持久化資料。hdfs啟動時通過fsimage來加載磁盤中原有的檔案樹,系統standby之後,通過fseditlog來儲存在檔案樹上的修改,fseditlog定期将儲存的修改資訊刷到fsimage中進行持久化存儲。fsimage中檔案元資訊的存儲結構如下(參見fimage.savefsimage()方法)

layoutversion(int):image layout版本号,0.19版本的hdfs中為-18

namespaceid(int): 命名空間id,系統初始化時生成,在一個namenode生命周期内保持不變,datanode想namenode注冊是傳回改id作為 registerid,以後每次datanode與namenode通信時都攜帶該id,不認識的id的請求将被拒絕。

numberitemoftree(long): 系統中的檔案總數

generationtimestamp: 生成image的時間戳

 參考資料:

1. http://blog.csdn.net/cklsoft/article/details/8917899

2. http://www.iteye.com/topic/1126509

3. http://jiangbo.me/blog/2012/10/18/hdfs-namenode-lease-management/

4. http://flyingdutchman.iteye.com/blog/1900536

繼續閱讀