1. 程式人生 > >_00004 Hadoop NameNode原始碼淺析(RPC是基礎)

_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的服務端。

serviceRpcServerRPC伺服器為了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()

這裡額外說明一下jettyjettytomcat一樣,也是一個伺服器,只是更小而已,被內建到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中所有檔案和目錄的元資料資訊,在我的hadoopHDFS版中,該檔案的中儲存檔案和目錄的格式如下:

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):屬於該檔案的blockblockid

b)       numBytes(long):該block的大小

c)        genStamp(long):該block的時間戳

當該檔案對應的numBlocks數不為1,而是大於1時,表示該檔案對應有多個block資訊,此時緊接在該fsimage之後的就會有多個blockidnumBytesgenStamp資訊。

因此,在namenode啟動時,就需要對fsimage按照如下格式進行順序的載入,以將fsimage中記錄的HDFS元資料資訊載入到記憶體中。

# BlockMap

從以上fsimage中載入如namenode記憶體中的資訊中可以很明顯的看出,在fsimage中,並沒有記錄每一個block對應到哪幾個datanodes的對應表資訊,而只是儲存了所有的關於namespace的相關資訊。而真正每個block對應到datanodes列表的資訊在hadoop中並沒有進行持久化儲存,而是在所有datanode啟動時,每個datanode對本地磁碟進行掃描,將本datanode上儲存的block資訊彙報給namenodenamenode在接收到每個datanode的塊資訊彙報後,將接收到的塊資訊,以及其所在的datanode資訊等儲存在記憶體中。HDFS就是通過這種塊資訊彙報的方式來完成 block-> datanodes list的對應表構建。Datanodenamenode彙報塊資訊的過程叫做blockReport,而namenodeblock -> datanodeslist的對應表資訊儲存在一個叫BlocksMap的資料結構中。

BlocksMap的內部資料結構如下:

如上圖顯示,BlocksMap實際上就是一個Block物件對BlockInfo物件的一個Map表,其中Block物件中只記錄了blockidblock大小以及時間戳資訊,這些資訊在fsimage中都有記錄。而BlockInfo是從Block物件繼承而來,因此除了Block物件中儲存的資訊外,還包括代表該block所屬的HDFS檔案的INodeFile物件引用以及該block所屬datanodes列表的資訊(即上圖中的DN1DN2DN3,該資料結構會在下文詳述)。

因此在namenode啟動並載入fsimage完成之後,實際上BlocksMap中的key,也就是Block物件都已經載入到BlocksMap中,每個key對應的value(BlockInfo)中,除了表示其所屬的datanodes列表的陣列為空外,其他資訊也都已經成功載入。所以可以說:fsimage載入完畢後,BlocksMap中僅缺少每個塊對應到其所屬的datanodeslist的對應關係資訊。所缺這些資訊,就是通過上文提到的從各datanode接收blockReport來構建。當所有的datanode彙報給namenodeblockReport處理完畢後,BlocksMap整個結構也就構建完成。

# BlockMapdatanode列表資料結構

BlockInfo中,將該block所屬的datanodes列表儲存在一個Object[]陣列中,但該陣列不僅僅儲存了datanodes列表,“。實際上該陣列儲存瞭如下資訊:

上圖表示一個block包含有三個副本,分別放置在DN1DN2DN3三個datanode上,每個datanode對應一個三元組,該三元組中的第二個元素,即上圖中prev block所指的是該block在該datanode上的前一個BlockInfo引用。第三個元素,也就是上圖中next Block所指的是該block在該datanode上的下一個BlockInfo引用。每個block有多少個副本,其對應的BlockInfo物件中就會有多少個這種三元組。

      Namenode採用這種結構來儲存block->datanodelist的目的在於節約namenode記憶體。由於namenodeblock->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列表資訊。這些資訊需要從datanodeRPC遠端呼叫blockReport中獲取,所以載入fsimage完畢後,namenode程序進入rpc等待狀態,等待所有的datanodes傳送blockReports

# blockReport階段

每個datanode在啟動時都會掃描其機器上對應儲存hdfs block的目錄下(dfs.data.dir)所儲存的所有檔案塊,然後通過namenoderpc呼叫將這些block資訊以一個long陣列的方式傳送給namenodenamenode在接收到一個datanodeblockReport rpc呼叫後,從rpc中解析出block陣列,並將這些接收到的blocks插入到BlocksMap表中,由於此時BlocksMap缺少的僅僅是每個block對應的datanode資訊,而namenoe能從report中獲知當前report上來的是哪個datanode的塊資訊,所以,blockReport過程實際上就是namenode在接收到塊資訊彙報後,填充BlocksMap中每個block對應的datanodes列表的三元組資訊的過程。其流程如下圖所示:

當所有的datanode彙報完blocknamenode針對每個datanode的彙報進行過處理後,namenode的啟動過程到此結束。此時BlocksMapblock->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 、、、、、、、、、、、、、、、、、、、、、