小白也能看懂的源碼分析系列(1)—HADOOP的NameNode啓動過程

小白也能看懂的源碼分析系列(1)—HADOOP的NameNode啓動過程

一、前言

​ HADOOP作爲大數據的基石,甚至是大數據的代名詞,各種耳熟能詳的框架基於HADOOP生態展開,發展日益迅速,HADOOP生態的完善,離不開HADOOP這個項目的偉大,作爲一名大數據方向的工程師或者研究人員,這是必須要熟悉的框架,想要進一步深入的理解它的偉大之處,外面必須要熟悉它的原理,原理從何而來?—源碼。我們這一節會以一個簡單的方式來分析hadoop的源碼,小白不要一看到源碼分析專題就頭疼避而遠之,這裏我保證你看得懂!

二、源碼正確打開方式

step1:

我們單純的分析NameNode源碼,沒必要把hadoop源碼包下載下來,如果下載全量源碼,對沒有fq條件的同學不友好,下載依賴會很麻煩,我們可以新建一個空的maven項目,把下面的依賴配到pom.xml文件裏面,再把對應的源碼下載下來

    <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.7.3</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>2.7.3</version>
    </dependency>

step2:

我們這次的目的是看NameNode源碼,使用idea的類查找快捷鍵Ctrl+N,再輸入NameNode既可查找到

在這裏插入圖片描述

step3:

我們點進去的只是idea反編譯後的文件,需要點擊右上角的下載源碼,這樣就能順利的查看了,我們在查看源碼的過程中,只要看到右上角有Download sources的提示,果斷點擊下載就行了

在這裏插入圖片描述

step4:

任何你在linux中通過jps命令看到的進程,NameNode、DataNode、NodeManger…一切看得到的,都是通過main方法啓動,所以,這是進入源碼的第一步,找到NameNode裏面的main方法,終於可以進入正題了

在這裏插入圖片描述

必須要知道的快捷鍵:

Ctrl + Alt + ← 表示返回上一步的位置;Ctrl + Alt + → 進入當前的下一步前提是你進入過下一步。不懂的話這兩個快捷鍵實操一下就知道什麼意思,分析源碼必須知道的這兩個快捷鍵!

三、分析NameNode啓動過程

進入main方法後,我們可以看到邏輯很簡單,小白也能看的懂,顯示解析傳入的參數,打印啓動的信息,最關鍵的一步是通過createNameNode(argv, null)方法新建NameNode對象

  public static void main(String argv[]) throws Exception {
    if (DFSUtil.parseHelpArgument(argv, NameNode.USAGE, System.out, true)) {
      System.exit(0);
    }

    try {
      StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
      // 創建NameNode對象
      NameNode namenode = createNameNode(argv, null);
      if (namenode != null) {
        namenode.join();
      }
    } catch (Throwable e) {
      LOG.error("Failed to start namenode.", e);
      terminate(1, e);
    }
  }

我們再進入createNameNode(argv, null)方法,重點在switch裏面,switch前面是一堆解析參數的邏輯,switch裏面會根據傳入的參數執行不同的邏輯,關於啓動參數,switch裏面第一個選項,我們最熟悉的也就是-format,也就是我們搭建集羣的時候會執行的hadoop namenode –format命令。默認不加參數的,也就是啓動NameNode,在最後的default邏輯裏面,會new NameNode()對象

