了解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 children=new ArrayList();
private int numOfLeaves;
其实是一棵树,每个节点下面都可以挂一批孩子,由于还是是node节点,因此从node的接口实现发现,除了datanodedescripter之外就是innernode,所以说这是一棵树。
现在有了根节点,尝试将注册进来的datanode安排进去,称为add,在整个add过程中,都会频繁出现getNetworkLocation方法,整个东西对理解整段代码阻碍太大了,不得不搞清楚问题1.
问题又回到namenode处理注册的datanode这里来:
private void resolveNetworkLocation (DatanodeDescriptor node) {
List names = new ArrayList(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 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 resolve(List names) {
// normalize all input names to be in the form of IP addresses
names = NetUtils.normalizeHostNames(names);
List result = new ArrayList(names.size());
if (names.isEmpty()) {
return result;
}
List uncachedHosts = this.getUncachedHosts(names);
// Resolve the uncached hosts
List 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 resolve(List names) {
List m = new ArrayList(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"。
好了,那么我们看一下这个脚本该怎么配置。