首页 > 代码库 > Hadoop源码之Configuration
Hadoop源码之Configuration
本文hadoop版本为最新版本2.6。Configuration做为Hadoop的一个基础功能承担着重要的责任,为Yarn、HSFS、MapReduce、NFS、调度器等提供参数的配置、配置文件的分布式传输(实现了Writable接口)等重要功能。
Hadoop的加载配置文件的功能没有采用Java自己的java.util.Properties,也没有采用Apache Jakarta Commons中的Commons Configuration,而是自己单独实现了一个自己的Configuration类:org.apache.hadoop.conf.Configuration,在hadoop-common-project子工程中。它的实现子类有:HdfsConfiguration、YarnConfiguration、JobConf、NfsConfiguration、FairSchedulerConfiguration等。
一、Configuration类重要属性讲解
A、quitemode:boolean类型,配置信息加载过程中,是否处于安静模式,即有一些信息不会被记录,默认是true;
B、resources:ArrayList<Resource>类型,Resource是Configuration的内部类,有两个属性Object resource和String name;resources是一个对象数组,用于存储有关包含配置信息的对象;
C、finalParameters:Set<String>类型,所有被声明为final的变量集合,声明为final就表示不能被后续覆盖;
D、loadDefaults:boolean类型,是否加载默认配置;
E、REGISTRY:WeakHashMap<Configuration,Object>类型,用于多个对象的相关配置的注册及对它们进行管理,记录了所有的Configuration;
F、defaultResources:CopyOnWriteArrayList<String>类型,用于存储默认的配置资源名或路径;
G、properties:java内置的Properties类型,存储所有配置信息,KV值;
H、overlay:Properties类型,是用户设置的而不是通过对资源解析得到的;
I、classloader:ClassLoader类型,主要用于加载指定的类或者加载相关资源;
J、updatingResource:HashMap<String, String[]>类型,存储最近加载或修改的属性;
K、VAR_PATTERN:静态Pattern类型,用于正则匹配,Pattern.compile("\\$\\{[^\\}\\$\u0020]+\\}"),正则表达式中$、{、}都是保留字,所以需要用"\"进行转义,“\\$\\{”用于匹配${key}中的key前面的"${";最后的"\\}"用于匹配key后的"}";中间部分"[^\\}\\$\u0020]+"用于匹配属性扩展键,将匹配除了"$"、"}"和空格(\u0020指的是空格)以外的所有字符,还有"+"出现至少1次。
L、MAX_SUBST:静态int类型,默认值是20,MAX_SUBST是设定对带有环境变量的值所能够深入解析的层次数,超出这个最大的层数的值将不能够解析。
二、Configuration的初始化
A、静态代码块,用于加载默认的配置资源
1 //是一个静态初始化块,用于加载默认的配置资源。 2 static { 3 //print deprecation warning if hadoop-site.xml is found in classpath 4 ClassLoader cL = Thread.currentThread().getContextClassLoader(); 5 if (cL == null) { 6 cL = Configuration.class.getClassLoader(); 7 } 8 if (cL.getResource("hadoop-site.xml") != null) { 9 LOG.warn("DEPRECATED: hadoop-site.xml found in the classpath. " +10 "Usage of hadoop-site.xml is deprecated. Instead use core-site.xml, "11 + "mapred-site.xml and hdfs-site.xml to override properties of " +12 "core-default.xml, mapred-default.xml and hdfs-default.xml " +13 "respectively");14 }15 addDefaultResource("core-default.xml");16 addDefaultResource("core-site.xml");17 }
以上代码会在调用构造方法之前执行,会加载core-default.xml和core-site.xml两个文件,Configuration的子类也会同样加载这两个文件。
B、三个构造方法,Configuration()、Configuration(boolean loadDefaults)、Configuration(Configuration other),第一个会调用第二个参数是true;第二个确定是否加载默认配置;第三个就是将指定的Configuration对象重新复制一份。
三、加载资源
可以通过Configuration的addResource()方法或者静态方法addDefaultResource()(设置了loadDefaults标志)来添加资源到Configuration中。但是add之后资源并不会立即被加载,hadoop的Configuration被设计成了“懒加载”,即在需要时才会被加载。在add之后会调用reloadConfiguration()方法清空properties和finalParameters。
A、addDefaultResource方法,这是一个静态方法。通过这个方法可以添加系统的默认资源。在HDFS中会被用来加载hdfs-default.xml和hdfs-site.xml;在MapReduce中会被用来加载mapred-default.cml和mapred-site.xml,可以在相关的Configuration子类中找到相应地静态代码块。
1 /** 2 * Add a default resource. Resources are loaded in the order of the resources 3 * added. 4 * 5 * @param name file name. File should be present in the classpath. 6 */ 7 public static synchronized void addDefaultResource(String name) { 8 if (!defaultResources.contains(name)) { 9 defaultResources.add(name);10 for (Configuration conf : REGISTRY.keySet()) {11 if (conf.loadDefaults) {12 conf.reloadConfiguration();13 }14 }15 }16 }
此方法通过遍历REGISTRY中得元素并在元素(Configuration对象)上调用reloadConfiguration方法,就会触发资源的重新加载。
B、addResource方法,该方法有6种形式:
public void addResource(Path file)
public void addResource(String name)
public void addResource(URL url)
public void addResource(InputStream in)
public void addResource(InputStream in, String name)
public void addResource(Configuration conf)
也就是可以add的形式:可以是一个输入流、HDFS文件路径、WEB URL、CLASSPATH资源、以及Configuration对象。这些方法都会将参数封装成Resource对象后,传递给addResourceObject方法并调用该方法。在addResourceObject方法中会将Resource对象加入resources中并调用reloadConfiguration方法。代码如下:
1 public synchronized void reloadConfiguration() {2 properties = null; // trigger reload3 finalParameters.clear(); // clear site-limits4 }5 6 private synchronized void addResourceObject(Resource resource) {7 resources.add(resource); // add to resources8 reloadConfiguration();9 }
四、get*取值
A、get*方法,get*方法一般有两个参数,一个是需要获取属性的名字,另外一个是默认值,以便找不到值时就返回默认值。这些方法都会先通过getTrimmed(name)去掉两端的空格,然后调用get(String name)方法取值。get方法中经过处理过期键之后会调用substituteVars消除属性扩展情况。在调用substituteVars之前会先调用getProps方法,这个方法在发现properties为null时会通过loadResources加载配置资源。
1 protected synchronized Properties getProps() { 2 if (properties == null) { 3 properties = new Properties(); 4 HashMap<String, String[]> backup = 5 new HashMap<String, String[]>(updatingResource); 6 loadResources(properties, resources, quietmode); 7 if (overlay != null) { 8 properties.putAll(overlay); 9 for (Map.Entry<Object, Object> item : overlay.entrySet()) {10 String key = (String) item.getKey();11 updatingResource.put(key, backup.get(key));12 }13 }14 }15 return properties;16 }
loadResources相关调用代码如下:
1 private void loadResources(Properties properties, 2 ArrayList<Resource> resources, 3 boolean quiet) { 4 if (loadDefaults) { //加载默认配置资源 5 for (String resource : defaultResources) { 6 loadResource(properties, new Resource(resource), quiet); 7 } 8 9 //support the hadoop-site.xml as a deprecated case 10 if (getResource("hadoop-site.xml") != null) { 11 loadResource(properties, new Resource("hadoop-site.xml"), quiet); 12 } 13 } 14 15 for (int i = 0; i < resources.size(); i++) { //其他配置资源 16 Resource ret = loadResource(properties, resources.get(i), quiet); 17 if (ret != null) { 18 resources.set(i, ret); 19 } 20 } 21 } 22 23 private Resource loadResource(Properties properties, Resource wrapper, boolean quiet) { 24 String name = UNKNOWN_RESOURCE; 25 try { 26 Object resource = wrapper.getResource(); 27 name = wrapper.getName(); 28 //得到用于创建DOM解析器的工厂 29 DocumentBuilderFactory docBuilderFactory 30 = DocumentBuilderFactory.newInstance(); 31 //ignore all comments inside the xml file忽略XML中得注释 32 docBuilderFactory.setIgnoringComments(true); 33 34 //allow includes in the xml file提供对XML命名空间的支持 35 docBuilderFactory.setNamespaceAware(true); 36 try { 37 //设置XInclude处理状态为true,即允许XInclude机制 38 docBuilderFactory.setXIncludeAware(true); 39 } catch (UnsupportedOperationException e) { 40 LOG.error("Failed to set setXIncludeAware(true) for parser " 41 + docBuilderFactory 42 + ":" + e, 43 e); 44 } 45 //获取解析XML的DocumentBuilder对象 46 DocumentBuilder builder = docBuilderFactory.newDocumentBuilder(); 47 Document doc = null; 48 Element root = null; 49 boolean returnCachedProperties = false; 50 //根据不同资源,做预处理并调用相应刑事的DocumentBuilder.parse 51 if (resource instanceof URL) { // an URL resource 52 doc = parse(builder, (URL) resource); 53 } else if (resource instanceof String) { // a CLASSPATH resource 54 URL url = getResource((String) resource); 55 doc = parse(builder, url); 56 } else if (resource instanceof Path) { // a file resource 57 // Can‘t use FileSystem API or we get an infinite loop 58 // since FileSystem uses Configuration API. Use java.io.File instead. 59 File file = new File(((Path) resource).toUri().getPath()) 60 .getAbsoluteFile(); 61 if (file.exists()) { 62 if (!quiet) { 63 LOG.debug("parsing File " + file); 64 } 65 doc = parse(builder, new BufferedInputStream( 66 new FileInputStream(file)), ((Path) resource).toString()); 67 } 68 } else if (resource instanceof InputStream) { 69 doc = parse(builder, (InputStream) resource, null); 70 returnCachedProperties = true; 71 } else if (resource instanceof Properties) { 72 overlay(properties, (Properties) resource); 73 } else if (resource instanceof Element) { 74 root = (Element) resource; 75 } 76 77 if (root == null) { 78 if (doc == null) { 79 if (quiet) { 80 return null; 81 } 82 throw new RuntimeException(resource + " not found"); 83 } 84 root = doc.getDocumentElement(); 85 } 86 Properties toAddTo = properties; 87 if (returnCachedProperties) { 88 toAddTo = new Properties(); 89 } 90 //根节点应该是configuration 91 if (!"configuration".equals(root.getTagName())) 92 LOG.fatal("bad conf file: top-level element not <configuration>"); 93 //获取根节点的所有子节点 94 NodeList props = root.getChildNodes(); 95 DeprecationContext deprecations = deprecationContext.get(); 96 for (int i = 0; i < props.getLength(); i++) { 97 Node propNode = props.item(i); 98 if (!(propNode instanceof Element)) 99 continue; //如果子节点不是Element,则忽略100 Element prop = (Element) propNode;101 if ("configuration".equals(prop.getTagName())) {102 //如果子节点是configuration,递归调用loadResource进行处理,这意味着configuration的子节点可以是configuration103 loadResource(toAddTo, new Resource(prop, name), quiet);104 continue;105 }106 //子节点是property107 if (!"property".equals(prop.getTagName()))108 LOG.warn("bad conf file: element not <property>");109 NodeList fields = prop.getChildNodes();110 String attr = null;111 String value = http://www.mamicode.com/null;112 boolean finalParameter = false;113 LinkedList<String> source = new LinkedList<String>();114 //查找name、value、final的值115 for (int j = 0; j < fields.getLength(); j++) {116 Node fieldNode = fields.item(j);117 if (!(fieldNode instanceof Element))118 continue;119 Element field = (Element) fieldNode;120 if ("name".equals(field.getTagName()) && field.hasChildNodes())121 attr = StringInterner.weakIntern(122 ((Text) field.getFirstChild()).getData().trim());123 if ("value".equals(field.getTagName()) && field.hasChildNodes())124 value =http://www.mamicode.com/ StringInterner.weakIntern(125 ((Text) field.getFirstChild()).getData());126 if ("final".equals(field.getTagName()) && field.hasChildNodes())127 finalParameter = "true".equals(((Text) field.getFirstChild()).getData());128 if ("source".equals(field.getTagName()) && field.hasChildNodes())129 source.add(StringInterner.weakIntern(130 ((Text) field.getFirstChild()).getData()));131 }132 source.add(name);133 134 // Ignore this parameter if it has already been marked as ‘final‘135 if (attr != null) {136 if (deprecations.getDeprecatedKeyMap().containsKey(attr)) {137 DeprecatedKeyInfo keyInfo =138 deprecations.getDeprecatedKeyMap().get(attr);139 keyInfo.clearAccessed();140 for (String key : keyInfo.newKeys) {141 // update new keys with deprecated key‘s value142 loadProperty(toAddTo, name, key, value, finalParameter,143 source.toArray(new String[source.size()]));144 }145 } else {146 loadProperty(toAddTo, name, attr, value, finalParameter,147 source.toArray(new String[source.size()]));148 }149 }150 }151 152 if (returnCachedProperties) {153 overlay(properties, toAddTo);154 return new Resource(toAddTo, name);155 }156 return null;157 } catch (IOException e) {158 LOG.fatal("error parsing conf " + name, e);159 throw new RuntimeException(e);160 } catch (DOMException e) {161 LOG.fatal("error parsing conf " + name, e);162 throw new RuntimeException(e);163 } catch (SAXException e) {164 LOG.fatal("error parsing conf " + name, e);165 throw new RuntimeException(e);166 } catch (ParserConfigurationException e) {167 LOG.fatal("error parsing conf " + name, e);168 throw new RuntimeException(e);169 }170 }
如上,如果允许(loadDefaults==true)加载默认资源则会优先加载defaultResources中得资源,如果CLASSPATH下还有hadoop-site.xml文件也会加载;最后将指定的资源进行加载,因为有顺序,所以有同名的话会被覆盖,除非是final类型的。
通过以上getProps就会获得所有配置信息了,调用其getProperty方法就可以获取需要属性的值了。再传递给substituteVars进行属性扩展,代码如下:
1 //是配合正则表达式对象对含有环境变量的参数值进行解析的方法 2 private String substituteVars(String expr) { 3 if (expr == null) { 4 return null; 5 } 6 Matcher match = VAR_PATTERN.matcher(""); 7 String eval = expr; 8 //循环,最多做MAX_SUBST次属性扩展 9 for (int s = 0; s < MAX_SUBST; s++) {10 match.reset(eval);11 if (!match.find()) {12 return eval; //什么都没找到,返回13 }14 String var = match.group();15 var = var.substring(2, var.length() - 1); // remove ${ .. }获得属性扩展的键16 String val = null;17 try {18 //俺看java虚拟机的系统属性有没有var对应的val,这一步保证了优先使用java的系统属性19 val = System.getProperty(var);20 } catch (SecurityException se) {21 LOG.warn("Unexpected SecurityException in Configuration", se);22 }23 if (val == null) {24 val = getRaw(var); //然后是Configuration对象中得配置属性25 }26 if (val == null) {27 //属性扩展中得var没有绑定,不做扩展,返回28 return eval; // return literal ${var}: var is unbound29 }30 // substitute替换${ ... },完成属性扩展31 eval = eval.substring(0, match.start()) + val + eval.substring(match.end());32 }33 //属性扩展次数太多,抛出异常34 throw new IllegalStateException("Variable substitution depth too large: "35 + MAX_SUBST + " " + expr);36 }
这里会限制扩展次数,优先考虑配置的系统属性,然后是Configuration中配置的属性。java系统属性,可以通过-DXXX=YYY的方式在jvm或者启动命令中指定。
这样set*获取到string类型的值了,然后可以根据返回类型进行处理。
五、set*设置配置项,set相对于get则要简单一些,set*方法最终会调用set(String name, String value, String source)方法,source方法用来说明configuration的来源,一般设置为null,这个方法会调用properties和overlay的setProperty()方法,保存传入的键值对,同时也会更新updatingResource。
在编写mapreduce时,可能需要各个task共享一些数据,可以通过Configuration的set*方法来配置,并在mapper或者reducer中setup方法中的context获取。
总之来说,一、创建对象,会加载默认资源(前提是loadResource=true);二、add资源(可选,没有这步就是hadoop默认的资源了),会清楚原来的数据,但不会立即加载资源;三、get*方法,会触发资源的加载(getProps),处理属性扩展等,返回属性对应的值;四、set*设置自己的参数。
参考:
1、蔡斌 陈湘萍 《Hadoop技术内幕---深入将诶西Hadoop Common和HDFS结构设计与实现原理》
2、一些网络资源
Hadoop源码之Configuration