【HDFS】datanode註冊信息都有啥?

datanode節點在啓動的時候,有一個向namenode註冊的過程,那麼註冊的過程中datanode到底向namenode傳遞了哪些信息?

  private void register() throws IOException {
    if (dnRegistration.getStorageID().equals("")) {
      setNewStorageID(dnRegistration);
    }
    while(shouldRun) {
      try {
        // reset name to machineName. Mainly for web interface.
        dnRegistration.name = machineName + ":" + dnRegistration.getPort();
        dnRegistration = namenode.register(dnRegistration);//dnRegistration
        break;
      } catch(SocketTimeoutException e) {  // namenode is busy
        LOG.info("Problem connecting to server: " + getNameNodeAddr());
        try {
          Thread.sleep(1000);
        } catch (InterruptedException ie) {}
      }
    }
dnRegistration對象封裝了datanode的註冊信息,datanode初始化的時候創建一個DatanodeRegistration實例,我們看是傳進去了機器名和端口號。

this.dnRegistration = new DatanodeRegistration(machineName + ":" + tmpPort);
啥是機器名?

    if (conf.get("slave.host.name") != null) {
      machineName = conf.get("slave.host.name");   
    }
    if (machineName == null) {
      machineName = DNS.getDefaultHost(
                                     conf.get("dfs.datanode.dns.interface","default"),
                                     conf.get("dfs.datanode.dns.nameserver","default"));
    }
看到沒有,機器名你可以手工配置key就叫slave.host.name,但是這個多數都不配,因爲對於大規模集羣來說,一個個節點去配置機器名是很沒效率的。

所以datanode怎麼獲取呢,一種方式是找dns要,但是一般也不配置dns,因爲這樣又把dns服務牽扯進來,有些集羣的數據節點沒有配置dns或者dns壓力比較大,所以基本上都不配置dfs.datanode.dns.interface或者dfs.datanode.dns.nameserver,查看hdfs-default.xml可以看出都是default,那下面還有啥辦法?我擦只能找hosts了,就是本機配置的/etc/hosts文件,這裏裏邊建立了ip和機器名的對應關係,一般的例如hosts文件你可以這樣配置:

127.0.0.1       YZSJHL19-87 localhost.localdomain localhost
10.4.19.87             YZSJHL19-87.opi.com

  public static String getDefaultHost(String strInterface, String nameserver)
    throws UnknownHostException {
    if (strInterface.equals("default")) 
      return InetAddress.getLocalHost().getCanonicalHostName();

    if (nameserver != null && nameserver.equals("default"))
      return getDefaultHost(strInterface);

    String[] hosts = getHosts(strInterface, nameserver);
    return hosts[0];
  }
然後看上面返回的結果是啥,第一個if就返回了,返回結果是string類型的機器名即YZSJHL19-87.opi.com,InetAddress.getLocalHost()返回的結果如下

YZSJHL19-87.opi.com/10.4.19.87,也就說你使用shell命令行顯示的機器名是啥,dn往namenode註冊的機器名就是啥。


好了,現在看看端口號是什麼,就是前面提到的tmpPort,

  public static String getServerAddress(Configuration conf,
                                        String oldBindAddressName,
                                        String oldPortName,
                                        String newBindAddressName) {
    String oldAddr = conf.get(oldBindAddressName);
    String oldPort = conf.get(oldPortName);
    String newAddrPort = conf.get(newBindAddressName);
    if (oldAddr == null && oldPort == null) {
      return newAddrPort;
    }
    String[] newAddrPortParts = newAddrPort.split(":",2);
    if (newAddrPortParts.length != 2) {
      throw new IllegalArgumentException("Invalid address/port: " + 
                                         newAddrPort);
    }
    if (oldAddr == null) {
      oldAddr = newAddrPortParts[0];
    } else {
      LOG.warn("Configuration parameter " + oldBindAddressName +
               " is deprecated. Use " + newBindAddressName + " instead.");
    }
    if (oldPort == null) {
      oldPort = newAddrPortParts[1];
    } else {
      LOG.warn("Configuration parameter " + oldPortName +
               " is deprecated. Use " + newBindAddressName + " instead.");      
    }
    return oldAddr + ":" + oldPort;
  }
首先,需要使用上面的方法獲取一個網絡地址,傳入的分別是下面的三個key,

  public static InetSocketAddress getStreamingAddr(Configuration conf) {
    String address = 
      NetUtils.getServerAddress(conf,
                                "dfs.datanode.bindAddress", 
                                "dfs.datanode.port",
                                "dfs.datanode.address");
    return NetUtils.createSocketAddr(address);
  }
<property>
  <name>dfs.datanode.address</name>
  <value>0.0.0.0:50010</value>
  <description>
    The datanode server address and port for data transfer.
  </description>
</property>
注意啊,在hdfs-default.xml文件裏就只配置了dfs.datanode.address,它的值是0.0.0.0:50010,這樣對於getServerAddress方法,這時候直接返回這個0.0.0.0:50010這個地址。

InetSocketAddress socAddr = DataNode.getStreamingAddr(conf);
    int tmpPort = socAddr.getPort();
其實這個socAddr就用了這一下,目的就是拿到50010,前面的Ip地址不全是0也無所謂,都只拿到50010端口。

這樣datanode註冊時的節點名字就有了,就是”機器名:端口號”,注意不是ip。

再回到註冊部分的代碼,有了節點名後還不行,還要有個存儲id:

    if (dnRegistration.getStorageID().equals("")) {
      setNewStorageID(dnRegistration);
    }
一開始dnRegistration是沒有StorageID的,看一下setNewStorageID是啥吧,很簡單是吧,存儲id是DS開頭+隨機號+ip+端口號+datanode註冊時的時間戳。

  public static void setNewStorageID(DatanodeRegistration dnReg) {
    String ip = "unknownIP";
    try {
      ip = DNS.getDefaultIP("default");
    } catch (UnknownHostException ignored) {
      LOG.warn("Could not find ip address of \"default\" inteface.");
    }
    int rand = 0;
    try {
      rand = SecureRandom.getInstance("SHA1PRNG").nextInt(Integer.MAX_VALUE);
    } catch (NoSuchAlgorithmException e) {
      LOG.warn("Could not use SecureRandom");
      rand = R.nextInt(Integer.MAX_VALUE);
    }
    dnReg.storageID = "DS-" + rand + "-"+ ip + "-" + dnReg.getPort() + "-" + 
                      System.currentTimeMillis();
  }//DS-1402344465-10.4.19.59-50010-1374756186082

到這裏,datanode的自身的情況就搞定了。也說清楚了。但是datanode向namenode註冊,不是說你想註冊就能註冊,隨便起個服務就能向namenode註冊,你想加入幫派,至少對幫派要有所瞭解吧?所以這裏還有些比較重要的機制。

1、通過握手瞭解namenode,使datanode加強對組織的瞭解,namenode會發放一些信息,等datanode註冊的時候就要憑藉這個信物做簡單的認證。

private NamespaceInfo handshake() throws IOException {
    NamespaceInfo nsInfo = new NamespaceInfo();
    while (shouldRun) {
      try {
        nsInfo = namenode.versionRequest();
        break;
      } catch(SocketTimeoutException e) {  // namenode is busy
        LOG.info("Problem connecting to server: " + getNameNodeAddr());
        try {
          Thread.sleep(1000);
        } catch (InterruptedException ie) {}
      }
    }//通過握手拿到namenode的namespace信息,包括namespaceID,版本號和其他問題
    String errorMsg = null;
    // verify build version
    if( ! nsInfo.getBuildVersion().equals( Storage.getBuildVersion() )) {
    	//通過比較namenode和datanode自己的編譯版本號確定是否匹配
      errorMsg = "Incompatible build versions: namenode BV = " 
        + nsInfo.getBuildVersion() + "; datanode BV = "
        + Storage.getBuildVersion();
      LOG.fatal( errorMsg );
      notifyNamenode(DatanodeProtocol.NOTIFY, errorMsg);  
      throw new IOException( errorMsg );
    }
    assert FSConstants.LAYOUT_VERSION == nsInfo.getLayoutVersion() :
      "Data-node and name-node layout versions must be the same."
      + "Expected: "+ FSConstants.LAYOUT_VERSION + " actual "+ nsInfo.getLayoutVersion();
    return nsInfo;
  }
datanode通過versionRequest方法獲取系統版本信息。
  synchronized NamespaceInfo getNamespaceInfo() {
    return new NamespaceInfo(dir.fsImage.getNamespaceID(),
                             dir.fsImage.getCTime(),
                             getDistributedUpgradeVersion());
  }
namenode返回的是namespaceID(namespace的id都是在namenodeformat的時候產生的隨機數,取31位),Ctime初始化的時候都是0 ,只有系統升級的時候纔會改變這個數據,所有Ctime就認爲是0可以了;後面的分佈式升級版本號,先不管,生產環境中一般不這樣升級。這樣握手就得到了兩個比較重要的信息,一個是namespaceID,再一個是Ctime。

2、獲取datanode數據盤的VERSION信息,如果沒有就格式化。

recoverTransitionRead方法完成了這個過程,首先將每個數據盤都描述成StorageDirectory對象,StorageDirectory對象有個成員叫root所謂root就是數據目錄的首層,比如我們指定/disk6/hadoop/dfs.data爲其數據盤,那麼/disk6/hadoop/dfs.data就是這個存儲空間的根路徑。然後逐個數據目錄進行分析

a、首先必須確保root不爲空。

b、如果你指定的數據目錄不存在,比如/disk6這快盤是空的,那麼/disk6/hadoop/dfs.data顯然不存在,這種情況下,datanode啓動選項中如果沒有-format參數,那麼你就會在日誌中看到“Storage directory " + rootPath + " does not exist."”,比如我們在線修了一塊盤,修完回來肯定是空的,我們再按照原來的配置文件重啓datanode,那麼就會出現這個日誌。

c、如果你指定的數據盤不是目錄,對不起也不行。

d、如果你指定的數據盤不能寫入,比如這個目錄的權限是root的755,那麼普通的hadoop啓動賬戶也沒法使用這快盤,日誌“Cannot access storage directory " + rootPath”

下面,如果這個數據目錄正常,即存在的可寫目錄。

路徑檢查完了,還要處理一下storage這個文件,這是二進制文件,通過讀第一個int數據判斷存儲版本號是否過期,這部分回頭結合datanode的格式化再說吧。

繼續往下就是解析VERSION文件,如果這個文件存在就返回NORMAL狀態,其餘的先不梳理了,將專門有一篇博文處理這些細節。

如果既沒有Version文件也沒有那些中間狀態的目錄或者文件,那麼datanode認爲這個目錄需要格式化,比如你弄個測試集羣,你把datanode的Version文件刪掉,這時候,datanode對這個目錄的分析結果就是需要格式化,返回NOT_FORMATTED狀態,

這時就會自動格式化這個目錄:

  void format(StorageDirectory sd, NamespaceInfo nsInfo) throws IOException {
    sd.clearDirectory(); // create directory
    this.layoutVersion = FSConstants.LAYOUT_VERSION;
    this.namespaceID = nsInfo.getNamespaceID();
    this.cTime = 0;
    // store storageID as it currently is
    sd.write();
  }
看到沒有第一步就是可怕的清空存儲目錄:fullyDelete,好可怕。

    public void clearDirectory() throws IOException {
      File curDir = this.getCurrentDir();
      if (curDir.exists())
        if (!(FileUtil.fullyDelete(curDir)))
          throw new IOException("Cannot remove current directory: " + curDir);
      if (!curDir.mkdirs())
        throw new IOException("Cannot create directory " + curDir);
    }
然後寫入佈局版本,這個同一版本的hadoop的nn,dn,tt,jt都是一樣的。然後把從namenod握手得到的namespaceid,還記得嗎31位的隨機數,然後把這些東西全部寫到VERSION文件中。

格式化完了,這就是一個合格的數據存儲目錄了,把這個目錄加到存儲目錄的列表裏。然後全部更新本機全部存儲目錄的VERSION文件,因爲這一次的握手建立的信息要保證全部數據目錄保持一致。

這樣dn的存儲信息算是齊備了,後面還有兩點,一個是http服務端口號,同時datanode自己還起了一個rpcserver,這兩個服務的連接信息也會交給namenode,這時候要給註冊信息補充這些信息:

  public void setStorageInfo(DataStorage storage) {
    this.storageInfo = new StorageInfo(storage);
    this.storageID = storage.getStorageID();
  }
一個是StorageInfo,就是VERSION文件的內容,二是StorageID(DS-隨機數-IP-port-註冊的當前時間戳),再回到DataNode的註冊方法:

private void register() throws IOException {
    if (dnRegistration.getStorageID().equals("")) {
      setNewStorageID(dnRegistration);
    }
    while(shouldRun) {
      try {
        // reset name to machineName. Mainly for web interface.
        dnRegistration.name = machineName + ":" + dnRegistration.getPort();
        dnRegistration = namenode.register(dnRegistration);
        break;
      } catch(SocketTimeoutException e) {  // namenode is busy
查看namenode端實現的register方法:
  public DatanodeRegistration register(DatanodeRegistration nodeReg
                                       ) throws IOException {
    verifyVersion(nodeReg.getVersion());
    namesystem.registerDatanode(nodeReg);
      
    return nodeReg;
  }
首先匹配一下layout版本,這個同一個版本的這個都是寫死的,這是爲了防止不同版本的dn註冊進來。再看registerDatanode方法:

String dnAddress = Server.getRemoteAddress();
    if (dnAddress == null) {
      // Mostly called inside an RPC.
      // But if not, use address passed by the data-node.
      dnAddress = nodeReg.getHost();//儘量拿到IPdatanode的IP地址,實在不行拿到主機名也行
    }      
    // check if the datanode is allowed to be connect to the namenode
    if (!verifyNodeRegistration(nodeReg, dnAddress)) {
      throw new DisallowedDatanodeException(nodeReg);
    }
    String hostName = nodeReg.getHost();
    // update the datanode's name with ip:port
    DatanodeID dnReg = new DatanodeID(dnAddress + ":" + nodeReg.getPort(),
                                      nodeReg.getStorageID(),
                                      nodeReg.getInfoPort(),
                                      nodeReg.getIpcPort());
    nodeReg.updateRegInfo(dnReg);
    nodeReg.exportedKeys = getBlockKeys();
      
    NameNode.stateChangeLog.info(
                                 "BLOCK* NameSystem.registerDatanode: "
                                 + "node registration from " + nodeReg.getName()
                                 + " storage " + nodeReg.getStorageID());
首先嚐試獲取datanode的ip,如果獲取失敗,就想辦法拿到datanode的機器名,然後檢查datanode是不是在黑名單裏,一般是不設置白名單的。

爲了描述一個datanode,這裏採用DataNodeID對象,主要信息是ip+端口號+存儲id+http服務器端口號+datanode自己的rpc服務器服務端口號。

DatanodeDescriptor nodeS = datanodeMap.get(nodeReg.getStorageID());
    DatanodeDescriptor nodeN = host2DataNodeMap.getDatanodeByName(nodeReg.getName());
      
    if (nodeN != null && nodeN != nodeS) {
      NameNode.LOG.info("BLOCK* NameSystem.registerDatanode: "
                        + "node from name: " + nodeN.getName());
      // nodeN previously served a different data storage, 
      // which is not served by anybody anymore.
      removeDatanode(nodeN);
      // physically remove node from datanodeMap
      wipeDatanode(nodeN);
      nodeN = null;
    }
namenode的大管家FSNamesystem對象,維護者2張表,一個是datanodeMap,以存儲ID做key,以datanode描述符做value;另一張表示機器機器域名或者IP做key,datanode描述符做value,至於這倆表幹啥用的,先不管了。

下面進行判斷,如果從第二張表找到了一個datanode描述符,說明這個機器上曾經註冊上來過一個datanode,但是又跟存儲id那張表找到的不是一個,那麼不管第一張表裏找到沒有,都認爲前期的那個版本失效,從心跳列表中將其刪除。同時從兩張表中將其刪除。

如果從第一張表裏即存儲id的datanode描述符存在,並且與第二張表找到的一樣,那麼認爲這個datanode是重啓的。如果不相等說明遺漏了這個存儲節點,需要繼續補充完整這個存儲節點的信息。具體涉及到心跳隊列、以及機架感知和網路拓撲。











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