大数据处理系统:Hadoop源代码情景分析
上QQ阅读APP看书,第一时间看更新

第3章 Hadoop集群和YARN

3.1 Hadoop集群

虽然Hadoop也可以在单机上运行,但是这个平台的典型运行场景无疑是在多机的集群(Cluster)上。我们把运行着Hadoop平台的集群,就Hadoop平台的边界所及,称为“Hadoop集群”。其中的每台机器都成为集群的一个“节点(node)”,节点之间连成一个局域网。这个局域网一般都是交换网,而不是路由网。这就是说,集群中只有交换机(switch),一般是二层交换机,也可能是三层交换机,但是没有普通的路由器,因为那些路由器引入的延迟太大了。不过这也不绝对,有时候可能确实需要将一个集群分处在不同网段中,而通过路由器相连,但是这并不影响Hadoop的运行(除性能降低之外)。就Hadoop而言,路由器与交换机在逻辑上是一样的。

集群内的节点之间可以通过IP地址通信,也可以通过节点的域名即URL通信,这就需要有DNS的帮助。这意味着,在网络可以通达的某处存在着DNS服务,因而可以根据对方的URL查到其IP地址。这也意味着,集群内这些节点都应有个域名,并且登记在DNS中。实际上节点间还可以通过节点名通信,但是那跟URL本质上是一样的,最后总是转换成IP地址。不管是静态设定还是通过DHCP动态分配,每个节点都必须有个IP地址。

既然可能需要通过域名互相访问,这些节点就得与DNS打交道。一般涉及DNS的操作都在操作系统或底层的库程序中,对于应用层是透明的。比方说我们通过HTTP访问网站就只需要提供其域名,而HTTP驱动层自然会与DNS服务器交互以获得目标网站的IP地址。但是Hadoop不能甘于DNS对其保持透明,因为它的有些操作需要知道具体节点的IP地址、域名之类的信息,因此需要直接与DNS交互,为此Hadoop定义了一个名为DNS的类,用来提供帮助。


      class DNS{}

      ]String cachedHostname=resolveLocalHostname()

      ]String cachedHostAddress=resolveLocalHostIPAddress()

      ]String LOCALHOST="localhost"

      ]reverseDns(InetAddress hostIp, String ns) //ns:The host name of a reachable DNS server

                          //这是逆向查询,从 IP地址查其域名参数nsDNS服务器的地址

        > String[]parts=hostIp.getHostAddress().split("\\.")

        > String reverseIP=parts[3]+"."+parts[2]+"."+parts[1]+"."+parts[0]+".in-addr.arpa"


          //倒排 IP地址的4个字段,例如“110.22.33.4”就变成“4.33.22.110.in-addr.arpa”

      > DirContext ictx=new InitialDirContext()

      > attribute=ictx.getAttributes(

              "dns://"+((ns==null)? "":ns)+"/"+reverseIP, new String[]{"PTR"})

                                    //形成一个DNS查询语句

      > String hostname=attribute.get("PTR").get().toString()

      > hostnameLength=hostname.length()

      > if (hostname.charAt(hostnameLength-1)==.){

      >+ hostname=hostname.substring(0, hostnameLength-1)

      > }

      > return hostname

    ]getIPs(String strInterface, boolean returnSubinterfaces)

                //获取绑定在某个网口(例如 eth0 eth0∶0)上的所有 IP地址

    ]getDefaultIP(String strInterface) //获取绑定在某个网口上的默认 IP地址

    ]resolveLocalHostname()    //获取本机的主机名

      > String localhost=InetAddress.getLocalHost().getCanonicalHostName()

      > return localhost

    ]resolveLocalHostIPAddress() //获取本机的 IP地址

      > String address=InetAddress.getLocalHost().getHostAddress()

    ]getDefaultHost(String strInterface, String nameserver)//参数nameserverDNS服务器地址

      > if ("default".equals(strInterface))return cachedHostname

      > if ("default".equals(nameserver))return getDefaultHost(strInterface)

      >> return getDefaultHost(strInterface, null) //将第二个参数设成null

      > String[]hosts=getHosts(strInterface, nameserver)

      > return hosts[0]

    ]getHosts(String strInterface, String nameserver) //获取绑定于网口strInterface的所有域名

                    //参数nameserverDNS服务器地址或 IP地址,可以是null

      > String[]ips=getIPs(strInterface)     //获取该网口的所有 IP地址

      > Vector<String> hosts=new Vector<String>()

      > for (int ctr=0; ctr < ips.length; ctr++){ //逐一查询绑定于这些地址的域名

      >+ hosts.add(reverseDns(InetAddress.getByName(ips[ctr]), nameserver))

      > }

      > return hosts.toArray(new String[hosts.size()]) //返回这些域名

