NameNode对域名/IP的解析——DNSToSwitchMapping
实际上,这个过程很简单,NameNode把这个NameNode节点所在主机的ip地址按照用户设计的规则转换成一个对应的路径格式(如:/*/*/*/*),这个路径就对应了树状网络拓扑图中的一个非叶子节点,NameNode就把这个数据节点存放到这个非叶子节点下面,作为它的叶子节点。现在关键的问题就是NameNode如何把一个ip地址解析成一个路径的形式。刚才说了,NameNode是按照用户设计的转换规则来的,它自己并不知道如何转换,也就是说这个转换是交由用户自己来实现的。
HDFS将ip地址转换成路径的操作交给了用户自己来实现,即用户通过自己实现接口DNSToSwitchMapping,然后在配置文件中指明实现的这个类,NameNode就能够自动的调用用户的解析实现了。哦,对了,这个对应的配置项是:topology.node.switch.mapping.impl。
现在我想说一下,HDFS为什么要把ip地址的解析交给用户自己来实现?这是因为HDFS不得不把这个过程交给用户来实现,只有集群管理员即用户才知道哪些ip地址对应的机器被放在同一个机架下面,哪些机架在同一个路由器下面,说白了就是只有集群管理员才知道机器之间的网络距离。好了,现在就来具体看看与DNSToSwitchMapping相关的实现。
HDFS考虑到性能问题,一方面把DNSToSwitchMapping接口定义成一个批量解析任务,另一方面自己还定义了一个缓存类CacheDNSToSwitchMapping来保存解析的结果。同时,HDFS自己也定义了一个DNSToSwitchMapping的实现RawScriptBasedMapping,这个实现类被设计成了一个插件集成类,它通过执行shell脚本语言调用第三方的实现来获取一批ip地址的解析结果,这实际上还是将真正的解析过程交给了用户。在默认的情况下,NameNode使用ScriptBasedMapping来解析ip地址。现在来好好的分析一下这个RawScriptBasedMapping实现类。
RawScriptBasedMapping类主要包含三个参数:
scriptName:调用第三方ip解析实现的脚本命令;
maxArgs:第三方ip解析实现一次调用最多能解析多少个ip;
conf:通过配置文件获取scriptName和maxArgs;
RawScriptBasedMapping根据NameNode传过来的ip解析任务分批调用第三方ip解析器来执行这些ip的解析,每一批不能超过maxArgs个ip地址。调用的过程如下:
- private String runResolveCommand(List<String> args) {
- int loopCount = 0;
- if (args.size() == 0) {
- return null;
- }
- StringBuffer allOutput = new StringBuffer();
- int numProcessed = 0;
- if (maxArgs < MIN_ALLOWABLE_ARGS) {
- LOG.warn("Invalid value " + Integer.toString(maxArgs) + " for " + SCRIPT_ARG_COUNT_KEY + "; must be >= " + Integer.toString(MIN_ALLOWABLE_ARGS));
- return null;
- }
- while (numProcessed != args.size()) {
- int start = maxArgs * loopCount;
- List <String> cmdList = new ArrayList<String>();
- cmdList.add(scriptName);
- for (numProcessed = start; numProcessed < (start + maxArgs) && numProcessed < args.size(); numProcessed++) {
- cmdList.add(args.get(numProcessed));
- }
- File dir = null;
- String userDir;
- if ((userDir = System.getProperty("user.dir")) != null) {
- dir = new File(userDir);
- }
- ShellCommandExecutor s = new ShellCommandExecutor(cmdList.toArray(new String[0]), dir);
- try {
- s.execute();
- allOutput.append(s.getOutput() + " ");
- } catch (Exception e) {
- LOG.warn(StringUtils.stringifyException(e));
- return null;
- }
- loopCount++;
- }
- return allOutput.toString();
- }
1).自定义一个ip解析器
利用C语言写一个简单的程序,对每一个传入的ip地址,都输出对应的一个路径,然后编译连接生成一个可执行文件:ipresolve
- #include<stdio.h>
- int main(int argc, char ** argv){
- if(argc>1){
- int i;
- for(i=1; i<argc; ++i) printf("/rack/rakc/rakc%d\n",i);
- }
- return 0;
- }
2).在HDFS的配置文件core-site.xml中配置这个第三方插件
- <property>
- <name>topology.script.file.name</name>
- <value>/home/**/ipresolve</value>
- <description>可执行文件ipresolve的绝对路径</description>
- </property>
- <property>
- <name>topology.script.number.args</name>
- <value>10</value>
- <description></description>
- </property>
3).测试
- public static void main(String[] args){
- Configuration conf = new Configuration();
- ScriptBasedMapping sbp = new ScriptBasedMapping();
- sbp.setConf(conf);
- List<String> ips = new ArrayList<String>();
- ips.add("127.0.0.1");
- ips.add("127.0.0.2");
- ips.add("127.0.0.3");
- ips.add("127.0.0.4");
- List<String> result = sbp.resolve(ips);
- for(int i=0; i<result.size(); ++i){
- System.out.println(result.get(i));
- }
- }
4).测试输出
当然,对用户来说有两种方式来实现用户自定义的ip解析器,一中是直接实现DNSToSwitchMapping接口,并将这个实现类配置到配置文件的项;另一种就是上面的的插件方式。就性能而言,第一种方式为佳,就可扩展行而言,第二种方式为佳,不过,这最后还是取决于用户自己的应用场景。