Namenode的介紹
一個典型的HDFS系統包括一個NameNode和多個DataNode。
NameNode作為HDFS中文件目錄和文件分配的管理者,它保存的最重要信息,就是下面兩個映射:
文件名=>數據塊
數據塊=>DataNode列表
其中,文件名=>數據塊保存在磁盤上(持久化);但NameNode上不保存數據塊=>DataNode列表,該列表是通過DataNode上報建立起來的。
NameNode啟動流程
在命令行啟動namenode的方法是:bin/hadoop namenode
查看bin/hadoop腳本,可以看到最后執行的java類是:org.apache.hadoop.hdfs.server.namenode.NameNode
NameNode的代碼骨架如下:
public class NameNode implements ClientProtocol, DatanodeProtocol,NamenodeProtocol { //操作hdfs文件系統的類 public FSNamesystem namesystem; /** RPC服務器,DFSClient,DataNode和Namenode通信都要通過它 */ private Server server; /** httpServer,平時我們在瀏覽器查看的hdfs的web管理控制台,就是通過它顯示的,它包裝了一個內嵌的jetty */ private HttpServer httpServer; public static NameNode createNameNode(String argv[], Configuration conf) throws IOException { ... StartupOption startOpt = parseArguments(argv); ... switch (startOpt) { case FORMAT: //首次啟動namenode要格式化,或者是重新初始化namenode boolean aborted = format(conf, true); System.exit(aborted ? 1 : 0); case FINALIZE: //完成升級hadoop,刪除備份 aborted = finalize(conf, true); System.exit(aborted ? 1 : 0); default: } ... //創建NameNode對象,接着會執行initialize方法初始化 NameNode namenode = new NameNode(conf); return namenode; } private void initialize(Configuration conf) throws IOException { ... //從fsimage和edits log加載元數據 this.namesystem = new FSNamesystem(this, conf); .... //創建RPCServer,默認的rpc線程數是10,默認端口是8020 this.server = RPC.getServer(this, socAddr.getHostName(), socAddr.getPort(), handlerCount, false, conf, namesystem .getDelegationTokenSecretManager()); startHttpServer(conf);//啟動http服務器,啟動后可以通過http://namenode:50070 訪問hdfs的管理頁面 .... this.server.start(); //啟動RPC server .... startTrashEmptier(conf);//啟動回收站清理線程,將過期的已刪除文件,真正刪除。 } public static void main(String argv[]) throws Exception { try { ... NameNode namenode = createNameNode(argv, null); if (namenode != null) namenode.join(); } ... } }
NameNode的啟動流程最復雜的就是FSNamesystem的初始化了,這個類是NameNode啟動的核心邏輯,而其他啟動邏輯都比較好懂。可以自行查看代碼。
org.apache.hadoop.hdfs.server.namenode.FSNamesystem具備了Namenode所提供基本服務的基礎上,也可以料想到它實現的復雜性。
FSNamesystem的分析
FSNamesystem是文件系統命名空間系統類,它的骨架成員如下:
public class FSNamesystem { //存儲文件樹 public FSDirectory dir; //BlocksMap類維護塊(Block)到其元數據的映射表,元數據信息包括塊所屬的inode、存儲塊的Datanode。 final BlocksMap blocksMap = new BlocksMap(DEFAULT_INITIAL_MAP_CAPACITY,DEFAULT_MAP_LOAD_FACTOR); //失效塊的映射表。 public CorruptReplicasMap corruptReplicas = new CorruptReplicasMap(); //datanode到塊的映射表 NavigableMap<String, DatanodeDescriptor> datanodeMap = new TreeMap<String, DatanodeDescriptor>(); //datanodeMap的子集,只包含認為存活的DatanodeDescriptor,HeartbeatMonitor會定期清除過期的元素 ArrayList<DatanodeDescriptor> heartbeats = new ArrayList<DatanodeDescriptor>(); //描述某些塊的副本數量不足塊的實體類,而且,對於塊設定了優先級,通過一個優先級隊列來管理塊副本不足的塊的集合。 private UnderReplicatedBlocks neededReplications = new UnderReplicatedBlocks(); //描述當前尚未完成塊副本復制的塊的列表。 private PendingReplicationBlocks pendingReplications; //對文件的租約進行管理。 public LeaseManager leaseManager = new LeaseManager(this); Daemon hbthread = null; // 周期性地調用FSNamesystem類定義的heartbeatCheck方法,來監視Datanode結點發送的心跳狀態信息,並做出處理 public Daemon lmthread = null; // LeaseMonitor thread Daemon smmthread = null; // 用來周期性地檢查是否達到離開安全模式的條件,因此,該線程必須在進入安全模式之后啟動(也就是達到threshold)。 public Daemon replthread = null; // 周期性調用兩個方法:計算塊副本數量,以制定計划並調度Datanode處理 ;處理未完成塊的流水線復制的副本 private ReplicationMonitor replmon = null; // Replication metrics //用來保存Datanode結點的主機 -> DatanodeDescriptor數組的映射 private Host2NodesMap host2DataNodeMap = new Host2NodesMap(); //表示一個具有樹狀網絡拓撲結構的計算機集群,例如,一個集群可能由多個數據中心(Data Center)組成,在這些數據中心分布着為計算需求而設置的很多計算機的機架(Rack)。 NetworkTopology clusterMap = new NetworkTopology(); //該接口是一個支持插件的定義,通過插件定義DNS-name/IP-address -> RackID之間轉換的解析器。 private DNSToSwitchMapping dnsToSwitchMapping; //對指定的塊副本的存放位置進行定位選擇的實現類。 ReplicationTargetChooser replicator; //用來跟蹤Datanode的,哪些Datanode允許連接到Namenode,哪些不能夠連接到Namenode,都在該類中指定的列表中記錄着 private HostsFileReader hostsReader; }
FSNamesystem的更多分析,參考 http://blog.csdn.net/shirdrn/article/details/4610578
FSNamesystem初始化的代碼骨架:
private void initialize(NameNode nn, Configuration conf) throws IOException { ... this.dir = new FSDirectory(this, conf); ... //從fsimage和edits加載元數據信息 this.dir.loadFSImage(getNamespaceDirs(conf), getNamespaceEditsDirs(conf), startOpt); ... this.hbthread = new Daemon(new HeartbeatMonitor()); //監視Datanode結點發送的心跳狀態信息的后台線程 this.lmthread = new Daemon(leaseManager.new Monitor());//對文件的租約進行管理后台線程。 this.replmon = new ReplicationMonitor(); this.replthread = new Daemon(replmon);//處理未完成塊的流水線復制的副本 hbthread.start(); lmthread.start(); replthread.start(); //從配置文件讀取datanode的黑白名單 this.hostsReader = new HostsFileReader(conf.get("dfs.hosts",""), conf.get("dfs.hosts.exclude","")); //處理退役節點,一般會把退役節點的塊做遷移 this.dnthread = new Daemon(new DecommissionManager(this).new Monitor( conf.getInt("dfs.namenode.decommission.interval", 30), conf.getInt("dfs.namenode.decommission.nodes.per.interval", 5))); dnthread.start(); this.dnsToSwitchMapping = ReflectionUtils.newInstance( conf.getClass("topology.node.switch.mapping.impl", ScriptBasedMapping.class, DNSToSwitchMapping.class), conf); if (dnsToSwitchMapping instanceof CachedDNSToSwitchMapping) { dnsToSwitchMapping.resolve(new ArrayList<String>(hostsReader.getHosts())); } ... }
下面重點介紹成員FSDirectory類,FSNamesystem的對於元數據操作文件,就是通過它完成。
FSDirectory的分析
FSDirectory保存文件名到文件塊的映射,並且把hdfs的修改寫入到磁盤。
要介紹FSDirectory,就要了解幾個類。
INode抽象類
該類是一個保存在內存中的file/block層次結構,一個基本的INode包含了文件和目錄inode的通用域(Field),如名字,父目錄,修改時間,訪問時間,權限 。
INodeDirectory類
INodeDirectory類繼承自INode,它表示目錄,可以想象得到,作為一個目錄,應該提供從目錄中檢索得到指定的INode的操作。
INodeFile類
INodeFile類繼承自INode,表示文件,正好與INodeDirectory相對應。它包含了文件對應的塊信息,塊大小,副本數。一個INodeFile類實例是不持有任何客戶端或者Datanode信息的,就是一個基本的實在的文件。
INodeFileUnderConstruction類
因為在HDFS集群中需要執行計算任務,這要涉及到塊的復制等操作,而某些塊需要由Namenode調度分派給指定的進程去執行,這就需要一種實體類,既能夠包含INodeFile的基本信息,又能夠包含與在該INodeFile上執行操作的進程,所以,Hadoop實現了一個INodeFileUnderConstruction類,並在INodeFile類中實現了由INodeFile到INodeFileUnderConstruction的轉換。
一個INodeFileUnderConstruction文件具有持有操作該文件的進程(客戶端)的一些信息,如果客戶端進程同時也是HDFS集群中Datanode,它就能夠根據租約的有效性來執行與該文件相關的操作,例如復制等。
FSDirectory類
到了這里,我們可以介紹FSDirectory類了。它是用來存儲文件系統目錄的狀態。它處理向磁盤中寫入或加載數據,並且對目錄中的數據發生的改變記錄到日志中。它保存了一個最新的filename->blockset的映射表,並且將它寫入到磁盤中。它的主要功能實現是成員FSImage fsImage完成。
它的核心屬性如下:
class FSDirectory implements FSConstants, Closeable { final INodeDirectoryWithQuota rootDir;// 具有配額限制的目錄INodeDirectory,這里即是hdfs的根目錄,但根目錄不做配額驗證 FSImage fsImage; // FSImage映像,管理元數據的序列化和反序列化 }
它的初始化代碼如下:
FSDirectory(FSNamesystem ns, Configuration conf) { this(new FSImage(), ns, conf); ... } FSDirectory(FSImage fsImage, FSNamesystem ns, Configuration conf) { rootDir = new INodeDirectoryWithQuota(INodeDirectory.ROOT_NAME, ns.createFsOwnerPermissions(new FsPermission((short)0755)), Integer.MAX_VALUE, -1); this.fsImage = fsImage; .... namesystem = ns; .... } //FSNamesystem在初始化完FSDirectory dir成員,會調用loadFSImage方法,從fsimage和edits加載元數據信息 void loadFSImage(Collection<File> dataDirs,Collection<File> editsDirs,StartupOption startOpt) throws IOException { // format before starting up if requested if (startOpt == StartupOption.FORMAT) {// 如果啟動選項類型為FORMAT(格式化),在啟動之前需要進行格式化 fsImage.setStorageDirectories(dataDirs, editsDirs);// 設置FSImage映像文件文件的存儲目錄:${dfs.name.dir},默認是/tmp/hadoop/dfs/name,是一個目錄數組。 fsImage.format();// 對FSImage執行格式化操作 startOpt = StartupOption.REGULAR; } try { if (fsImage.recoverTransitionRead(dataDirs, editsDirs, startOpt)) { // 根據啟動選項及其對應存儲目錄(${dfs.name.dir}),分析存儲目錄,必要的話從先前的事務恢復過來 fsImage.saveNamespace(true); } FSEditLog editLog = fsImage.getEditLog(); assert editLog != null : "editLog must be initialized"; if (!editLog.isOpen()) editLog.open(); fsImage.setCheckpointDirectories(null, null); } ... }
通過loadFSImage方法,我們可以看到加載一個FSImage映像的過程:首先需要對內存中的FSImage對象進行格式化;然后從將指定存儲目錄中的EditLog日志文件作用到格式化完成的FSImage內存映像上;最后需要再創建一個空的EditLog日志准備記錄對命名空間進行修改的操作,以備檢查點進程根據需要將EditLog內容作用到FSImage映像上,保持FSImage總是最新的,保證EditLog與FSImage同步。
FSDirectory的更多分析參考 http://blog.csdn.net/shirdrn/article/details/4631518
總結
上面將了namenode相關的核心類的成員和初始化流程,這里總結下namenode的代碼調用邏輯:
hdfs的目錄和文件的創建,刪除,還有文件的讀寫,追加,都是客戶端通過rpc,調用namenode的接口。
接着namenode調用成員FSNamesystem namesystem完成文件的操作,namesystem會做租約的管理,網絡拓撲的控制,文件權限的控制等。
接着namesystem調用成員FSDirectory dir操作,dir會做文件名到文件塊的映射管理。
接着dir調用成員FSImage fsImage操作,fsImage會hdfs的所有變化,追加寫入了EditLog,做持久化。
Secondrary Namenoe會定時(默認是一個小時)把namenode的EditLog和fsimage合並為一個fsimage,減少EditLog的文件大小。
本文只講解namenode核心類的職責和調用邏輯,細節請自行查看hadoop的相關源碼。