这个类没有构造函数,实际上也未创建对象,而只是作为类似于程序库那样的模块存在。模块外部需要调用其某个函数时,就通过例如DNS.getDefaultHost()这样的方式加以调用。这个模块也有几个数据成分,用来缓存本机的主机名和IP地址。

在Hadoop涉及DNS的代码中,这些就已经是最底层的了,再往下就由Java语言的SDK,即JDK提供了。例如InetAddress.getLocalHost(),这里的InetAddress就是从JDK导入的,在源码文件DNS.java的前面有“import java.net.InetAddress”。

举个使用的实例。在HDFS子系统内DataNode的代码中,有个方法函数getHostName(),用来获取本节点的主机名,其实现是这样的:


      private static String getHostName(Configuration config)throws UnknownHostException {

        String name=config.get(DFS_DATANODE_HOST_NAME_KEY);

                                    //先看看在配置文件中是否已有设定

        if (name==null){          //如果没有设定,就求助于DNS服务

          name=DNS.getDefaultHost(

            config.get(DFS_DATANODE_DNS_INTERFACE_KEY,

                      DFS_DATANODE_DNS_INTERFACE_DEFAULT),

            config.get(DFS_DATANODE_DNS_NAMESERVER_KEY,

                      DFS_DATANODE_DNS_NAMESERVER_DEFAULT));

        }

        return name;

      }

Hadoop允许在配置文件中设定本节点的主机名,所以先要看看Configuration中是否有这样的配置项,如果没有就求助于DNS服务。整个过程涉及5个字符串常数:


      String  DFS_DATANODE_HOST_NAME_KEY="dfs.datanode.hostname"

      String  DFS_DATANODE_DNS_INTERFACE_KEY="dfs.datanode.dns.interface"

      String  DFS_DATANODE_DNS_INTERFACE_DEFAULT="default"

      String  DFS_DATANODE_DNS_NAMESERVER_KEY="dfs.datanode.dns.nameserver"

      String  DFS_DATANODE_DNS_NAMESERVER_DEFAULT="default"

可见,如果配置文件中设定了本节点主机名,则配置项名称是“dfs.datanode.hostname”。然而我们在配置文件中搜索不到这个配置项,所以并未设定。下面的配置项“dfs.datanode.dns.interface”倒是有,其值为“default”。这个配置项本应是网卡的名称,现在采用默认。下一个配置项是DNS服务器,我们在前面看到DNS.getDefaultHost()的第二个参数是DNS服务器的主机名nameserver;这也是可以通过配置文件设定的,配置项名称为“dfs.datanode.dns.nameserver”。事实上,hdfs-default.xml中有这个配置项:


      <property>

        <name>dfs.datanode.dns.interface</name>

        <value>default</value>

        <description>The name of the Network Interface from which a data node should

                      report its IP address.

        </description>

      </property>

配置项的值为default,因而DNS.getDefaultHost()会将其替换成null。没有DNS主机名怎么办呢?那也不要紧,操作系统或DNS驱动自会将其补上。

这里应该说明,代码中的config是个Configuration类的对象,我们不妨称之为“配置块”。配置块中的信息主要来自配置文件,但也可以在程序中动态加以设置或改变。所以,配置信息主要来自配置文件,但不一定完全来自配置文件。

以上所述都不涉及集群的构成和拓扑。说到Hadoop集群的构成和拓扑,这就有点复杂了。Hadoop的代码中定义了几个相关的class,用来反映和管理集群的拓扑。Hadoop之所以需要有集群的构成和拓扑信息,是因为这些信息对于合理安排数据存储和计算、对于容错、对于提高效率、都是颇为重要的。

在实际投入运行的集群中,节点机一般不太会是普通的台式PC机,而会是装在机架(rack)上的服务器,尤其是所谓“刀片式(blade)”服务器。不过我们并不关心其是否刀片式,关心的是:一般同一个机架上的服务器总是连在同一个交换机上,所以同一机架上的两个节点之间通信只需流经一台交换机,但是不同机架上的节点间通信就可能要流经至少两台交换机了。另外,容错的关键在于冗余,假定我们要把一个节点上的某些数据冗余存储在另一个节点上作为备份,那就不应该选择同一机架上的节点,要不然两个节点说不定就同时断电了。

