1. 程式人生 > 程式設計 >Hadoop原始碼篇 --- NameNode的啟動流程解析

Hadoop原始碼篇 --- NameNode的啟動流程解析

前言

提醒一下,這裡面需要有RPC的基礎,如果對RPC沒有了解的朋友,可以先跳轉到以往寫的兩篇RPC文章中。

理論方面:從零開始的高併發(七)--- RPC的介紹,協議及框架

(可略過)程式碼方面:從零開始的高併發(八)--- RPC框架的簡單實現

當然也不需要太過深入,知道點皮毛即可。因為Hadoop中有一個Hadoop RPC需要有點基礎知識。

暫時先記得下面的滿足RPC的條件(非完整):

1.不同程式間的方法呼叫

2.RPC分為服務端和客戶端,客戶端呼叫服務端的方法,方法的執行是在服務端

3.協議說得直白點就是一個介面,但是這個介面必須存在versionID

4.協議裡面會存在抽象方法,這些抽象方法交由服務端實現

好的那我們開始

一、NameNode的啟動流程解析

我們的第一個任務,就是來驗證NameNode是不是一個RPC的服務端

1.1 第一步:找到NameNode的main方法

現在我們來到Hadoop2.7.0的原始碼,先開啟NameNode.java的程式碼,找到它的main方法

為了方便大家觀看,我再標好一些註釋

1.2 第二步:點開建立NameNode的createNameNode方法

這個createNameNode方法我們分成兩部分看

第一部分:傳參的部分

我們操作HDFS叢集時,會通過如下一些命令

hdfs namenode -format

hadoop=daemon.sh start namanode
複製程式碼

第二部分:switch部分

程式碼過長沒法截圖,直接複製下來