public static NameNode createNameNode(String argv[], Configuration conf)
      throws IOException {
    LOG.info("createNameNode " + Arrays.asList(argv));
    if (conf == null)
      conf = new HdfsConfiguration();
    // 解析一般的參數,也就是我們熟悉的key-value的形式,會set到我們常用的Configuration.
    GenericOptionsParser hParser = new GenericOptionsParser(conf, argv);
    argv = hParser.getRemainingArgs();
    // 解析其它特殊的啓動參數,在下面的switch中
    StartupOption startOpt = parseArguments(argv);
    if (startOpt == null) {
      printUsage(System.err);
      return null;
    }
    setStartupOption(conf, startOpt);
	// 判斷 startOpt 啓動參數
    switch (startOpt) {
      // 我們最熟悉的format參數,格式化NameNode,等同於命令行hadoop namenode –format
      case FORMAT: {
        boolean aborted = format(conf, startOpt.getForceFormat(),
            startOpt.getInteractiveFormat());
        terminate(aborted ? 1 : 0);
        return null; // avoid javac warning
      }
      case GENCLUSTERID: {
        System.err.println("Generating new cluster id:");
        System.out.println(NNStorage.newClusterID());
        terminate(0);
        return null;
      }
      case FINALIZE: {
        System.err.println("Use of the argument '" + StartupOption.FINALIZE +
            "' is no longer supported. To finalize an upgrade, start the NN " +
            " and then run `hdfs dfsadmin -finalizeUpgrade'");
        terminate(1);
        return null; // avoid javac warning
      }
      case ROLLBACK: {
        boolean aborted = doRollback(conf, true);
        terminate(aborted ? 1 : 0);
        return null; // avoid warning
      }
      case BOOTSTRAPSTANDBY: {
        String toolArgs[] = Arrays.copyOfRange(argv, 1, argv.length);
        int rc = BootstrapStandby.run(toolArgs, conf);
        terminate(rc);
        return null; // avoid warning
      }
      case INITIALIZESHAREDEDITS: {
        boolean aborted = initializeSharedEdits(conf,
            startOpt.getForceFormat(),
            startOpt.getInteractiveFormat());
        terminate(aborted ? 1 : 0);
        return null; // avoid warning
      }
      case BACKUP:
      case CHECKPOINT: {
        NamenodeRole role = startOpt.toNodeRole();
        DefaultMetricsSystem.initialize(role.toString().replace(" ", ""));
        return new BackupNode(conf, role);
      }
      case RECOVER: {
        NameNode.doRecovery(startOpt, conf);
        return null;
      }
      case METADATAVERSION: {
        printMetadataVersion(conf);
        terminate(0);
        return null; // avoid javac warning
      }
      case UPGRADEONLY: {
        DefaultMetricsSystem.initialize("NameNode");
        new NameNode(conf);
        terminate(0);
        return null;
      }
      default: {
        // 默認進入啓動NameNode的邏輯
        DefaultMetricsSystem.initialize("NameNode");
        return new NameNode(conf);
      }
    }
  }

進入new NameNode(conf)我一路點進去,進入構造方法,重點方法在initialize(conf)

  protected NameNode(Configuration conf, NamenodeRole role) 
      throws IOException { 
    this.conf = conf;
    this.role = role;
    setClientNamenodeAddress(conf);
    String nsId = getNameServiceId(conf);
    String namenodeId = HAUtil.getNameNodeId(conf, nsId);
    // 從配置裏面獲取是否開啓了HA機制
    this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
    // 獲取當前namenode的狀態,總共有3中實現,ActiveState、StandbyState、BackupState
    state = createHAState(getStartupOption(conf));
    this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
    this.haContext = createHAContext();
    try {
      initializeGenericKeys(conf, nsId, namenodeId);
      // 初始化方法,主要邏輯都在這裏面,重點看這裏
      initialize(conf);
      try {
        haContext.writeLock();
        state.prepareToEnterState(haContext);
        state.enterState(haContext);
      } finally {
        haContext.writeUnlock();
      }
    } catch (IOException e) {
      this.stop();
      throw e;
    } catch (HadoopIllegalArgumentException e) {
      this.stop();
      throw e;
    }
    this.started.set(true);
  }