现在的交换机都是可网管的,有自己的IP地址,所以Hadoop把交换机也看成网络中的节点。而机架也可能是可网管的,或者就让交换机同时代表着机架,因为机架上往往集成着交换机。

这样,如果把交换机和机架也看成节点,那么整个局域网或者整个集群的拓扑就是个树状的结构。Hadoop集群中能实际参与数据处理的节点都是这棵树上的叶节点,树的根节点就是整个局域网的入口,通常这是一台路由器。其余的节点则都是“中间节点”或“内部节点”,那都是交换机或机架。

Hadoop需要了解这些节点的基本情况以及连接的拓扑,所以定义了几个class来反映实际的情况。首先是反映节点基本情况的NodeBase,下面是其定义的摘要:


      class NodeBase implements Node {}

      ]static char PATH_SEPARATOR=/

      ]static String ROOT=""

      ]String name; //host∶port#

      ]String location; //string representation of this node's location

      ]int level; //which level of the tree the node resides

      ]Node parent; //its parent

      ]NodeBase(String name, String location, Node parent, int level)

      ]getPath(Node node)

        > return node.getNetworkLocation()+PATH_SEPARATOR_STR+node.getName()

请读者记住,既然是摘要,就肯定不完整,只是把其中我们可能感兴趣的内容摘出来了。例如构造函数NodeBase(),根据调用参数表的不同就有好几个,这里只列出了其中信息最详尽、参数最多的一个。

我们先看其数据部分:有name即节点名;有location即其所在的位置或路径,也就是从根节点往下以“/”分隔的节点路径,类似于文件路径,例如“/east/row8/rack3”就是东区8排3号机架;还有level即节点所处的层次;还有该节点的父节点parent,那应该是机架或交换机。注意,parent的类型是Node,但实际上Node是界面而不是类,这表示任何实现了Node界面的类都是可以的,而NodeBase就实现了这个界面。

再看其方法部分,这个类实现了Node这个界面(interface),所以至少会提供Node界面所规定的所有方法函数。不过这里只列出了一个getPath(Node node)。给定一个实现了Node界面的某类对象,Nodebase.getPath()返回其在局域网中的全路径,比方说可能是“/east/row8/rack3/Node_A”,如果节点名是“Node_A”的话。

可想而知,集群所在的局域网中的每个节点都会有个NodeBase对象。下一步就是它们互连的拓扑了。为此Hadoop定义了一个类叫NetworkTopology:


      class NetworkTopology {}

      ]static String DEFAULT_RACK="/default-rack"   //默认的机架节点名

      ]static int DEFAULT_HOST_LEVEL=2             //树的高度默认为两层

      ]InnerNode clusterMap            //这是整个集群(局域网)的拓扑图

      ]int numOfRacks                 //集群中共有几个机架

      ]class InnerNode extends NodeBase {}

      ]]List<Node> children=new ArrayList<Node>() //递归构成整个集群的拓扑

      ]]int numOfLeaves                         //本子树有几个叶节点

      ]]isRack()

      ]]isAncestor(Node n)

      ]]isParent(Node n)

      ]]add(Node n)  //Add node n to the subtree of this node,

                      //这是NetworkTopology.InnerNode.add()

      ]add(Node node) //Add a leaf node,这是NetworkTopology.add()

      ]remove(Node node)

      ]contains(Node node)                   //集群中是否含有这个节点

      ]getDatanodesInRack(String loc)          //获取指定机架上的所有节点

      ]getNode(String loc)                   //获取这个节点对象

      ]getNumOfRacks()                     //集群中共有几个机架

      ]getNumOfLeaves()                     //有几个叶节点

      ]getDistance(Node node1, Node node2)       //计算任意两个节点之间的拓扑距离

      ]isOnSameRack(Node node1, Node node2)     //两个节点是否在同一机架上

      ]getWeight(Node reader, Node node)  //节点node离节点reader的远近

                                        //0 is local,1 is same rack,2 is off rack

NetworkTopology的数据成分主要就是clusterMap,这是一个InnerNode类对象,而InnerNode类就定义于NetworkTopology内部。逻辑上NetworkTopology.clusterMap相当于根节点的InnerNode对象,实际上根节点是虚的,因为集群内并没有这么一个节点,那可能就是一条网线。但是clusterMap.children是个List,其中的元素可以是InnerNode,也可以是NodeBase。若是InnerNode则还有自己的children,这样就递归构成了整个集群的拓扑。