switch (startOpt) {
  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.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: {
    DefaultMetricsSystem.initialize("NameNode");
    return new NameNode(conf);
  }
複製程式碼

比如第一小段是hdfs namenode -format,它是一個format

自然走的就是這個分支啦。

但是現在我比如輸入的是start 也就是第二小段hadoop=daemon.sh start namanode,因為其他的都不滿足,所以走的就是最後一個分支

再點進去這個new NameNode(conf)

1.3 NameNode的初始化

點進去this方法,其實就是下面的那個

前面的一大截也不需要看,無非就是一些引數的傳遞問題,看這個try裡面的,有一個initialize,這個單詞的中文是初始化,那我們就再繼續點進去

1.4 初始化的具體步驟

1.4.1 HttpServer

前面的是判斷了一些奇奇怪怪的條件的暫時先不管,直接看到我們比較敏感的位置

此時我們看見建立HttpServer的程式碼了,我們如果是初次搭建我們的大資料叢集時,是不是會訪問一個50070的web頁面,比如

這裡雖然不是啥重要的流程,不過順便解釋一下50070怎麼來的

第二句開始就start了,所以該有的引數第一句肯定都有了,我們看看後面那個getHttpServerBindAddress(conf)

然後我們看到了這兩個引數,DFS_NAMENODE_HTTP_ADDRESS_KEY 和 DFS_NAMENODE_HTTP_ADDRESS_DEFAULT

DFS_NAMENODE_HTTP_ADDRESS_KEY是自己手動配置的地址,但是一般我們都沒有去手動配置,所以hadoop會使用一個預設的地址 DFS_NAMENODE_HTTP_ADDRESS_DEFAULT

看到這裡,就知道我們當時訪問那個網站為啥是本機ip加一個50070了吧。

1.4.2 對HttpServer2進行增強的servlet

此時我們回到第一張圖,也就是第二句就是start的那張startHttpServer方法,點進去start方法看看

我們往下看,作為一名Java Developer,我們的關注點自然就是我們熟悉的servlet了

setupServlets(httpServer,conf);
複製程式碼

這裡綁定了一堆的servlet,綁的越多功能越強,我們再點進去

可以看到就是瘋狂地add一些各式各樣的servlet,我們姑且先不看,其實servlet大家應該非常熟悉,點進去先啥都別想直接跳doGet()方法就能看到它們都分別做了什麼了

我們看到目前為止可以先畫一下我們的流程圖了

1.5 流程圖的第一步

首先我們現在是有一個HttpServer2(Hadoop自己封裝的HttpServer),它上面綁定了很多提供各種各樣功能的servlet

它對外提供一個50070的埠,瀏覽器傳送一個http://ip address:50070/listPaths的請求去請求servlet後,就會返回那個web頁面了

1.6 Hadoop RPC

回到1.4.1中的那張HttpServer的那個位置。在啟動HttpServer後,會載入元資料,但是我們現在模擬的場景是叢集第一次啟動,第一次啟動的時候是不存在元資料的,所以我們先直接跳過這個步驟,去到Hadoop RPC的位置

為什麼我會知道這個Hadoop RPC會有兩個這樣的服務,這個當然不是我猜的,也並不是我找過什麼資料或者點進去看過,而是在NameNode原始碼中告訴我們的

我們直接把這一段英文放到百度翻譯上看看

這裡它就說明瞭,NameNode不僅僅是個類,還是個伺服器,向外界公開一個IPC Server和一個Http server,補充說明一下,這個IPC Server就是讓開發者去進行命令操作的。這個Http Server就是那個開放了50070介面讓開發者瞭解HDFS的情況的。而FSNamessystem這個類是管理了HDFS的元資料的

現在我們知道了,這兩個服務一個是提供給內部的DataNode去呼叫,而另外一個是提供給服務端去呼叫的

1.6.1 驗證實現協議且協議中存在versionID

這裡的方法起名已經非常直接了,createRpcServer()哈哈哈,我們點選進去

這裡的註釋為寫了建立RPC服務端實現,它return了一個NameNodeRpcServer,那是不是它才是我們要找的NameNode服務端呢?

還記得我們開頭說判斷RPC的依據嗎,3.協議說得直白點就是一個介面,但是這個介面必須存在versionID,好的我們記住這句話,再點進NameNodeRpcServer

相信這就是我們想要看到的,它確實實現了一個NamenodeProtocols,感覺快要看到真相了是吧,再點進去

我去,繼承了這麼多個協議,怪不得我們的NameNode的功能如此強大,這裡面得有多少個方法啊,這時候我們隨便點進去一個介面去看

1.6.2 驗證存在設定引數的過程

滿足是一個介面,也有一個versionID了吧,我們現在覺得它就是服務端了,可是我們還沒有看見那些set伺服器地址啊,埠啊···等等這些引數的程式碼,所以還是不能一口咬定,所以我們現在再退回去到class NameNodeRpcServer裡面,拉到296行

再拉取到343行,發現又有一段類似的

還記得我們剛才說過,兩個服務一個是提供給內部的DataNode去呼叫,而另外一個是提供給服務端去呼叫的,所以這裡這倆,第一個serviceRpcServer是服務於NameNode和DataNode之間呼叫的,而第二個clientRpcServer是服務於客戶端與NameNode,DataNode進行互動呼叫的

而且在建立它們之後,會有很多的協議被新增進來,這些協議也是帶有許許多多的方法的,新增的協議越多,這兩個服務的功能也就越強大

所以它們和HttpServer的套路是一樣的,它們是通過新增協議來增強自己,而HttpServer是通過新增servlet而已

1.7 流程圖的第二步

把NameNodeRpcServer的結構圖畫出,也就是主體為 serviceRpcServer 和 clientRpcServer ,然後它們倆提供了各種各樣的服務方法

客戶端操作NameNode(比如建立目錄mkdirs)就要使用 clientRpcServer 提供的服務,而各個DataNode和NameNode的互相呼叫則通過 serviceRpcServer 實現

圖中的namenode可以視為standBy NameNode,在HA高可用中提到過的active和standby忘記的可以去複習一下

1.8 正式啟動前的檢查

安全模式我們在HDFS的第一篇中的心跳機制中已經提過了,這裡直接複製過來

hadoop叢集剛開始啟動時會進入安全模式(99.99%),就用到了心跳機制,其實就是在叢集剛啟動的時候,每一個DataNode都會向NameNode傳送blockReport,NameNode會統計它們上報的總block數,除以一開始知道的總個數total,當 block/total < 99.99% 時,會觸發安全模式,安全模式下客戶端就沒法向HDFS寫資料,只能進行讀資料。

點進startCommonServices

1.8.1 NameNode資源檢查1 --- 獲取需要檢查的目錄

NameNodeResourceChecker直譯過來就是NameNode的資源檢查器,我們點進去看到

duReserved

這裡是一個duReserved的值,它可以讓我們自行設定,如果不設定,就使用它給我們的預設值。我們也可以檢視一下這個DFS_NAMENODE_DU_RESERVED_DEFAULT的預設值,它為100M,定義在DFSConfigKeys.java中

這裡我也可以補充一下,我們需要判斷告警的目錄就在下面的getNamespaceEditsDirs(conf)中,一直點進去就可以看見我們剛剛提到的,需要檢查的三個目錄(NameNode中儲存fsimage的目錄和edit log的目錄和JournalNode的目錄,這些引數全部都是定義在DFSConfigKeys.java中)

localEditDirs並不是HDFS上的目錄,而是linux磁碟上的目錄,之後遍歷這個 localEditDirs 加入到一個volume中

在HDFS上很多位置都可以看到volumes這個單詞,volumes其實就是一個存放需要檢查的目錄的目錄集,註釋上的第一句話“Add the volume of the passed-in directory to the list of volumes to check.”意思就是將傳入目錄的卷新增到要檢查的卷列表中,這裡的卷就是volumes。

所以我們現在回到startCommonServices,現在已經得知

// 作用就是獲取到所有需要檢查的目錄
nnResourceChecker = new NameNodeResourceChecker(conf);
複製程式碼

1.8.2 NameNode資源檢查2 --- 檢查是否有足夠磁碟空間儲存元資料

checkAvailableResources() 點進去看一下

hadoop的方法命名總是如此地直白,hasAvailableDiskSpace直譯過來就是有沒有可用空間,再點選進去

這裡看到 volumes 了,因為剛剛也解釋過了,volumes 就是存放需要進行檢查的目錄(也就是那3個目錄)的,那自然把 volumes 作為引數傳進來,就把那三兄弟一鍋端了。再點進進去areResourcesAvailable方法,既然這個 volumes 是一個集合,那我們打賭它裡面的邏輯肯定有一個for迴圈來遍歷 volumes 裡面的那些值進行檢查

果不其然,for迴圈出現了。此時isResourceAvailable就是判斷依據了

這裡我們看到,它先獲取到了當前目錄的大小(jdk),然後和我們之前講到的 duReserved (預設100M)做比較。這時候我們就把前面的知識點串起來了。

如果空間不足,它就會列印一段話,這段話在我們日常的公司叢集環境中不太可能看到,但是如果我們是自己搭建的叢集進行學習時就會看到,這個時候就是我們的虛擬機器器的空間不足,雖然叢集服務正常啟動,可是叢集無法正常工作。

1.8.3 NameNode資源檢查3 --- 安全模式的檢查

面試題:我們是否真的清楚為什麼hadoop叢集會進入安全模式呢?

回到FSNameSystem,點進去 setBlockTotal() 方法

點進去看看它是如何獲取正常block個數的

獲取正在構建的block是個啥意思呢,再繼續點進去

在HDFS裡面block有兩種狀態,一種是complete,已經是可以使用的一個完整block,還有一種是UnderConstruct,這個屬於正在構建,還不能正常使用,對應下文中的這個程式碼

所以我們退回來,getCompleteBlocksTotal中,使用blockTotal總block數-numUCBlocks得出來的就是準備complete的block個數

退回到setBlockTotal中的safeMode.setBlockTotal((int)getCompleteBlocksTotal()),點進去setBlockTotal

這裡就是設定何時可以退出安全模式的程式碼,threshold預設值為0.999,比如我們總block數為1000,那如果存在999個isComplete的block,也就認為叢集是健康狀態,而可以退出安全模式。

點進去checkMode(),我們可以發現needEnter()即是判斷進入安全模式的條件(剛剛說明的是退出的)。

任意滿足以下3個條件的任意一個,都會進去安全模式

第一個條件

threshold != 0沒啥好說的,這個東西本身就不可能設定0,blockSafe < blockThreshold 這裡的blockSafe是指什麼呢?

我們的叢集啟動時,NameNode啟動後DataNode也會啟動,在DataNode啟動時會向NameNode上報自己的block狀態資訊,每上報一個,blockSafe就會加一。blockThreshold滿足安全模式閾值條件所需的塊數,當上報的量比安全模式要求的量少時,安全模式啟動。

第二個條件

datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold

DataNode和NameNode會有心跳機制,這個條件大概就是,叢集中存活的DataNode個數少於datanodeThreshold個就讓叢集處於安全模式

非常搞笑的是,這個datanodeThreshold的預設值是0,還需要自己配置,所以這個條件基本自己不配置是不生效的。hhh

第三個

!nameNodeHasResourcesAvailable()直接翻譯過來,磁碟空間是否充足,對,引用的就是上面提過的hasAvailableDiskSpace,直譯過來就是有沒有可用空間.

1.9 服務啟動

感動,整了這麼久終於可以正常啟動了

當然這裡會連帶啟動一些服務,後面再展開了

1.10 流程圖的第三步

其實就是補充了FSNameNode,然後把前幾次畫的都放在一起而已

finally

這一篇就是NameNode的啟動的大致流程了,當然有很多細節的部分我們仍未深究或者是一筆帶過,這些細節有些不重要有些是之後再進行補充的,截圖真的是非常麻煩,希望這些無論是對你,對我都會有所收穫。