重點看initialize(conf)方法,進入此方法,重點邏輯方法:

  • loadNamesystem(conf)
    • 執行FSNamesystem的初始化邏輯,加載fsimage文件進內存
    • 初始化FSNamesystem時,會實例化BlockManagerBlockManager保存了Datanode塊的信息
  • startCommonServices(conf)
    • 啓動rpc server,包括clientRpcServer和serviceRpcServer,NameNode要跟其它服務進行rpc通信遠程調用其它服務的方法,所以是需要clientRpcServer的,這點不懂rpc的通信可以仔細瞭解下rpc的原理
    • 啓用blockManager,主要功能是兩個,第一是對block進行管理上報,第二是啓動datanodeManager,負責註冊到NameNode,管理了NameNodeDataNode的心跳連接線程與上下線
 protected void initialize(Configuration conf) throws IOException {
    if (conf.get(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS) == null) {
      String intervals = conf.get(DFS_METRICS_PERCENTILES_INTERVALS_KEY);
      if (intervals != null) {
        conf.set(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS,
          intervals);
      }
    }

    UserGroupInformation.setConfiguration(conf);
    // 如果使用kerberos做認證的話,這裏要使用配置的用戶登錄
    loginAsNameNodeUser(conf);
	// 這裏是初始化一些Metrics,做監控分析使用
    NameNode.initMetrics(conf, this.getRole());
    StartupProgressMetrics.register(startupProgress);

    if (NamenodeRole.NAMENODE == role) {
      startHttpServer(conf);
    }

    this.spanReceiverHost =
      SpanReceiverHost.get(conf, DFSConfigKeys.DFS_SERVER_HTRACE_PREFIX);
	// 這裏是很關鍵的一步,會從本地加載fsimage文件進內存,fsimage是hdfs核心的一部分,存放了hdfs全量的文件信息,我們平常看到的所有hdfs目錄信息,都在這個文件裏面記錄並持久化
    loadNamesystem(conf);
    // 啓動NameNode的rpcserver,用於與其它rpc客戶端進行交互做準備,大家都知道hadoop之前是通過rpc進行通信,這裏是關鍵的一步,裏面初始化並添加了一堆的Protocol協議
    rpcServer = createRpcServer(conf);
    if (clientNamenodeAddress == null) {
      // This is expected for MiniDFSCluster. Set it now using 
      // the RPC server's bind address.
      clientNamenodeAddress = 
          NetUtils.getHostPortString(rpcServer.getRpcAddress());
      LOG.info("Clients are to use " + clientNamenodeAddress + " to access"
          + " this namenode/service.");
    }
    if (NamenodeRole.NAMENODE == role) {
      httpServer.setNameNodeAddress(getNameNodeAddress());
      httpServer.setFSImage(getFSImage());
    }
    
    pauseMonitor = new JvmPauseMonitor(conf);
    pauseMonitor.start();
    metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
    // 這裏是很關鍵的第二步,會啓動rpc server,啓動blockManager
    startCommonServices(conf);
  }

到這裏,我們就不再繼續深入,裏面的邏輯相對複雜,可能需要花長篇大論來描述,讀者也可能不會有足夠的耐心讀下去,知道NameNode啓動的流程中大致做了哪些重要的事就行了,重點描述都在上面,有興趣的話可以繼續追蹤源碼,閱讀過程中沒必要每一行都懂,找到重點方法步入即可。想進一步深入瞭解,建議看完loadNamesystem(conf)startCommonServices(conf),會對NameNode啓動過程會有更深入的瞭解。

四、總結

本文寫的有點淺顯,主要目的是開個頭,帶讀者入個門,讓讀者知道,哦,原來Hadoop的源碼也就那麼回事,都看的懂。本教程是面向對java基礎、大數據基礎相對不那麼深入的童鞋,小白也能看懂的源碼分析系列我打算一直寫下去,保證人人都能看懂的情況下講解源碼分析過程。想要了解技術的本質,光會用是不行的,必須深入源碼分析框架的基礎原理,再高的房子都是由磚塊堆積的,房子的形狀千變萬化,萬變不離其宗的是蓋樓的轉頭!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章