然而拓扑中的信息是怎么来的呢?怎么知道哪个交换机或机架上有些什么节点?这主要靠各个节点主动向相关子系统的主节点登记,最终还是来自各个节点的路径配置。

Hadoop的代码中还定义了另一个类,NetworkTopologyWithNodeGroup,这是对NetworkTopology的扩充,下面仅摘取二者相异的部分:


      class NetworkTopologyWithNodeGroup extends NetworkTopology{}

      ]…


      ]isNodeGroupAware()

        > return true

      ]isOnSameNodeGroup(Node node1, Node node2)

      ]getWeight(Node reader, Node node)

                //0 is local,1 is same node group,2 is same rack,3 is off rack

      ]class InnerNodeWithNodeGroup extends InnerNode {}

      ]]isNodeGroup()

代码中有注释,说明这是针对4层结构的,而NetworkTopology则是针对3层结构的。比较二者的函数getWeight()的返回值,对于NetworkTopology,返回值是0至2,而对于NetworkTopologyWithNodeGroup则是0至3。这多出来的一层应该是在机架内部,把节点又分成了组。

那么究竟是3层还是4层呢?这要由系统管理员根据具体的集群网络来加以配置。Hadoop的代码中包含了一些用于系统配置的.xml文件(以XML语言编写)。其中有个文件core-default.xml,在目录common-proj ect/hadoop-common/src/main/resources中,就是一个十分重要的配置文件。当然,同样也很重要的配置文件还有好多。

配置文件中有很多配置项,每个配置项都是一个KV对,即“键/值对”,均以字符串模式存在于配置文件中。Hadoop的程序要用到具体配置项时就从配置块中读取,而配置块的信息则来自配置文件。以NetworkTopology对NetworkTopologyWithNodeGroup为例,我们可以了解Hadoop是怎样使用这些配置项的。当Hadoop需要创建一个网络拓扑对象,但不确定该是NetworkTopology还是NetworkTopologyWithNodeGroup时,程序中调用的是NetworkTopology.getInstance(),虽然这是NetworkTopology的getInstance(),但实际上并不局限于此,从而提供了灵活性:


      [NetworkTopology.getInstance()]




        public static NetworkTopology getInstance(Configuration conf){

          return ReflectionUtils.newInstance(

            conf.getClass(CommonConfigurationKeysPublic.NET_TOPOLOGY_IMPL_KEY,

                      NetworkTopology.class, NetworkTopology.class), conf);

      }

这个函数返回的可以是个NetworkTopology类对象,但也可以是继承、扩充了NetworkTopology的某个子类的对象,如NetworkTopologyWithNodeGroup。具体的对象是通过ReflectionUtils.newInstance()创建的,而究竟是创建哪一个类的对象则临时由conf.getClass(),即Configuration.getClass()确定。这个函数的第一个参数为配置项名称,它会在配置块中寻找该配置项的值,这就确定了实际要创建的是哪一个类的对象。如果配置块中没有这样的配置项,那就以第二个参数所给定的类作为默认,那就是NetworkTopology.class。注意,NetworkTopology.class并非一个NetworkTopology类对象,而是一个Class类对象,这是对于NetworkTopology这个类的描述。如果找到了配置项的值,就是目标类的路径名,那还得核对一下第三个参数,在这里也是NetworkTopology.class。这个参数本应是对某个界面(Interface)的描述,也可以是对某个基础类的描述,看所给定的类是否实现了这个界面,或者是否是对该基础类的扩充。

这里我们就看到Java语言的reflect机制的好处(之一)了。如果没有reflect机制,这里一般而言就会需要好多类似于下面这样的if语句:


      if(…)ret=new NetworkTopology(…);

      else if (…)ret=new NetworkTopologyWithNodeGroup(…)

      else if (…)…

那么这里的配置项名称是什么呢?在CommonConfigurationKeysPublic这个类中定义了许多常数字符串,其中之一就是NET_TOPOLOGY_IMPL_KEY:


      String  NET_TOPOLOGY_IMPL_KEY="net.topology.impl"

这个字符串的名称是NET_TOPOLOGY_IMPL_KEY,这是供程序代码用的;字符串的值是"net.topology.impl",这是配置项的名称。所以这个配置项的名称是net.topology.impl。如前所述,配置项不一定全都出现在配置文件中,程序中也可以在某个具体的Configuration对象中动态创建临时的配置项。不过配置文件core-default.xml中确实提供了这个名为net.topology.impl的配置项:


      <property>

        <name>net.topology.impl</name>

        <value>org.apache.hadoop.net.NetworkTopology</value>

        <description> The default implementation of NetworkTopology which

                      is classic three layer one.

        </description>

      </property>

