HDFS namenode源碼分析


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的相關源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM