提醒一下,這裏面須要有RPC的基礎,若是對RPC沒有了解的朋友,能夠先跳轉到以往寫的兩篇RPC文章中。java
理論方面:從零開始的高併發(七)--- RPC的介紹,協議及框架node
(可略過)代碼方面:從零開始的高併發(八)--- RPC框架的簡單實現linux
固然也不須要太過深刻,知道點皮毛便可。由於Hadoop中有一個Hadoop RPC須要有點基礎知識。web
暫時先記得下面的知足RPC的條件(非完整):面試
1.不一樣進程間的方法調用瀏覽器
2.RPC分爲服務端和客戶端,客戶端調用服務端的方法,方法的執行是在服務端安全
3.協議說得直白點就是一個接口,可是這個接口必須存在versionID服務器
4.協議裏面會存在抽象方法,這些抽象方法交由服務端實現併發
好的那咱們開始框架
咱們的第一個任務,就是來驗證NameNode是否是一個RPC的服務端
如今咱們來到Hadoop2.7.0的源碼,先打開NameNode.java的代碼,找到它的main方法
爲了方便你們觀看,我再標好一些註釋
這個createNameNode方法咱們分紅兩部分看
咱們操做HDFS集羣時,會經過以下一些命令
hdfs namenode -format
hadoop=daemon.sh start namanode
複製代碼
代碼過長無法截圖,直接複製下來
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.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: {
DefaultMetricsSystem.initialize("NameNode");
return new NameNode(conf);
}
複製代碼
好比第一小段是hdfs namenode -format,它是一個format
天然走的就是這個分支啦。
可是如今我好比輸入的是start 也就是第二小段hadoop=daemon.sh start namanode,由於其餘的都不知足,因此走的就是最後一個分支
再點進去這個new NameNode(conf)
點進去this方法,其實就是下面的那個
前面的一大截也不須要看,無非就是一些參數的傳遞問題,看這個try裏面的,有一個initialize,這個單詞的中文是初始化,那咱們就再繼續點進去
前面的是判斷了一些奇奇怪怪的條件的暫時先無論,直接看到咱們比較敏感的位置
此時咱們看見建立HttpServer的代碼了,咱們若是是初次搭建咱們的大數據集羣時,是否是會訪問一個50070的web頁面,好比
這裏雖然不是啥重要的流程,不過順便解釋一下50070怎麼來的
而後咱們看到了這兩個參數,DFS_NAMENODE_HTTP_ADDRESS_KEY 和 DFS_NAMENODE_HTTP_ADDRESS_DEFAULT
DFS_NAMENODE_HTTP_ADDRESS_KEY是本身手動配置的地址,可是通常咱們都沒有去手動配置,因此hadoop會使用一個默認的地址 DFS_NAMENODE_HTTP_ADDRESS_DEFAULT
看到這裏,就知道咱們當時訪問那個網站爲啥是本機ip加一個50070了吧。
此時咱們回到第一張圖,也就是第二句就是start的那張startHttpServer方法,點進去start方法看看
咱們往下看,做爲一名Java Developer,咱們的關注點天然就是咱們熟悉的servlet了
setupServlets(httpServer, conf);
複製代碼
這裏綁定了一堆的servlet,綁的越多功能越強,咱們再點進去
能夠看到就是瘋狂地add一些各式各樣的servlet,咱們姑且先不看,其實servlet你們應該很是熟悉,點進去先啥都別想直接跳doGet()方法就能看到它們都分別作了什麼了
咱們看到目前爲止能夠先畫一下咱們的流程圖了
首先咱們如今是有一個HttpServer2(Hadoop本身封裝的HttpServer),它上面綁定了不少提供各類各樣功能的servlet
它對外提供一個50070的端口,瀏覽器發送一個http://ip address:50070/listPaths的請求去請求servlet後,就會返回那個web頁面了
回到1.4.1中的那張HttpServer的那個位置。在啓動HttpServer後,會加載元數據,可是咱們如今模擬的場景是集羣第一次啓動,第一次啓動的時候是不存在元數據的,因此咱們先直接跳過這個步驟,去到Hadoop RPC的位置
爲何我會知道這個Hadoop RPC會有兩個這樣的服務,這個固然不是我猜的,也並非我找過什麼資料或者點進去看過,而是在NameNode源碼中告訴咱們的
咱們直接把這一段英文放到百度翻譯上看看
這裏它就說明了,NameNode不只僅是個類,仍是個服務器,向外界公開一個IPC Server和一個Http server,補充說明一下,這個IPC Server就是讓開發者去進行命令操做的。這個Http Server就是那個開放了50070界面讓開發者瞭解HDFS的狀況的。而FSNamessystem這個類是管理了HDFS的元數據的
如今咱們知道了,這兩個服務一個是提供給內部的DataNode去調用,而另一個是提供給服務端去調用的
這裏的方法起名已經很是直接了,createRpcServer()哈哈哈,咱們點擊進去
這裏的註釋爲寫了建立RPC服務端實現,它return了一個NameNodeRpcServer,那是否是它纔是咱們要找的NameNode服務端呢?
還記得咱們開頭說判斷RPC的依據嗎,3.協議說得直白點就是一個接口,可是這個接口必須存在versionID,好的咱們記住這句話,再點進NameNodeRpcServer
相信這就是咱們想要看到的,它確實實現了一個NamenodeProtocols,感受快要看到真相了是吧,再點進去
我去,繼承了這麼多個協議,怪不得咱們的NameNode的功能如此強大,這裏面得有多少個方法啊,這時候咱們隨便點進去一個接口去看
知足是一個接口,也有一個versionID了吧,咱們如今以爲它就是服務端了,但是咱們尚未看見那些set服務器地址啊,端口啊···等等這些參數的代碼,因此仍是不能一口咬定,因此咱們如今再退回去到class NameNodeRpcServer裏面,拉到296行
再拉取到343行,發現又有一段相似的
還記得咱們剛纔說過,兩個服務一個是提供給內部的DataNode去調用,而另一個是提供給服務端去調用的,因此這裏這倆,第一個serviceRpcServer是服務於NameNode和DataNode之間調用的,而第二個clientRpcServer是服務於客戶端與NameNode,DataNode進行交互調用的
並且在建立它們以後,會有不少的協議被添加進來,這些協議也是帶有許許多多的方法的,添加的協議越多,這兩個服務的功能也就越強大
因此它們和HttpServer的套路是同樣的,它們是經過添加協議來加強本身,而HttpServer是經過添加servlet而已。
把NameNodeRpcServer的結構圖畫出,也就是主體爲 serviceRpcServer 和 clientRpcServer ,而後它們倆提供了各類各樣的服務方法
客戶端操做NameNode(好比建立目錄mkdirs)就要使用 clientRpcServer 提供的服務,而各個DataNode和NameNode的互相調用則經過 serviceRpcServer 實現
圖中的namenode能夠視爲standBy NameNode,在HA高可用中提到過的active和standby忘記的能夠去複習一下
安全模式咱們在HDFS的第一篇中的心跳機制中已經提過了,這裏直接複製過來
hadoop集羣剛開始啓動時會進入安全模式(99.99%),就用到了心跳機制,其實就是在集羣剛啓動的時候,每個DataNode都會向NameNode發送blockReport,NameNode會統計它們上報的總block數,除以一開始知道的總個數total,當 block/total < 99.99% 時,會觸發安全模式,安全模式下客戶端就無法向HDFS寫數據,只能進行讀數據。
點進startCommonServices
NameNodeResourceChecker直譯過來就是NameNode的資源檢查器,咱們點進去看到
這裏是一個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);
複製代碼
checkAvailableResources() 點進去看一下
hadoop的方法命名老是如此地直白,hasAvailableDiskSpace直譯過來就是有沒有可用空間,再點擊進去
這裏看到 volumes 了,由於剛剛也解釋過了,volumes 就是存放須要進行檢查的目錄(也就是那3個目錄)的,那天然把 volumes 做爲參數傳進來,就把那三兄弟一鍋端了。再點進進去areResourcesAvailable方法,既然這個 volumes 是一個集合,那咱們打賭它裏面的邏輯確定有一個for循環來遍歷 volumes 裏面的那些值進行檢查
果不其然,for循環出現了。此時isResourceAvailable就是判斷依據了
這裏咱們看到,它先獲取到了當前目錄的大小(jdk),而後和咱們以前講到的 duReserved (默認100M)作比較。這時候咱們就把前面的知識點串起來了。
若是空間不足,它就會打印一段話,這段話在咱們平常的公司集羣環境中不太可能看到,可是若是咱們是本身搭建的集羣進行學習時就會看到,這個時候就是咱們的虛擬機的空間不足,雖然集羣服務正常啓動,但是集羣沒法正常工做。
面試題:咱們是否真的清楚爲何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,直譯過來就是有沒有可用空間.
感動,整了這麼久終於能夠正常啓動了
固然這裏會連帶啓動一些服務,後面再展開了
其實就是補充了FSNameNode,而後把前幾回畫的都放在一塊兒而已
這一篇就是NameNode的啓動的大體流程了,固然有不少細節的部分咱們仍未深究或者是一筆帶過,這些細節有些不重要有些是以後再進行補充的,截圖真的是很是麻煩,但願這些不管是對你,對我都會有所收穫。