这是标准的配置项格式,有名称name,有值value,还有说明description。这里说明采用NetworkTopology,并说明这是经典的三层结构的拓扑。

对这个配置项是这样使用,别的配置项也是一样,Hadoop的代码中到处都是这样的运行时动态配置。这个“运行时”配置不一定是初始化阶段进行的配置,那虽然也是“运行时”,但实际上却是半静态的,而Hadoop所用的这种方法才真正是动态的。

NetworkTopology中的内容是怎么来的呢?主要是由集群中各个节点主动向“主节点”报到、登记的。事实上,真正关心拓扑信息的首先是文件系统的主节点,即HDFS子系统的NameNode节点,因为这个节点需要安排文件内容的存储,这就需要知道哪些节点在同一个机架上,或接在同一台交换机上。另外,YARN子系统的主节点,即资源管理者ResourceManager,以及各个具体(应用)作业的管理者,有时候也需要知道集群的拓扑信息。大数据处理的原则是“数据在哪,计算就在哪”,但是有时候因条件所限无法把全部计算或者哪怕是部分计算放在数据所在的那个节点上,从而需要跨节点搬运数据,这时候把计算放在哪些节点上为好呢?这就需要用到拓扑信息了。

可是这里又有个问题。集群中那些运行着Hadoop软件的节点,固然可以主动向主节点登记,但是交换机和机架怎么办,有关它们的信息是怎么来的?它们不执行任何Hadoop软件,不会主动登记。所以有关交换机(和机架)的信息有其特殊性,需要特别加以管理。

为此Hadoop定义了一个界面DNSToSwitchMapping,意为从DNS域名如“x1.y2.com”至交换机节点名的映射。另外还有个界面DNSToSwitchMappingWithDependency则继承和扩充了这个界面,用于更复杂和灵活的拓扑。定义于DNSToSwitchMapping界面的函数主要就是resolve()。给定一个(或几个)域名或IP地址,如x1.y2.com,这个函数加以解析并返回该节点所在的位置,如/foo/rack。这里的foo是一台交换机,rack是所在的机架。

抽象类AbstractDNSToSwitchMapping实现了DNSToSwitchMapping界面,然而这只是抽象类。实际可用的、对此抽象类的扩充是CachedDNSToSwitchMapping,意为缓存着的映射,但是这依旧没有解决有关交换机和机架的信息来源问题。事实上,在目前Hadoop平台的设计中,这些信息来源于人为的配置。Hadoop的设计中提供了两个方法:一个是通过人工编辑的映射表文件提供信息;另一个是通过脚本生成这些信息。

Hadoop的代码中有两个类是对CachedDNSToSwitchMapping的扩充。其一是TableMapping,这是基于映射表文件的;其二是ScriptBasedMapping,这是基于脚本的。至于具体用哪一种,仍是通过配置项设定的。

同样是在CommonConfigurationKeysPublic这个类中,还有几个常数字符串的定义:


      String  NET_TOPOLOGY_NODE_SWITCH_MAPPING_IMPL_KEY=

                                          "net.topology.node.switch.mapping.impl"

      String  NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY="net.topology.script.file.name"

      String  NET_TOPOLOGY_TABLE_MAPPING_FILE_KEY="net.topology.table.file.name"

      String NET_DEPENDENCY_SCRIPT_FILE_NAME_KEY=

                                          "net.topology.dependency.script.file.name"

相应地,在配置文件core-default.xml中,则有:


      <property>

        <name>net.topology.node.switch.mapping.impl</name>

        <value>org.apache.hadoop.net.ScriptBasedMapping</value>

        <description> The default implementation of the DNSToSwitchMapping.It

          invokes a script specified in net.topology.script.file.name to resolve

          node names.If the value for net.topology.script.file.name is not set, the

          default value of DEFAULT_RACK is returned for all node names.

        </description>

      </property>




      <property>

        <name>net.topology.script.file.name</name>

        <value></value>                         //value为空

        <description> The script name that should be invoked to resolve DNS names to

          NetworkTopology names.Example:the script would take host.foo.bar as an

          argument, and return/rack1 as the output.

        </description>


      </property>




      <property>

        <name>net.topology.table.file.name</name>

        <value></value>                         //value为空

        <description> The file name for a topology file, which is used when the

          net.topology.node.switch.mapping.impl property is set to

          org.apache.hadoop.net.TableMapping.The file format is a two column text

          file, with columns separated by whitespace.The first column is a DNS or

          IP address and the second column specifies the rack where the address maps.

          If no entry corresponding to a host in the cluster is found, then

          /default-rack is assumed.

        </description>

      </property>

