1. 程式人生 > >【HDFS】hadoop的機架感知策略是啥?

【HDFS】hadoop的機架感知策略是啥?

瞭解hadoop的或多或少都聽說過機架感知策略,無論是balancer還是jobtracker分配作業、資料副本放置策略都會用到機架感知。那什麼叫機架感知?

首先故名思意機架感知就是感知機架,誰感知?就是hadoop系統嘛,更確切地說是hadoop能在系統內部建立一套伺服器和機架的位置拓撲圖,並且能識別系統節點的拓撲位置,知道了這些,才能做副本放置策略、作業本地化等更高層的設計。

難道說hadoop系統能自動感應叢集或者機房內部的網路拓撲結構?想想看,各個公司的機房拓撲或者網路結構都不一樣,採用的裝置型別也不相同,hadoop真的那麼吊能感受到?顯然不能!hadoop系統想獲得這個網路拓撲結構,需要系統管理員的幫助。

試想一下,hadoop能構建一幅網路拓撲圖,實際的網路拓撲圖又千變萬化,管理員該怎麼弄?所以這時候hadoop有必要設計一抽象的拓撲圖結構,管理員需要讓實際的網路拓撲結構儘量地與之適配。

Namenode的大管家FSNamesystem有兩個重要的成員:

  NetworkTopology clusterMap = new NetworkTopology();
  private DNSToSwitchMapping dnsToSwitchMapping;
  
這兩個東西就負責構建了機架及機架感知。

首先說拓撲邏輯類  NetworkTopology clusterMap = new NetworkTopology();這個NetworkTopology建構函式就搞了一把鎖就完事了:

  public NetworkTopology() {
    netlock = new ReentrantReadWriteLock();
  }
從建構函式裡似乎看不出來啥東西。我看檢視這個clusterMap物件,會發現很多地方呼叫它的add、remove等操作,也許從這裡可以看清楚這個網路拓撲類的面貌:
  public void add(Node node) {
    if (node==null) return;
    if( node instanceof InnerNode ) {
      throw new IllegalArgumentException(
        "Not allow to add an inner node: "+NodeBase.getPath(node));
    }
    netlock.writeLock().lock();
    try {
      Node rack = getNode(node.getNetworkLocation());
      if (rack != null && !(rack instanceof InnerNode)) {
        throw new IllegalArgumentException("Unexpected data node " 
                                           + node.toString() 
                                           + " at an illegal network location");
      }
      if (clusterMap.add(node)) {
        LOG.info("Adding a new node: "+NodeBase.getPath(node));
        if (rack == null) {
          numOfRacks++;
        }
      }
      LOG.debug("NetworkTopology became:\n" + this.toString());
    } finally {
      netlock.writeLock().unlock();
    }
  }
首先傳遞進來的必須是個實現了Node物件,而Node實際上是一個介面類,通過Ctrl+t可以看出我們熟悉的datanodeInfo也是實現了這個介面,另外還有一個NetworkTopology的內部類InnerNode也實現了這個介面,啥是InnerNode先不管,接著往下看。

上面的add方法先進行簡單地合法性判斷,然後拿到NetworkTopology構建方法裡的那把鎖,add方法在註冊datanode的時候呼叫,當時傳進來的物件是DatanodeDescriptor,注意這一句:

Node rack = getNode(node.getNetworkLocation());
getWorkLocation,怎麼得到網路位置?讓datanode描述符獲得網路位置,datanodeDescriptor繼承自DatanodeInfo物件,返回的就是一個string型別的location,初始化的時候直接寫死賦值/default-rack,如果中間沒有重新set,那麼返回的應該是/default-rack,但是datanodeInfo有個set方法,這是註冊的時候,想辦法給它確定的,這裡留下問題1,繼續往下:

上面得到一個rack的Node物件,下面又開始呼叫clusterMap的add方法,注意這個clusterMap是這樣的:

InnerNode clusterMap = new InnerNode(InnerNode.ROOT);
就是說這clusterMap是一個InnerNode物件,似乎代表了一個根,這時候繼續再看InnerNode,可以看到這個類
  private class InnerNode extends NodeBase {
    private ArrayList<Node> children=new ArrayList<Node>();
    private int numOfLeaves;
其實是一棵樹,每個節點下面都可以掛一批孩子,由於還是是node節點,因此從node的介面實現發現,除了datanodedescripter之外就是innernode,所以說這是一棵樹。

現在有了根節點,嘗試將註冊進來的datanode安排進去,稱為add,在整個add過程中,都會頻繁出現getNetworkLocation方法,整個東西對理解整段程式碼阻礙太大了,不得不搞清楚問題1.

問題又回到namenode處理註冊的datanode這裡來:

  private void resolveNetworkLocation (DatanodeDescriptor node) {
    List<String> names = new ArrayList<String>(1);
    if (dnsToSwitchMapping instanceof CachedDNSToSwitchMapping) {
      // get the node's IP address
      names.add(node.getHost());
    } else {
      // get the node's host name
      String hostName = node.getHostName();
      int colon = hostName.indexOf(":");
      hostName = (colon==-1)?hostName:hostName.substring(0,colon);
      names.add(hostName);
    }
    //特別需要注意這裡,解析一個節點屬於哪個機架,傳入的引數可以是機器的ip也可能是機器名,所以這裡要特別注意。在編寫解析指令碼的時候,必須要考慮到這兩種情況
//否則,可能有些機器解析不了。
    // resolve its network location
    List<String> rName = dnsToSwitchMapping.resolve(names);
    String networkLocation;
    if (rName == null) {
      LOG.error("The resolve call returned null! Using " + 
          NetworkTopology.DEFAULT_RACK + " for host " + names);
      networkLocation = NetworkTopology.DEFAULT_RACK;
    } else {
      networkLocation = rName.get(0);
    }
    node.setNetworkLocation(networkLocation);
  }
整個方法用到dnsToSwitchMapping物件的resolve方法,關鍵的東西來了,這個DNSToSwitchMapping介面就完成了拓撲結構的對映,目前只實現了CachedDNSToSwitchMapping這種,從上面的方法可以看出來你把機器IP輸入,通過resolve方法直接告訴了你網路位置。ok,進去看看吧,
  public List<String> resolve(List<String> names) {
    // normalize all input names to be in the form of IP addresses
    names = NetUtils.normalizeHostNames(names);

    List <String> result = new ArrayList<String>(names.size());
    if (names.isEmpty()) {
      return result;
    }

    List<String> uncachedHosts = this.getUncachedHosts(names);

    // Resolve the uncached hosts
    List<String> resolvedHosts = rawMapping.resolve(uncachedHosts);
    this.cacheResolvedHosts(uncachedHosts, resolvedHosts);
    return this.getCachedHosts(names);

  }
先把你傳進來的東西統統強保證成ip的形式,然後看看這些ip是不是被cache住了,如果cache住了,直接就能從cache裡拿到這個機器的位置。

否則對於沒有cache的就呼叫rawMapping 的resolve方法,這個rawMapping追蹤發現是ScriptBasedMapping類的內部類RawScriptBasedMapping的例項,前面rawMapping 實際呼叫的resolve方法是:

  public List<String> resolve(List<String> names) {
    List <String> m = new ArrayList<String>(names.size());
    
    if (names.isEmpty()) {
      return m;
    }

    if (scriptName == null) {
      for (int i = 0; i < names.size(); i++) {
        m.add(NetworkTopology.DEFAULT_RACK);
      }
      return m;
    }
    
    String output = runResolveCommand(names);
    if (output != null) {
      StringTokenizer allSwitchInfo = new StringTokenizer(output);
      while (allSwitchInfo.hasMoreTokens()) {
        String switchInfo = allSwitchInfo.nextToken();
        m.add(switchInfo);
      }
      
      if (m.size() != names.size()) {
        // invalid number of entries returned by the script
        LOG.warn("Script " + scriptName + " returned "
            + Integer.toString(m.size()) + " values when "
            + Integer.toString(names.size()) + " were expected.");
        return null;
      }
    } else {
      // an error occurred. return null to signify this.
      // (exn was already logged in runResolveCommand)
      return null;
    }
    
    return m;
  }

這裡就比較簡單了,看起來是呼叫一個指令碼,然後執行這個指令碼,這個指令碼具有這樣的功能,輸入ip地址給它,它給你返回位置,我擦,這不就是讓管理員手工配置一個機器和機架的對映關係嗎!

this.scriptName = conf.get(SCRIPT_FILENAME_KEY);
static final String SCRIPT_FILENAME_KEY = "topology.script.file.name";
配置檔案中key “topology.script.file.name”指定的指令碼的位置,這個指令碼要有可執行許可權,完成上面的功能。
if (scriptName == null) {
      for (int i = 0; i < names.size(); i++) {
        m.add(NetworkTopology.DEFAULT_RACK);
      }
      return m;
    }
    
如果你沒有配置這個東西,那麼所有的節點都會在同一個機架上,即"/default-rack"。

好了,那麼我們看一下這個指令碼該怎麼配置。