簡介
本文是筆者在學習HDFS的時候的學習筆記整理, 將HDFS的核心功能的原理都整理在這里了。
【廣告】 如果你喜歡本博客,請點此查看本博客所有文章:http://www.cnblogs.com/xuanku/p/index.html
HDFS的基礎架構
見下圖, 核心角色: Client, NameNode, Secondary NameNode, DataNode
-
Client: 對用戶提供系列操作工具&API
-
NameNode:
- 包含
map<filename, list<block_id>>
, 以及map<block_id, list<DataNode>>
的數據結構 - 資源分配算法
- 包含
-
DataNode:
- 管理好自己的磁盤, 上報數據給NameNode
讀取過程
- Client向NameNode讀取數據分布式信息
- Client找到第一個數據塊離自己最近的DataNode
- 跟這個DataNode交互並獲取數據
- 讀完之后開始跟下一個數據塊離自己最近的DataNode交互
- 讀完之后close連接
- 如果讀取過程中讀取失敗, 將會依次讀取該數據塊下一個副本, 失敗的節點將被記錄, 不再連接
近的判斷標准(NetworkTopology.sortByDistance):
- 如果客戶端和某個Datanode在同一台機器上, 優先
- 如果客戶端和某個Datanode在同一個rack上, 次優先
- 否則隨機
詳情請參考下圖:
參考:
寫入過程
流程
- 客戶端通知NameNode創建目錄
- 客戶端開始寫數據, 先寫到本地, 然后定期分塊
- 要寫新塊的時候再跟NameNode打交道, 獲取到新塊的目標地址
- 同一個數據塊的不同副本是鏈式同步, 客戶端只跟第一個副本打交道
- 只有所有副本都寫入成功, 才開始下一個塊的寫操作
- 如果有一個寫失敗, 則:
- 失敗的DataNode會加一個標記, 根據這個標記, 這份不完全的數據回頭會被刪除
- 不再往失敗的DataNode上面寫, 其他兩個DataNode繼續寫
- 告訴NameNode這份數據副本數不足, NameNode回頭會異步的補上
- 如果副本數少於某個配置(比如1個), 整個寫入就算失敗
詳情參考下圖:
副本分配策略
-
選擇一個本地節點
-
選擇一個本地機架節點
-
選擇一個遠程節點
-
隨機選擇一個節點
-
調整同步順序(以節約帶寬為目標)
參考: http://www.linuxidc.com/Linux/2012-01/50864.htm
如何感知機架位
通過NameNode的一個配置: topology.script.file.name 來控制的, 該配置的值對應一個腳本, 腳本輸入是一個IP/字符串, 輸出一個機架位名稱, 該名稱可以用"/xx/xx"的樹形結構來表示網絡拓撲。
如果沒有配置該值, 則代表所有機器一個機架位, 會增加機器網絡帶寬消耗。
參考: http://www.cnblogs.com/ggjucheng/archive/2013/01/03/2843015.html
HDFS Append文件邏輯
首先要說明兩個概念, block和replica, 在NN中叫block, 在DN中叫replica。
block有四種狀態:
- complete. block的長度和時間戳都不再變化, 並且至少有一個DN對應的rpelica是finalized的狀態。
- under_construction. 文件被create和append的時候, 該block就處於under_construction的狀態。該狀態下文件是可以被讀取的, 讀取的長度是保證在所有的DN上副本都能讀取到的長度;
- under_recovery. 如果一個文件的最后一個block在under_construction的狀態時, client異常掉線了, 那么需要有一段時間的lease過期和恢復釋放鎖和關閉文件的過程, 這段時間之內該block處於under_recovery的狀態.
- committed. 介於under_construction和complete之間的狀態。client收到所有DN寫成功的ARK, 但是NN還沒有收到任何一個DN報replica已經finalized的狀態。
replica狀態要復雜一些:
- Finalized. 寫完事兒之后的狀態。
- rbw(replica being written). 類似under_construction, 在創建和寫入過程中的replica。同under_construction, 也是可以被讀取的;
- rwr(rpelica waiting recovry). 異常之后, 等待lease過期的狀態;
- rur(replica under recovery). 異常之后, lease過期之后修復數據的狀態;
- Temporary. 類似rbw, 但是不是正常寫的狀態, 是比如集群間balance的狀態。跟rbw不同的是, 同步中的文件不可讀;
參考: http://yanbohappy.sinaapp.com/?p=175
SecondaryNameNode機制
- SecondaryNameNode不是說NameNode掛了的備用節點
- 他的主要功能只是定期合並日志, 防止日志文件變得過大
- 合並過后的鏡像文件在NameNode上也會保存一份
SecondaryNameNode工作過程:
- SecondaryNameNode向NameNode發起同步請求, 此時NameNode會將日志都寫到新的日志當中
- SecondaryNameNode向NameNode下載鏡像文件+日志文件
- SecondaryNameNode開始Merge這兩份文件並生成新的鏡像文件
- SecondaryNameNode向NameNode傳回新的鏡像文件
- NameNode文件將新的鏡像文件和日志文件替換成當前正在使用的文件
詳情請參考下圖:
BackupNode機制
跟Mysql的Master-Slave機制類似, BackupNode是作為熱備而存在, 同步更新NameNode節點的數據。
NameNode HA機制
NameNode數據主要包含兩類:
-
map<filename, list<block_id>>
即數據源信息這一類數據主要存儲在兩個文件中:
fsimg
,editlog
, 如上所說SecondaryNameNode的作用就是定期merge這兩個文件。在HA機制中, 流程為將editlog寫入一個共享存儲, 一般為QJM(Quorum Journal Manager)節點, 一般為3個節點。active NN的editlog實時寫入qjm節點, standby的NN定期從editlog中同步數據到自己的節點信息當中。
每次日志都有一個自增的epoch_id, jn會對比自己已有的epoch_id和NN給的epoch_id, 如果NN給的epoch_id比較小, 則會忽略該命令, 以此方式來達到避免腦裂問題。
NN通過ZKFC(ZooKeeper Fail Controller)來控制當前到底是哪個節點為ActiveNameNode, 在每個NN上都單獨啟動了一個ZKFC的進程, 該進程一方面監控NN的狀態信息, 另一方面跟ZK保持連接說明自己的狀態。類似租約, 一旦NN節點掛了, ZK會自動更新該節點狀態信息, 同時通知另外的節點, 另外的節點就好接替前一個NN的工作。
-
map<block_id, list<DataNode>>
即block_id和DataNode的對應關系這個本身信息是通過DataNode給NameNode上報來做到的, 增加了節點之后, DataNode跟每個NameNode都會上報一份信息。
類似qjm, DataNode也會維護一個自增的id, 當有NN切換的時候, 也會增加這個id, DN會拒絕比當前id還要小的控制命令。
HA的數據流圖如下:
參考:
- 當前HDFS HA介紹. http://www.it165.net/admin/html/201407/3465.html
- HDFS HA進化論. http://www.bubuko.com/infodetail_124006.html
HDFS Federation
本身實現不復雜, 就是將原來所有信息都放在一個NameNode節點里, 變成了可以將NameNode信息拆分放到多個節點里, 一人分一個目錄。
注意有一個block pool的概念, 為了避免在分配DataNode上的打架, 為每個NameNode分配了一個專屬的block pool, 這樣大家就分開了, 需要一開始配置自己的NameNode需要多少空間, 即Namespace Volume。
如下圖:
參考:
- http://zh.hortonworks.com/blog/an-introduction-to-hdfs-federation/
- http://blog.csdn.net/strongerbit/article/details/7013221/
- https://issues.apache.org/jira/secure/attachment/12453067/high-level-design.pdf
HDFS distcp
distcp1:
-
利用mapreduce來傳輸文件
-
為了保證文件內block的有序性, 所以map最小粒度為文件
問題出來了, 大文件會拖慢小文件, 導致整個拷貝效率不行
distcp2:
針對distcp1, 做了一些優化:
- 去掉了一些不必要的目錄檢查工作, 從而縮短了目錄檢查時間;
- 動態分配map task的工作量, 在運行過程中調整自己的任務量, 能優化部分distcp1的情況;
- 可對拷貝進行限速
- 支持HSFTP
fastcopy:
最主要是對federation機制的支持, 如果使用fastcp在同一個集群中不同的federation進行拷貝的時候, 則不需要再走一遍網絡和刪除, 只修改源數據即可, 但是distcp不行。
facebook的hadoop版本已經將fastcopy的這個特性集成到了distcp當中。
參考:
HDFS Balancer
是一個腳本, 該腳本做的事情很簡單:
- 從NameNode獲取DataNode數據分布信息
- 計算數據移動方案
- 執行移動方案
- 直到滿足平衡要求
平衡要求是該腳本的一個輸入(0~100), 代表不同機器的磁盤利用率差值。
注意:
- Balancer程序現在設計是不會將一個rack的數據移動到另外一個rack
- 也就是說跨rack的均衡是不能滿足的, 除非有修改后的Balancer程序
- 一般不要將Balancer放到NameNode上運行
數據流圖:
參考:
- balancer介紹. http://www.aboutyun.com/thread-7354-1-1.html
HDFS 快照
快照是給當前hdfs內容建立一個只讀備份, 可以針對整個hdfs或者某個目錄創建, 一般用於備份, 故障恢復, 避免人工故障等。
HDFS的快照有如下特點:
- 快照是瞬間創造的, 如果拋開inode的查詢時間, 只需要O(1)
- 快照創建以后需要額外的內存來存儲變化, 內存需要是O(M)
- 快照只是記錄了block_list和文件大小信息, 不做任何的實際數據拷貝
- 快照不會影響到現在數據的增刪改查, 查詢快照的時候, 會根據當前結果以及記錄的日志做減法來獲取快照數據
HDFS NFSv3
就是在本地通過掛載NFS訪問HDFS的文件
參考: http://blog.csdn.net/dmcpxy/article/details/18257065
HDFS dfsadmin
參考: http://blog.csdn.net/xiaojin21cen/article/details/42610697
hdfs 權限控制
認證:
支持兩種方式, simple和kerberos, 通過hadoop.security.authentication
這個類配置。默認是simple模式, simple模式下, 用戶名為whoami
, 組名為bash -c group
kerberos沒有研究。
授權:
針對每個目錄, 有讀寫一級可執行的權限設置, 權限有分為用戶級別, 組級別, 以及其他。
同時也可以通過設置ACL來針對一個目錄的某些用戶設置特殊權限。
參考: http://demo.netfoucs.com/skywalker_only/article/details/40709447
代碼閱讀筆記
讀取文件信息
客戶端代碼
- DFSClient.getFileInfo
- 到ClientProtocol類里查看getFileInfo
是接口, 用ctl+T查看其子類列表, 找到ClientNamenodeProtocolTranslatorPB類 - 再看ClientNamenodeProtocolTranslatorPB.getFileInfo函數
看到其調用了rpcProxy.getFileInfo類, 找rpcProxy的來源, 發現是構造函數傳進來的, 所以用ctl+alt+H來找到其反向調用關系 - NameNodeProxies.createNNProxyWithClientProtocol函數
發現在該函數中調用了RPC.getProtocolProxy函數來獲取該proxy, 在獲得該proxy的時候傳入了系列NN服務相關配置, 以及SocketFactory - 到RPC.getProtocolProxy函數
發現其是調用了getProtocolEngine(XX).getProxy(XX)的函數, 那么先看setProtocolEngine函數 - 到RPC.setProtocolEngine函數中看
發現其都設置的是RpcEngine的子類, 那么去看該RpcEngine的接口, 發現其實一個interface, 老辦法, 用ctl+T看其子類, 找到ProtobufRpcEngine子類 - 找ProtobufRpcEngine.getProxy函數
發現其主要是使用了自己的一個Invoker的類, 該Invoker是一個動態代理類, 主要關注其invoke函數即可 - 看ProtobufRpcEngine.Invoker.invoke函數
該函數就是各種網絡操作了, 可以看到他是將函數名拼成了一個字節流, 然后發給了NN, 然后hang住等待NN的返回結果
服務端代碼
- ClientProtocol類是用來通信的類, 客戶端和服務端都會用到, 直接看其子類即可
看到其子類有一個NameNodeRpcServer, 看起來肯定就是NN服務端這頭的代碼了 - NameNodeRpcServer.getFileInfo
發現其實調用了namesystem.getFileInfo函數 - namesystem.getFileInfo函數
一層一層往下面調用, 就可以找到其最終的邏輯了
NN啟動代碼
直接進入NameNode.main查看
- 參數獲取和查看我們略過, 我們着重關注后面的邏輯
- createNameNode
調用了構造函數: NameNode(conf), conf已經根據argv參數初始化好了
該函數又調用了NameNode.initialize函數 - initialize(conf)
- 啟動了http接口, 先啟動http接口應該是說可以通過http接口來查看啟動狀態
- 調用loadNamesystem函數
- 該函數又調用了FSNamesystem.loadNamesystem函數
FSNamesystem就是非常重要的類了, 基本上所有的NN的核心數據結構都放在這個類里面了 - 主要關注該類的loadFSImage函數
- namenode.join()
參考文章
- hdfs流程簡介. http://www.cnblogs.com/forfuture1978/archive/2010/03/14/1685351.html
- hdfs vs kfs. http://blog.csdn.net/Cloudeep/article/details/4467238
- hdfs的缺陷. http://www.cnblogs.com/wycg1984/archive/2010/03/20/1690281.html
- hdfs配置. http://cqfish.blog.51cto.com/622299/207766
- hdfs看分布式文件系統設計需求. http://dennis-zane.javaeye.com/blog/228537
- 利用Java API訪問hdfs文件. http://blog.csdn.net/zhangzhaokun/article/details/5597433
- hdfs重大性能殺手——shell. http://blog.csdn.net/fly542/article/details/6819945
- DataNode的stale狀態. http://www.tuicool.com/articles/RneQve
- hdfs源碼閱讀. http://www.linuxidc.com/Linux/2012-03/55966.htm