_00004 Hadoop NameNode原始碼淺析(RPC是基礎)
個性簽名:世界上最遙遠的距離不是天涯,也不是海角,而是我站在妳的面前,妳卻感覺不到我的存在
技術方向:Flume+Kafka+Storm+Redis/Hbase+Hadoop+Hive+Mahout+Spark ... 雲端計算技術
轉載宣告:
qq交流群:
# NameNode原始碼分析(RPC是基礎)
# namenode註釋翻譯
* NameNode servesas both directory namespace manager and
* "inodetable" for the Hadoop DFS. There isa single NameNode
* running in anyDFS deployment. (Well, except when there
* is a secondbackup/failover NameNode.)
*
* The NameNodecontrols two critical tables:
* 1) filename->blocksequence (namespace)
* 2) block->machinelist ("inodes")
*
* The first tableis stored on disk and is very precious.
* The second tableis rebuilt every time the NameNode comes
* up.
*
* 'NameNode'refers to both this class as well as the 'NameNode server'.
* The'FSNamesystem' class actually performs most of the filesystem
* management. The majority of the 'NameNode' class itselfis concerned
* with exposingthe IPC interface and the http server to the outside world,
* plus someconfiguration management.
*
* NameNodeimplements the ClientProtocol interface, which allows
* clients to askfor DFS services. ClientProtocol is not
* designed fordirect use by authors of DFS client code. End-users
* should insteaduse the org.apache.nutch.hadoop.fs.FileSystem class.
*
* NameNode alsoimplements the DatanodeProtocol interface, used by
* DataNodeprograms that actually store DFS data blocks. These
* methods areinvoked repeatedly and automatically by all the
* DataNodes in aDFS deployment.
*
* NameNode alsoimplements the NamenodeProtocol interface, used by
* secondarynamenodes or rebalancing processes to get partial namenode's
* state, forexample partial blocksMap etc.
**********************************************************/
對於HDFS來說NameNode是一個目錄名稱空間管理器和”inode table”,它是一個單個的NameNode執行在任何的DFS的部署環境中(好吧,除非是有第二個備份/故障轉移NameNode。)
NameNode控制著兩個關鍵表:
1) filename->blocksequence (namespace)第一個表名為檔名,放的是block的順序
2) block->machinelist ("inodes")第二個表是block,放的是block所存放的機器列表
第一個表存放在硬碟上並且非常珍貴
第二個表在每次NameNode重啟的時候會被重構
NameNode指的是這個類以及NameNode server,FSNamesystem這個類實際上執行的大多數檔案系統管理。NameNode這個類主要的工作就是關注暴露的IPC介面以及向外部世界(使用者)提供一些http服務,加上一些配置管理。
NameNode實現了ClientProtocol介面,它允許客戶請求DFS服務。ClientProtocol不是專門為直接使用DFS客戶機程式碼的作者設計的,終端使用者(程式設計師)應該使用FileSystem這個類。
NameNode也實現了DatanodeProtocol介面,被DataNode的程式使用去完成DFS資料塊的儲存。在一個DFS的環境中NameNode實現了DatanodeProtocol介面中的方法會被所有的DataNode自動重複的呼叫執行。
NameNode也實現了NamenodeProtocol介面,被secondarynamenode使用或在平衡過程的程序中得到NameNode 的部分狀態,例如部分blocksMap等等
# 知道了RPC原理才能更好的理解NameNode
# 首先看namenode類的結構,主要實現了ClientProtocol, DatanodeProtocol, NamenodeProtocol這三個介面
# 進入NameNode的原始碼找到publicclass NameNodeimplements ClientProtocol, DatanodeProtocol, NamenodeProtocol,FSConstants,RefreshAuthorizationPolicyProtocol,
RefreshUserMappingsProtocol {
# 接下來進入main方法(由於NameNode是一個RPC的服務端,所以我們進入RPC的main方法,為了證明NameNode是一個RPC的服務端)
public static void main(String argv[]) throws Exception {
try {
StringUtils.startupShutdownMessage(NameNode.class,argv,LOG);
NameNode namenode = createNameNode(argv,null);
if (namenode != null)
namenode.join();
} catch (Throwable e) {
LOG.error(StringUtils.stringifyException(e));
System.exit(-1);
}
}
# 進入createNameNode方法(只看重點,會有下面這麼一行)
NameNodenamenode = new NameNode(conf);
# 再點進去
public NameNode(Configuration conf) throws IOException {
try {
initialize(conf);
} catch (IOException e) {
this.stop();
throw e;
}
}
# 進入initialize(conf)方法(只看重點程式碼)
this.namesystem =newFSNamesystem(this, conf);
if (UserGroupInformation.isSecurityEnabled()){
namesystem.activateSecretManager();
}
// create rpc server
InetSocketAddress dnSocketAddr =getServiceRpcServerAddress(conf);
if (dnSocketAddr !=null) {
int serviceHandlerCount =
conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY,
DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT);
this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(),
dnSocketAddr.getPort(),serviceHandlerCount,
false, conf,namesystem.getDelegationTokenSecretManager());
this.serviceRPCAddress =this.serviceRpcServer.getListenerAddress();
setRpcServiceServerAddress(conf);
}
this.server = RPC.getServer(this, socAddr.getHostName(),
socAddr.getPort(),handlerCount, false, conf,namesystem
.getDelegationTokenSecretManager());
// The rpc-server port can be ephemeral... ensure we have the correct info
this.serverAddress =this.server.getListenerAddress();
FileSystem.setDefaultUri(conf,getUri(serverAddress));
LOG.info("Namenode up at: " +this.serverAddress);
startHttpServer(conf);
this.server.start(); //start RPC server
if (serviceRpcServer !=null) {
serviceRpcServer.start();
}
startTrashEmptier(conf);
namesystem後面再解釋(namenode的初始化,namenode啟動載入fsimage以及一些配置,後面詳細解釋)
//create rpc server 意思就是建立 RPC服務端,也就是說NameNode是一個RPC的服務端
注意:這裡不是啟動了一個rpc的服務端,而是啟動了兩個rpc的服務端。
serviceRpcServer:RPC伺服器為了HDFS服務通訊。備份節點(secondarynamenode),Datanodes和所有其他服務應該連線到這個伺服器配置。客戶應該只去呼叫NameNode 下的server的RPC服務端(這個是程式內部呼叫的)
server:主要是用來給客戶端呼叫的
# 然後再來看startHttpServer(conf);(開啟一個Http的伺服器)這個方法,跟進去
try {
this.httpServer = ugi.doAs(new PrivilegedExceptionAction<HttpServer>() {
@Override
public HttpServer run()throws IOException,InterruptedException {
String infoHost =infoSocAddr.getHostName();
int infoPort = infoSocAddr.getPort();
httpServer = new HttpServer("hdfs", infoHost,infoPort,
infoPort == 0, conf,
SecurityUtil.getAdmin
# 在進入HttpServer 的構造方法,跟進new HttpServer("hdfs", infoHost, inf
public HttpServer(String name, String bindAddress, int port,
boolean findPort, Configuration conf,AccessControlList adminsAcl)
throws IOException {
this(name, bindAddress, port, findPort, conf,adminsAcl,null);
}
# 再跟進this(name, bindAddress, port, findPort, conf,adminsAcl,null);
public HttpServer(String name, String bindAddress, int port,
boolean findPort, Configuration conf,AccessControlList adminsAcl,
Connector connector) throws IOException{
this.findPort = findPort;
this.conf = conf;
this.adminsAcl = adminsAcl;
if(connector ==null) {
到這裡就行了,把滑鼠放到new Server()上面去,可以看到這麼一行提示資訊
org.mortbay.jetty.Server.Server()
這裡額外說明一下jetty,jetty跟tomcat一樣,也是一個伺服器,只是更小而已,被內建到NameNode中去了。
到了這裡之後就可以看出來NameNode開啟了一個jetty服務,也就是可以通過瀏覽器訪問,也就是我們經常訪問的http://hadoopip:50070(這裡hadoopip表示你的hadoop機器的ip地址)出現的web介面
# NameNode啟動過程詳細剖析
# NameNode中幾個關鍵的資料結構
# FSImage
Namenode會將HDFS的檔案和目錄元資料儲存在一個叫fsimage的二進位制檔案中,每次儲存fsimage之後到下次儲存之間的所有hdfs操作,將會記錄在editlog檔案中,當editlog達到一定的大小(bytes,由fs.checkpoint.size引數定義)或從上次儲存過後一定時間段過後(sec,由fs.checkpoint.period引數定義),namenode會重新將記憶體中對整個HDFS的目錄樹和檔案元資料刷到fsimage檔案中。Namenode就是通過這種方式來保證HDFS中元資料資訊的安全性。
Fsimage是一個二進位制檔案,當中記錄了HDFS中所有檔案和目錄的元資料資訊,在我的hadoop的HDFS版中,該檔案的中儲存檔案和目錄的格式如下:
當namenode重啟載入fsimage時,就是按照如下格式協議從檔案流中載入元資料資訊。從fsimag的儲存格式可以看出,fsimage儲存有如下資訊:
1. 首先是一個image head,其中包含:
a) imgVersion(int):當前image的版本資訊
b) namespaceID(int):用來確保別的HDFSinstance中的datanode不會誤連上當前NN。
c) numFiles(long):整個檔案系統中包含有多少檔案和目錄
d) genStamp(long):生成該image時的時間戳資訊。
2. 接下來便是對每個檔案或目錄的源資料資訊,如果是目錄,則包含以下資訊:
a) path(String):該目錄的路徑,如”/user/build/build-index”
b) replications(short):副本數(目錄雖然沒有副本,但這裡記錄的目錄副本數也為3)
c) mtime(long):該目錄的修改時間的時間戳資訊
d) atime(long):該目錄的訪問時間的時間戳資訊
e) blocksize(long):目錄的blocksize都為0
f) numBlocks(int):實際有多少個檔案塊,目錄的該值都為-1,表示該item為目錄
g) nsQuota(long):namespaceQuota值,若沒加Quota限制則為-1
h) dsQuota(long):disk Quota值,若沒加限制則也為-1
i) username(String):該目錄的所屬使用者名稱
j) group(String):該目錄的所屬組
k) permission(short):該目錄的permission資訊,如644等,有一個short來記錄。
3. 若從fsimage中讀到的item是一個檔案,則還會額外包含如下資訊:
a) blockid(long):屬於該檔案的block的blockid,
b) numBytes(long):該block的大小
c) genStamp(long):該block的時間戳
當該檔案對應的numBlocks數不為1,而是大於1時,表示該檔案對應有多個block資訊,此時緊接在該fsimage之後的就會有多個blockid,numBytes和genStamp資訊。
因此,在namenode啟動時,就需要對fsimage按照如下格式進行順序的載入,以將fsimage中記錄的HDFS元資料資訊載入到記憶體中。
# BlockMap
從以上fsimage中載入如namenode記憶體中的資訊中可以很明顯的看出,在fsimage中,並沒有記錄每一個block對應到哪幾個datanodes的對應表資訊,而只是儲存了所有的關於namespace的相關資訊。而真正每個block對應到datanodes列表的資訊在hadoop中並沒有進行持久化儲存,而是在所有datanode啟動時,每個datanode對本地磁碟進行掃描,將本datanode上儲存的block資訊彙報給namenode,namenode在接收到每個datanode的塊資訊彙報後,將接收到的塊資訊,以及其所在的datanode資訊等儲存在記憶體中。HDFS就是通過這種塊資訊彙報的方式來完成 block-> datanodes list的對應表構建。Datanode向namenode彙報塊資訊的過程叫做blockReport,而namenode將block -> datanodeslist的對應表資訊儲存在一個叫BlocksMap的資料結構中。
BlocksMap的內部資料結構如下:
如上圖顯示,BlocksMap實際上就是一個Block物件對BlockInfo物件的一個Map表,其中Block物件中只記錄了blockid,block大小以及時間戳資訊,這些資訊在fsimage中都有記錄。而BlockInfo是從Block物件繼承而來,因此除了Block物件中儲存的資訊外,還包括代表該block所屬的HDFS檔案的INodeFile物件引用以及該block所屬datanodes列表的資訊(即上圖中的DN1,DN2,DN3,該資料結構會在下文詳述)。
因此在namenode啟動並載入fsimage完成之後,實際上BlocksMap中的key,也就是Block物件都已經載入到BlocksMap中,每個key對應的value(BlockInfo)中,除了表示其所屬的datanodes列表的陣列為空外,其他資訊也都已經成功載入。所以可以說:fsimage載入完畢後,BlocksMap中僅缺少每個塊對應到其所屬的datanodeslist的對應關係資訊。所缺這些資訊,就是通過上文提到的從各datanode接收blockReport來構建。當所有的datanode彙報給namenode的blockReport處理完畢後,BlocksMap整個結構也就構建完成。
# BlockMap中datanode列表資料結構
在BlockInfo中,將該block所屬的datanodes列表儲存在一個Object[]陣列中,但該陣列不僅僅儲存了datanodes列表,“。實際上該陣列儲存瞭如下資訊:
上圖表示一個block包含有三個副本,分別放置在DN1,DN2和DN3三個datanode上,每個datanode對應一個三元組,該三元組中的第二個元素,即上圖中prev block所指的是該block在該datanode上的前一個BlockInfo引用。第三個元素,也就是上圖中next Block所指的是該block在該datanode上的下一個BlockInfo引用。每個block有多少個副本,其對應的BlockInfo物件中就會有多少個這種三元組。
Namenode採用這種結構來儲存block->datanodelist的目的在於節約namenode記憶體。由於namenode將block->datanodes的對應關係儲存在了記憶體當中,隨著HDFS中檔案數的增加,block數也會相應的增加,namenode為了儲存block->datanodes的資訊已經耗費了相當多的記憶體,如果還像這種方式一樣的儲存datanode->blocklist的對應表,勢必耗費更多的記憶體,而且在實際應用中,要查一個datanode上儲存的block list的應用實際上非常的少,大部分情況下是要根據block來查datanode列表,所以namenode中通過上圖的方式來儲存block->datanode list的對應關係,當需要查詢datanode->blocklist的對應關係時,只需要沿著該資料結構中next Block的指向關係,就能得出結果,而又無需儲存datanode->blocklist在記憶體中。
# NameNode啟動過程
# fsimage載入過程
Fsimage載入過程完成的操作主要是為了:
1. 從fsimage中讀取該HDFS中儲存的每一個目錄和每一個檔案
2. 初始化每個目錄和檔案的元資料資訊
3. 根據目錄和檔案的路徑,構造出整個namespace在記憶體中的映象
4. 如果是檔案,則讀取出該檔案包含的所有blockid,並插入到BlocksMap中。
整個載入流程如下圖所示:
如上圖所示,namenode在載入fsimage過程其實非常簡單,就是從fsimage中不停的順序讀取檔案和目錄的元資料資訊,並在記憶體中構建整個namespace,同時將每個檔案對應的blockid儲存入BlocksMap中,此時BlocksMap中每個block對應的datanodes列表暫時為空。當fsimage載入完畢後,整個HDFS的目錄結構在記憶體中就已經初始化完畢,所缺的就是每個檔案對應的block對應的datanode列表資訊。這些資訊需要從datanode的RPC遠端呼叫blockReport中獲取,所以載入fsimage完畢後,namenode程序進入rpc等待狀態,等待所有的datanodes傳送blockReports。
# blockReport階段
每個datanode在啟動時都會掃描其機器上對應儲存hdfs block的目錄下(dfs.data.dir)所儲存的所有檔案塊,然後通過namenode的rpc呼叫將這些block資訊以一個long陣列的方式傳送給namenode,namenode在接收到一個datanode的blockReport rpc呼叫後,從rpc中解析出block陣列,並將這些接收到的blocks插入到BlocksMap表中,由於此時BlocksMap缺少的僅僅是每個block對應的datanode資訊,而namenoe能從report中獲知當前report上來的是哪個datanode的塊資訊,所以,blockReport過程實際上就是namenode在接收到塊資訊彙報後,填充BlocksMap中每個block對應的datanodes列表的三元組資訊的過程。其流程如下圖所示:
當所有的datanode彙報完block,namenode針對每個datanode的彙報進行過處理後,namenode的啟動過程到此結束。此時BlocksMap中block->datanodes的對應關係已經初始化完畢。如果此時已經達到安全模式的推出閾值,則hdfs主動退出安全模式,開始提供服務。
# NameNode原始碼分析總結
一個hdfs的cluster包含了一個NameNode和若干個DataNode,NameNode是master,主要負責管理hdfs檔案系統,具體的包括namespace管理(目錄結構)和block管理(具體filename->blocksequence(namespace),block->datanode list(“inodes”))。前者是通過FSImage寫入到本地檔案系統中,而後者是通過每次hdfs啟動時,datanode進行blockreport後在記憶體中重構的資料結構。在hdfs的程式程式碼中,namenode類其實只是一個用來被動接收呼叫的服務的包裝,它實現了ClientProtocol介面,用來接收來自DFSClient的RPC請求;它實現了DatanodeProtocol介面,用來接收來自datanode的各種服務請求;同時還實現了NamenodeProtocol,用來提供跟SeconddaryNameNode之間的RPC的請求和通訊。對以上資料結構進行維護的是hdfs中的FSNamesystem類。對於NameNode的各種請求,比如建立,修改,刪除,移動,getLocations的操作,在NameNode內部都是通過FSNamesystem提供的介面對內部資料結構進行的訪問。
NameNode是一個目錄名稱空間的管理器,NameNode在hdfs中只有一個。(當啟動一個NameNode的時候,會產生一個鎖檔案,是鎖住的,所以起不了第二個NameNode了)
NameNode維護這兩張核心表:
1. Filename------blocksequence (“namespace”)也就是block的順序
2. block------machinelist(“inodes”) 每個block的儲存的機器(dataNode)列表
NameNode其實就是一個RPC的服務端,並且啟動了兩個RPC服務端(這裡又涉及到了RPC原理了,看不懂的話就看下RPC的原理),並且還開啟了一個jetty伺服器,對外界提供了WEB的訪問方式。
The you smile until forever 、、、、、、、、、、、、、、、、、、、、、