这里,配置项“net.topology.node.switch.mapping.impl”表明采用ScriptBasedMapping,因此,就要根据另一配置项“net.topology.script.file.name”确定脚本文件名,但是那个配置项的值为空,表示没有这个脚本。那怎么办呢?这里说了:那样的话,所有节点所在的机架都将是DEFAULT_RACK,即“/default-rack”。换言之,就是把集群中所有的机器节点都视为同处于同一个机架上,那就是不分机架了。所以,我们说Hadoop的YARN和HDFS子系统都是可以“rack-aware”的,即具有获知具体节点所在机架的能力;但这是有条件的,如果不满足条件就不是“rack-aware”了。

同样,配置项“net.topology.table.file.name”的值也为空,所以即使把前面那个配置项的值换成TableMapping也是一样。

有了NodeBase、NetworkTopology、DNSToSwitchMapping以后,最好还要有个工具式的模块,把解析节点所在机架的操作及其管理变得更宏观、更方便,Hadoop代码中的RackResolver类就起着这样的作用。下面是这个类的摘要:


      class RackResolver {}

      ]static DNSToSwitchMapping dnsToSwitchMapping //DNSToSwitchMapping封装起来

      ]init(Configuration conf)

        > dnsToSwitchMappingClass=conf.getClass(CommonConfigurationKeysPublic.

                          NET_TOPOLOGY_NODE_SWITCH_MAPPING_IMPL_KEY,

                          ScriptBasedMapping.class, DNSToSwitchMapping.class)

        > newInstance=ReflectionUtils.newInstance(dnsToSwitchMappingClass, conf)

        > dnsToSwitchMapping=((newInstance instanceof CachedDNSToSwitchMapping)?

                      newInstance:new CachedDNSToSwitchMapping(newInstance))

      ]resolve(Configuration conf, String hostName) //第一次解析必须调用这个

        > init(conf)

        > return coreResolve(hostName)

      ]resolve(String hostName)                 //以后的解析就调用这个


        > coreResolve(hostName)

      ]coreResolve(String hostName)

        > tmpList=new ArrayList<String>(1)

        > tmpList.add(hostName)

        > rNameList=dnsToSwitchMapping.resolve(tmpList)

        > if (rNameList==null||rNameList.get(0)==null){

        >+ rName=NetworkTopology.DEFAULT_RACK

        >+ LOG.info("Couldn't resolve"+hostName+".Falling back to"

                                              +NetworkTopology.DEFAULT_RACK)

        > }else {

        >+ rName=rNameList.get(0)

        >+ LOG.info("Resolved"+hostName+"to"+rName)

        > }

        > return new NodeBase(hostName, rName)

这个类不提供构造函数,代码中也从不创建RackResolver类对象,就相当于一个静态的数据结构和一组函数。其内部成分DNSToSwitchMapping其实是实现了这个界面的某类对象,也是作为static成分而存在的。这就是说,在一台Java虚拟机JVM上只会有一个RackResolver,也并不需要创建,只要有某个package导入(import)RackResolver, JVM就会将其装载进来,但是其内部的DNSToSwitchMapping对象(实际上是ScriptBasedMapping或TableMapping对象)是在其init()函数中创建的。需要知道一个节点在什么机架上的时候,就要调用RackResolver.resolve(hostName)来加以解析。不过首次调用时一定要用RackResolver.resolve(conf, hostName),那样才会调用其init()函数以创建DNSToSwitchMapping对象。

至于RackResolver怎样进行解析,则原理上跟文件路径名(字符串)的解析相仿,文件路径中的最后一个目录节点名就相当于这里的机架节点名,前面的都相当于交换机节点名,读者可以结合着摘要自行阅读源代码,这里就不详述了。

另外,Hadoop的代码中还定义了一个Cluster类。那是YARN框架中供各个节点上的NodeManager用来跟集群内的其他节点通信的接口。对具体的节点而言,“集群”就是其所处的“世界”,而Cluster对象就代表着“境外”,代表着这个节点的“外交”,但是Cluster类跟集群的拓扑无关。