Hadoop-0.20.0 源代码分析

xiangya123

贡献于2012-06-04

字数:0 关键词: Hadoop 分布式/云计算/大数据

Hadoop-0.20.0 Hadoop-0.20.0 Hadoop-0.20.0 Hadoop-0.20.0 源代码分析 注:以下所有内容都是来自: http://blog.csdn.net/shirdrn/article/details/4569702 Hadoop 框 架是 两个 模型 实现 的有 机整 合, 亦即 Hadoop 分 布式 文件 系统 ( HDFS)与 MapReduce 并 行编 程模 型, 也就 是说 , Hadoop 框 架要 能够 提供 的基 本功 能就 是, 在存 储 系统HDFS 上 进行 MapReduce 并 行计 算, 所以 ,如 果想 要了 解 Hadoop 框 架的 工作 原理 和 运行 机制 ,主 要从 这两 个方 面着 手。 其实,Hadoop 中MapReduce 并 行计 算应 该是 在HDFS 实 现的 ,因 此了 解计 算所 基 于HDFS 应 该是 入口 点, 当对 HDFS 有 了一 定的 了解 ,就 能够 知道 这样 一个 并行 计算 平台 能够 提供 哪 些进 行计 算的 基础 要素 。 当然,在 了解 HDFS 之前,应 该先 熟悉 一下 Hadoop 对 文件 系统 FS 是 如何 实现 的 ,都 提供 了 哪些 操作 。在org.apache.hadoop.fs 包中,提 供了 文件 系统 的高 层抽 象 (FileSystem 类), 基 于该 抽象 的文 件系 统 ,可 以来 实现 满足 实际 需要 的文 件系 统实 现类 ,例 如用 来在 本地 存储 原 生文 件的 文件 系统 ( RawLocalFileSystem) ,例 如一 个文 件系 统之 上可 以存 在其 它一 些 类 型的 文件 系统 (基 于校 验和 的文 件系 统 ChecksumFileSystem 类 就是 这样 的, ChecksumFileSystem extends FilterFileSystem,FilterFileSystem 是 一个 最基 本的 文件 系 统 实现 )。 我 在阅 读源 代码 的过 程中 ,首 先从 与 org.apache.hadoop.fs 包 中文 件系 统相 关的 其它 包 org.apache.hadoop.security 开 始。 org.apache.hadoop.security 包 中的 一些 类涉 及到 文件 系统 中用 户的 信息 ,例 如用 户权 限等 等。所以,为 了能 够深 入了 解文 件系 统和 方便 阅读 源代 码 ,就 应该 了解 与文 件系 统相 关的 安 全 支持 ,实 际上 也就 是位 于 Hadoop 源 代码 中 org.apache.hadoop.security 包 中实 现。 下面org.apache.hadoop.security 包 中类 的继 承关 系: view plain 1.◦java.lang.Object 2.◦org.apache.hadoop.security.Group(implementsimplementsimplementsimplementsjava.security.Principal) 3.◦org.apache.hadoop.security.SecurityUtil 4.◦org.apache.hadoop.security.SecurityUtil.AccessControlList 5.◦java.lang.Throwable(implementsimplementsimplementsimplementsjava.io.Serializable) 6.◦java.lang.Exception 7.◦java.io.IOException 8.◦org.apache.hadoop.fs.permission.AccessControlException 9. ◦org.apache.hadoop.security.AccessControlException 10.◦org.apache.hadoop.security.User(implementsimplementsimplementsimplementsjava.security.Principal) 11.◦org.apache.hadoop.security.UserGroupInformation(implementsimplementsimplementsimplementsjava.security.P rincipal,org.apache.hadoop.io.Writable) 12.◦org.apache.hadoop.security.UnixUserGroupInformation 下 面分 别对 其中 关键 类的 源代 码进 行阅 读分 析: Group Group Group Group 类与 User User User User 类 首先,org.apache.hadoop.security.Group 类与org.apache.hadoop.security.User 类 都是 一 个 实体 类, 表征 一个 属于 HDFS 文 件系 统中 存在 的一 类实 体, 它们 的定 义非 常相 似, 下面 只 拿出 Group 类 来说 明。 Group 类 表示 一个 组的 概念 实现 ,它 实现 了 java.security.Principal 接 口, 也就 是说 Group 类 表示 一个 用来 容纳 一些 对象 的实 体 ,比 如一 个组 中可 以包 含多 个不 同的 用户 ,一 个组 中可 以 包含 多种 不同 的权 限, 一个 组中 还可 以包 含多 种授 权的 证书 ,等 等。 这个 类比 较容 易 ,包 含 一个 final 修 饰的 组名 称的 字段 ,也 就是 说一 个组 一旦 创建 就不 能修 改组 名称 , Group 类 没 有提 供修 改组 名称 的方 法 。新 创建 一个 组的 时候 ,需 要指 定组 名称 。该 类中 比较 重要 的 是 equals 方 法, 用来 比较 某个 Object 对 象( 组对 象) 是够 与该 组( this) 相互 匹配 。 UserGroupInformation UserGroupInformation UserGroupInformation UserGroupInformation 抽象类 在Hadoop 框 架中 ,分 布式 文件 系统 框架 HDFS 具 有一 个用 来存 储用 户和 组信 息的 实现 , 它 是通 过一 个位 于 org.apache.hadoop.security 包 中的 UserGroupInformation 抽 象类 来抽 象 这些 信息 的 ,如 果对 于特 定的 基于 用户 和组 的操 作系 统 ,都 可以 继承 自该 抽象 类 ,用 来实 现 表示 用户 与组 的一 些信 息的 实体 ,及 其一 些简 单的 操作 。 下 面是 抽象 类的 源代 码: view plain 1.packagepackagepackagepackageorg.apache.hadoop.security; 2. 3.importimportimportimportjava.io.IOException; 4.importimportimportimportjava.security.AccessController; 5.importimportimportimportjava.security.Principal; 6.importimportimportimportjava.util.Set; 7. 8.importimportimportimportjavax.security.auth.Subject; 9.importimportimportimportjavax.security.auth.login.LoginException; 10. 11.importimportimportimportorg.apache.commons.logging.Log; 12.importimportimportimportorg.apache.commons.logging.LogFactory; 13.importimportimportimportorg.apache.hadoop.conf.Configuration; 14.importimportimportimportorg.apache.hadoop.io.Writable; 15. 16./**该类是一个用来储存用户和组信息的抽象类,并且它实现了 Hadoop定义的用来实现序列化 的接口 Writable类,也就是说,用户和组的信息是可序列化的。 17.*/ 18.publicpublicpublicpublicabstractabstractabstractabstractclassclassclassclassUserGroupInformationimplementsimplementsimplementsimplementsWritable,Principal{ 19.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalLogLOG=LogFactory.getLog(UserGroupInformation.classclassclassclass) ; 20.privateprivateprivateprivatestaticstaticstaticstaticUserGroupInformationLOGIN_UGI=nullnullnullnull;//用户组信息属性 21. 22.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalThreadLocalcurrentUser=newnewnewnewThreadLocal();//线程局部 Subject变量 23. 24./**获取当前用户线程的 UserGroupInformation信息 */ 25.publicpublicpublicpublicstaticstaticstaticstaticUserGroupInformationgetCurrentUGI(){ 26.Subjectuser=getCurrentUser();//调用,得到当前用户线程的 Subject 27.ifififif(user==nullnullnullnull){//如果为 null 28.user=currentUser.get();//获取当前用户线程当前线程局部变量拷贝的值 29.ifififif(user==nullnullnullnull){//没能获取到当前用户的 Subject,无法认证,直接返回 30.returnreturnreturnreturnnullnullnullnull; 31.} 32.} 33. 34.SetugiPrincipals=user.getPrincipals(UserGroupIn formation.classclassclassclass);//获取用户身份信息 35. 36.UserGroupInformationugi=nullnullnullnull; 37.ifififif(ugiPrincipals!=nullnullnullnull&&ugiPrincipals.size()==1){ 38.ugi=ugiPrincipals.iterator().next(); 39.ifififif(ugi==nullnullnullnull){ 40.throwthrowthrowthrownewnewnewnewRuntimeException("Cannotfind_currentuser_UGIintheSu bject!"); 41.} 42.}elseelseelseelse{ 43.throwthrowthrowthrownewnewnewnewRuntimeException("Cannotresolvecurrentuserfromsubject, "+ 44. "whichhad"+ugiPrincipals.size()+ 45. "UGIprincipals!"); 46.} 47.returnreturnreturnreturnugi; 48.} 49. 50./** 51.*根据构造的 UserGroupInformation实例,为当前线程设置用户和组信息 52.*/ 53.@Deprecated 54.publicpublicpublicpublicstaticstaticstaticstaticvoidvoidvoidvoidsetCurrentUGI(UserGroupInformationugi){ 55.setCurrentUser(ugi); 56.} 57. 58./** 59.*获取当前用户所拥有的 Subject实例 60.*/ 61.staticstaticstaticstaticSubjectgetCurrentUser(){ 62.returnreturnreturnreturnSubject.getSubject(AccessController.getContext());//根据线程当前 调用上下文(例如堆栈信息),构造(或者获取到)当前用户的 Subject实例 63.} 64. 65./** 66.*设置当前用户线程所具有的 UserGroupInformation的信息 67.*WARNING-Thismethodshouldbeusedonlyintestcasesandotherexcep tional 68.*cases! 69.*/ 70.publicpublicpublicpublicstaticstaticstaticstaticvoidvoidvoidvoidsetCurrentUser(UserGroupInformationugi){ 71.Subjectuser=SecurityUtil.getSubject(ugi);//根据 ugi信息获取当前用户 的 Subject实例 72.currentUser.set(user);//将当前用户线程局部变量 ,当前线程副本中的值设置为根 据 ugi获取到的 Subject实例 73.} 74. 75./**获取用户名称 76.*/ 77.publicpublicpublicpublicabstractabstractabstractabstractStringgetUserName(); 78. 79./**获取到一个用户所属的组(可能该用户属于多个组)的名称 80.*/ 81.publicpublicpublicpublicabstractabstractabstractabstractString[]getGroupNames(); 82. 83./**登录系统的方法实现 ,如果登录成功则返回登录用户的一些信息 ,包括用户 、组相关信息 的一个实例 */ 84.publicpublicpublicpublicstaticstaticstaticstaticUserGroupInformationlogin(Configurationconf)throwsthrowsthrowsthrowsLoginE xception{ 85.ifififif(LOGIN_UGI==nullnullnullnull){ 86.LOGIN_UGI=UnixUserGroupInformation.login(conf);//默认使 用Hadoop实现 的UnixUserGroupInformation来进行登录 87.} 88.returnreturnreturnreturnLOGIN_UGI; 89.} 90. 91./**从Hadoop的配 置Configuration类实例中读 取UserGroupInformation的信息 */ 92.publicpublicpublicpublicstaticstaticstaticstaticUserGroupInformationreadFrom(Configurationconf)throwsthrowsthrowsthrowsIOE xception{ 93.trytrytrytry{ 94.returnreturnreturnreturnUnixUserGroupInformation.readFromConf(conf,UnixUserGroupInform ation.UGI_PROPERTY_NAME);//默认使用 UnixUserGroupInformation实现类来读 取 Hadoop配置类实例,获取用户和组信息 95.}catchcatchcatchcatch(LoginExceptione){ 96.throwthrowthrowthrow(IOException)newnewnewnewIOException().initCause(e); 97.} 98.} 99.} 该 类中 使用 到 javax.security.auth.Subject 类 ,该 类的 实例 包含 了一 个实 体的 两种 信息 :一 个 是用 来认 证的 身份 信息 ,另 一个 与该 用户 安全 相关 的信 息, 例如 许可 证书 。 UserGroupInformation 类 实现 了 org.apache.hadoop.io.Writable 接 口, 该接 口是 Hadoop 框 架基 于 DataInput 和DataOutput 定 义的 一个 序列 化协 议, 实现 该接 口的 类支 持序 列化 操 作。org.apache.hadoop.io.Writable 接 口定 义如 下所 示: view plain 1.packagepackagepackagepackageorg.apache.hadoop.io; 2. 3.importimportimportimportjava.io.DataOutput; 4.importimportimportimportjava.io.DataInput; 5.importimportimportimportjava.io.IOException; 6. 7.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceWritable{ 8./** 9.*将对象( this)的属性字段序列化到输出流 DataOuputout中。 10.*/ 11.voidvoidvoidvoidwrite(DataOutputout)throwsthrowsthrowsthrowsIOException; 12. 13./** 14.*从输入流 DataInputin中读取属性字段信息,重组为( this)对象,这是一个反序列化 操作。 15.*/ 16.voidvoidvoidvoidreadFields(DataInputin)throwsthrowsthrowsthrowsIOException; 17.} 总 结一 下, UserGroupInformation 抽 象类 主要 定义 的操 作如 下: 1、 获取 当前 用户 线程 的用 和组 信息 ( UGI) ,通 过 getCurrentUGI()方 法实 现的 ; 2、获 取用 户名 和组 名 ,分 别通 过抽 象方 法 getUserName()和getGroupNames()方 法实 现的 ; 3、根据Hadoop 的 配置 类 Configuration 实例,登 录系 统后 返回 一个 UserGroupInformation 类 的实 例, 通过 方法 login(Configuration conf)实 现的 ; 4、读取Hadoop 的 配置 类 Configuration 实例,返 回一 个 UserGroupInformation 类 的实 例 , 通 过方 法 readFrom(Configuration conf)实 现的 。 UserGroupInformation 类 定义 了一 个与 文件 系统 相关 的用 户和 组信 息抽 象的 内容 ,Hadoop 框 架实 现了 一个 基于 Unix 系 统的 用户 和组 信息 的实 现类 UnixUserGroupInformation,该类 继 承自 UserGroupInformation 抽 象类 。 从UserGroupInformation 抽 象类 与其 子类 UnixUserGroupInformation 的 属性 字段 可以 看 出 ,抽 象类 所定 义的 功能 信息 重心 在, 描述 一个 登录 以后 获得 的 UserGroupInformation 实 例 ,而 UnixUserGroupInformation 类 主要 是侧 重于 登录 前的 信息 的处 理。 首先 看一 下 UnixUserGroupInformation 类 中定 义的 属性 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalStringDEFAULT_USERNAME="DrWho"; 2.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalStringDEFAULT_GROUP="Tardis"; 3.finalfinalfinalfinalstaticstaticstaticstaticprivateprivateprivateprivateHashMapuser2UGIMap= newnewnewnewHashMap(); 4. 5. privateprivateprivateprivate String userName; 6. privateprivateprivateprivate String[] groupNames; 7. 8. finalfinalfinalfinal privateprivateprivateprivate staticstaticstaticstatic String UGI_TECHNOLOGY = "STRING_UGI"; 前 面两 个是 默认 的 Unix 用 户名 DEFAULT_USERNAME 和 组名 DEFAULT_GROUP,另外 其 中用 户 名userName 和组名groupNames 是根据UnixUserGroupInformation 类 构造 方法 设 置的 ,这 样保 证了 即使 无法 得到 用户 和组 的信 息 ,也 能够 使用 默认 的值 去填 充 ,比 较适 合 用 于测 试, 快速 定位 到用 户名 和组 名的 设置 处。 第 二个 属性 user2UGIMap 是 一个 <用 户名 , 用 户和 组信 息实 例 >的Map,用 来快 速获 取到 用 户 和组 的信 息。 最 后一 个 UGI_TECHNOLOGY 定 义读 获取 用户 和组 信息 的方 式 ,显 然该 类中 默认 使用 从文 本 中读 取字 符串 的方 式来 构造 。 对于UnixUserGroupInformation 类 实例 的构 造, 该类 给出 了四 个方 法: view plain 1. publicpublicpublicpublic UnixUserGroupInformation() { 2. } 3. 4. publicpublicpublicpublic UnixUserGroupInformation(String userName, String[] groupNames) { 5. setUserGroupNames(userName, groupNames); 6. } 7. 8. /** 9. * 根据一个或多个包含了 “用户名和组名 ”的字符串的来构造 UnixUserGroupInformation 实 例。 10. */ 11. publicpublicpublicpublic UnixUserGroupInformation(String[] ugi) { 12. ifififif (ugi==nullnullnullnull || ugi.length < 2){ 13. throwthrowthrowthrow newnewnewnew IllegalArgumentException( "Parameter does contain at least "+ "one user name and one group name"); 14. } 15. 16. publicpublicpublicpublic staticstaticstaticstatic UnixUserGroupInformation createImmutable(String[] ugi) { 17. returnreturnreturnreturn newnewnewnew UnixUserGroupInformation(ugi) { 18. publicpublicpublicpublic voidvoidvoidvoid readFields(DataInput in) throwsthrowsthrowsthrows IOException { 19. throwthrowthrowthrow newnewnewnew UnsupportedOperationException(); 20. } 21.}; 22.} 23.String[]groupNames=newnewnewnewString[ugi.length-1]; 24.System.arraycopy(ugi,1,groupNames,0,groupNames.length); 25.setUserGroupNames(ugi[0],groupNames); 26.} 该 类实 现了 其抽 象基 类定 义的 两个 抽象 方法 ,用 来获 取用 户名 和组 名, 如下 所示 : view plain 1.publicpublicpublicpublicString[]getGroupNames(){ 2.returnreturnreturnreturngroupNames; 3.} 4. 5.publicpublicpublicpublicStringgetUserName(){ 6.returnreturnreturnreturnuserName; 7.} UserGroupInformation 抽 象类 实现 了 org.apache.hadoop.io.Writable 接 口, 并没 有在 其中 实 现序 列化 操作 的两 个方 法 ,所 以在 其子 类 UnixUserGroupInformationg 给 出了 实现 ,如下: view plain 1./** 2.*反序列化重组( this)对象 3.*/ 4.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin)throwsthrowsthrowsthrowsIOException{ 5.//首先读取 UGI类型,默认就是从文本读字符串的类型 6.StringugiType=Text.readString(in); 7.ifififif(!UGI_TECHNOLOGY.equals(ugiType)){ 8.throwthrowthrowthrownewnewnewnewIOException("ExpectUGIprefix:"+UGI_TECHNOLOGY+",butre ceiveaprefix:"+ugiType); 9.} 10. 11.//从DataInputin流对象中读取用户和组的信息 12.userName=Text.readString(in); 13.intintintintnumOfGroups=WritableUtils.readVInt(in); 14.groupNames=newnewnewnewString[numOfGroups]; 15.forforforfor(intintintinti=0;i0){ 12.currentUGI=user2UGIMap.get(ugi[0]);//根据从配置实例中读取到的用户名, 从 user2UGIMap中获取一个 UnixUserGroupInformation实例 13.} 14.ifififif(currentUGI==nullnullnullnull){ 15.trytrytrytry{ 16.currentUGI=newnewnewnewUnixUserGroupInformation(ugi);//如果 user2UGIMap中不 存在从 conf中读取用户对应的 UnixUserGroupInformation实例 ,直接根据字符串信息构造一 个 17.user2UGIMap.put(currentUGI.getUserName(),currentUGI);//同时加入 到 user2UGIMap中 18.}catchcatchcatchcatch(IllegalArgumentExceptione){ 19.throwthrowthrowthrownewnewnewnewLoginException("Loginfailed:"+e.getMessage()); 20.} 21.} 22.returnreturnreturnreturncurrentUGI; 23.} 与 执行 Unix 系 统的 Shell 命 令相 关的 三个 方法 ,介 绍如 下: view plain 1./** 2.*模拟 Unix系统的 Shell命令 whoami来获取当前用户名 3.*/ 4.staticstaticstaticstaticStringgetUnixUserName()throwsthrowsthrowsthrowsIOException{ 5.String[]result=executeShellCommand(newnewnewnewString[]{Shell.USER_NAME_COMMAND} );//调用 executeShellCommand()方法 6.ifififif(result.length!=1){ 7.throwthrowthrowthrownewnewnewnewIOException("Expectonetokenastheresultof"+ 8.Shell.USER_NAME_COMMAND+":"+toString(result)); 9.} 10.returnreturnreturnreturnresult[0]; 11.} 12. 13./** 14.*模拟 Unix系统的 Shell命令 groups来获取和当前用户相关的组列表信息 15.*/ 16.privateprivateprivateprivatestaticstaticstaticstaticString[]getUnixGroups()throwsthrowsthrowsthrowsIOException{ 17.returnreturnreturnreturnexecuteShellCommand(Shell.getGROUPS_COMMAND()); 18.} 19. 20./*根据输入的 Unix系统 Shell命令,模拟 Unix系统执行 */ 21.privateprivateprivateprivatestaticstaticstaticstaticString[]executeShellCommand(String[]command)throwsthrowsthrowsthrowsIOExcep tion{ 22.Stringgroups=Shell.execCommand(command); 23.StringTokenizertokenizer=newnewnewnewStringTokenizer(groups); 24.intintintintnumOfTokens=tokenizer.countTokens(); 25.String[]tokens=newnewnewnewString[numOfTokens]; 26.forforforfor(intintintinti=0;tokenizer.hasMoreTokens();i++){ 27.tokens[i]=tokenizer.nextToken(); 28.} 29.returnreturnreturnreturntokens; 30.} 上 面三 个方 法能 够模 拟执 行 Unix 系 统的 Shell 命 令, 与 Hadoop 框 架中 实现 的工 具 类 org.apache.hadoop.util.Shell 类 密切 相关 ,执 行 Unix 系 统的 Shell 命 令的 实现 有一 点点 复 杂 ,可 以参 考其 工具 类实 现。 上 面分 析与 登录 系统 之前 用户 和组 信息 的获 取实 现 。当 一个 用户 具备 了充 分的 信息 ,可 以登 录 文件 系统 进行 特定 的操 作 。下 面就 分析 执行 登录 的过 程了 ,有 三个 实现 方法 ,基 本原 理都 是 一致 的, 下面 给出 这三 个方 法的 声明 及其 说明 : view plain 1./** 2.*通过模拟 Unix系统执行 Shell命令获取用户名和组名信息,执行登录动作。 3.*/ 4.publicpublicpublicpublicstaticstaticstaticstaticUnixUserGroupInformationlogin()throwsthrowsthrowsthrowsLoginException; 5. 6./** 7.*调用 readFromConf方法从 conf中获取 UGI; 8.*若不为 UGI!=null,则根据 save来决定是否将 UGI存储到 conf中; 9.*若UGI==null,调用无参 Login()方法登录 ,返回不为 null的UGI,并根据 save来决定 是否将 UGI存储到 conf中。 10.*/ 11.publicpublicpublicpublicstaticstaticstaticstaticUnixUserGroupInformationlogin(Configurationconf,booleanbooleanbooleanbooleansav e)throwsthrowsthrowsthrowsLoginException; 12. 13./** 14.*设置 save=false,调用 login(conf,save)执行登录动作,也就是不把用户和组信息保 存到 conf中。 15.*/ 16.publicpublicpublicpublicstaticstaticstaticstaticUnixUserGroupInformationlogin(Configurationconf)throwsthrowsthrowsthrowsLogi nException; 最 后, 该类 还有 一个 用于 比较 两个 UGI 是 否相 同的 方法 public boolean equals(Object other)。 总 结一 下, UnixUserGroupInformation 类 主要 对登 录前 的用 户名 和组 名信 息进 行格 式化 , 使 用两 种方 式来 获取 : 1、 通过 从 Hadoop 的 配置 类实 例中 获取 到用 户名 和组 名; 2、 模拟 执行 Unix 系统Shell 命 令获 取到 用 户名 和组 名。 在Hadoop 框 架源 代码 org.apache.hadoop.fs 包 中, 都是 关于 Hadoop 文 件系 统实 现的 相 关类,主 要包 括文 件系 统模 型的 建立 ,及 其在 该文 件系 统定 义 、实 现基 本的 文件 操作 。例如 给 出文 件系 统抽 象, 对文 件系 统上 存储 的文 件执 行基 本操 作进 行抽 象, 等等 。 在 该包 中, 类的 继承 关系 如下 所示 : view plain 1.◦java.lang.Object 2.◦org.apache.hadoop.fs.BlockLocation(implementsimplementsimplementsimplementsorg.apache.hadoop.io.Wri table) 3.◦org.apache.hadoop.conf.Configured(implementsimplementsimplementsimplementsorg.apache.hadoop.conf.Co nfigurable) 4.◦org.apache.hadoop.fs.FileSystem(implementsimplementsimplementsimplementsjava.io.Closeable) 5.◦org.apache.hadoop.fs.FilterFileSystem 6.◦org.apache.hadoop.fs.ChecksumFileSystem 7. ◦org.apache.hadoop.fs.InMemoryFileSystem 8. ◦org.apache.hadoop.fs.LocalFileSystem 9.◦org.apache.hadoop.fs.HarFileSystem 10.◦org.apache.hadoop.fs.RawLocalFileSystem 11.◦org.apache.hadoop.fs.FsShell(implementsimplementsimplementsimplementsorg.apache.hadoop.util.Too l) 12.◦org.apache.hadoop.fs.Trash 13.◦org.apache.hadoop.fs.ContentSummary(implementsimplementsimplementsimplementsorg.apache.hadoop.io.Wr itable) 14.◦org.apache.hadoop.fs.FileChecksum(implementsimplementsimplementsimplementsorg.apache.hadoop.io.Writ able) 15.◦org.apache.hadoop.fs.MD5MD5CRC32FileChecksum 16.◦org.apache.hadoop.fs.FileStatus(implementsimplementsimplementsimplementsjava.lang.Comparable,or g.apache.hadoop.io.Writable) 17.◦org.apache.hadoop.fs.FileSystem.Statistics 18.◦org.apache.hadoop.fs.FileUtil 19.◦org.apache.hadoop.fs.FileUtil.HardLink 20.◦org.apache.hadoop.fs.FsUrlStreamHandlerFactory(implementsimplementsimplementsimplementsjava.net.URL StreamHandlerFactory) 21.◦java.io.InputStream(implementsimplementsimplementsimplementsjava.io.Closeable) 22.◦java.io.FilterInputStream 23.◦java.io.BufferedInputStream 24.◦org.apache.hadoop.fs.BufferedFSInputStream(implementsimplementsimplementsimplementsorg. apache.hadoop.fs.PositionedReadable,org.apache.hadoop.fs.Seekable) 25.◦java.io.DataInputStream(implementsimplementsimplementsimplementsjava.io.DataInput) 26.◦org.apache.hadoop.fs.FSDataInputStream(implementsimplementsimplementsimplementsorg.apac he.hadoop.fs.PositionedReadable,org.apache.hadoop.fs.Seekable) 27.◦org.apache.hadoop.fs.FSInputStream(implementsimplementsimplementsimplementsorg.apache.hadoop.fs. PositionedReadable,org.apache.hadoop.fs.Seekable) 28.◦org.apache.hadoop.fs.FSInputChecker 29.◦org.apache.hadoop.fs.LocalDirAllocator 30.◦java.io.OutputStream(implementsimplementsimplementsimplementsjava.io.Closeable,java.io.Flushable) 31.◦java.io.FilterOutputStream 32.◦java.io.DataOutputStream(implementsimplementsimplementsimplementsjava.io.DataOutput) 33.◦org.apache.hadoop.fs.FSDataOutputStream(implementsimplementsimplementsimplementsorg.apa che.hadoop.fs.Syncable) 34.◦org.apache.hadoop.fs.FSOutputSummer 35.◦org.apache.hadoop.fs.Path(implementsimplementsimplementsimplementsjava.lang.Comparable) 36.◦org.apache.hadoop.util.Shell 37.◦org.apache.hadoop.fs.DF 38.◦org.apache.hadoop.fs.DU 39.◦java.lang.Throwable(implementsimplementsimplementsimplementsjava.io.Serializable) 40.◦java.lang.Error 41.◦org.apache.hadoop.fs.FSError 42.◦java.lang.Exception 43.◦java.io.IOException 44.◦org.apache.hadoop.fs.ChecksumException 首 先对 文件 系统 最顶 层抽 象类 FileSystem 进 行源 代码 的阅 读分 析。 FileSystem 抽 象类 继承 自 org.apache.hadoop.conf.Configured 配 置基 类, 实现 了 java.io.Closeable 接 口, 通过 这一 点, 可以 了解 到, FileSystem 抽 象类 作为 一个 文件 系统 的 抽象 定义 ,它 是可 配置 的 ,也 就是 说可 以通 过指 定的 配置 文件 中的 一些 配置 项来 描述 一个 文 件系 统, 实际 上, 最重 要的 配置 类是 org.apache.hadoop.conf.Configuration, org.apache.hadoop.conf.Configured 中 定义 的方 法就 是 对 org.apache.hadoop.conf.Configuration 配 置类 进行 设置 或获 取, 满足 一个 基 于 org.apache.hadoop.conf.Configuration 配 置类 的其 它类 的需 要。 FileSystem 抽 象类 定义 了文 件系 统所 具有 的基 本特 征和 基本 操作 。首 先从 该抽 象类 的属 性 定 义来 看, 这些 属性 描述 了文 件系 统的 静态 特性 。该 类中 定义 了如 下属 性: view plain 1.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringFS_DEFAULT_NAME_KEY="fs.default.name"; 2./**文件系统缓存 */ 3.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalCacheCACHE=newnewnewnewCache(); 4. /** 该文件系统( this)在缓存中的键实例 */ 5. privateprivateprivateprivate Cache.Key key; 6. /** 记录文件系统类的统计信息的 Map */ 7. privateprivateprivateprivate staticstaticstaticstatic finalfinalfinalfinal Map, Statistics> statistics Table = newnewnewnew IdentityHashMap, Statistics>(); 8. /** 9. * 该文件系统( this)的统计信息的实例 10. */ 11. protectedprotectedprotectedprotected Statistics statistics; 12. /** 13. * 当文件系统关闭或者 JVM 退出以后,需要将缓存中的文件清空。该 Set中的内容是, 对缓存中文件的 Path,并且是排好序的。 14. */ 15. privateprivateprivateprivate Set deleteOnExit = newnewnewnew TreeSet(); Hadoop 框 架实 现的 文件 系统 ,从 FileSystem 的Cache CACHE 的 含义 可以 看出 ,一 个文 件 系统 可以 管理 与它 相关 的并 被缓 存的 多个 文件 系统 的实 例, 这一 组文 件系 统协 调存 储工 作 ,并 为 Hadoop 实 现的 MapReduce 并 行计 算框 架的 机制 提供 便利 的存 储基 础。 文件系统缓存 FileSystem 抽 象类 定义 了一 个文 件系 统缓 存 Cache CACHE, 用来 缓存 文件 系统 对象 。也 就 是可 能存 在多 个文 件系 统对 象, 从而 可知 ,每 个文 件系 统除 了管 理基 于其 上的 内容 之外 , 还 可能 要管 理缓 存的 一组 文件 系统 实例 ,这 要看 具体 的文 件系 统是 如何 实现 的。 当 然, 也可 能是 在分 布式 环境 中, 一个 文件 系统 管理 远程 的和 本地 的文 件系 统实 例。 为 了能 够快 速获 取到 一个 存在 于缓 存中 的文 件系 统对 象 ,Hadoop 采 用了 Hash 算法,将文 件 系统 对象 以键 值对 的方 式存 储到 HashMap 中 ,也 就 是 org.apache.hadoop.fs.FileSystem.Cache 缓 存类 定义 的 map 属 性, 如下 所示 : view plain 1. privateprivateprivateprivate finalfinalfinalfinal Map map = newnewnewnew HashMap(); 其 中, org.apache.hadoop.fs.FileSystem.Cache.Key 是 org.apache.hadoop.fs.FileSystem.Cache 的 一个 内部 静态 类, 作为 缓存 Cache 中Map 的 键 ,一 个 Key 所 包含 的内 容就 是一 个 URI 的 信息 及其 用户 名, 下面 是 Key 类 的属 性: view plain 1. finalfinalfinalfinal String scheme; 2.finalfinalfinalfinalStringauthority; 3.finalfinalfinalfinalStringusername; 缓存org.apache.hadoop.fs.FileSystem.Cache 的Map 的 值是 继承 自 FileSystem 抽 象类 的 子 类。 可以 看出 ,可 以通 过一 个合 法的 URI 信 息与 用户 名快 速获 取到 缓存 中存 在的 一个 文 件 系统 的对 象, 从而 能够 获取 到指 定文 件系 统中 文件 信息 。该 缓存 类提 供了 3个 基本 的操 作 ,如 下所 示: view plain 1./**根据 URI与Configuration,从缓存中取出一个 FileSystem实例,要求同步缓存操 作。 */ 2.synchronizedsynchronizedsynchronizedsynchronizedFileSystemget(URIuri,Configurationconf)throwsthrowsthrowsthrowsIOException; 3./**根据指定的缓存 Key实例 ,从缓存中删除该 Key对应的 FileSystem实例 ,要求同步缓存操 作。 */ 4.synchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidremove(Keykey,FileSystemfs); 5./**迭代缓存 Map,删除缓存中的缓存的全部文件系统实例,要求同步缓存操作。 */ 6.synchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidcloseAll()throwsthrowsthrowsthrowsIOException; 文件系统统计信息 上面statisticsTable 是 一个 IdentityHashMap, Statistics>, 键 是继 承自 FileSystem 的Class,值 是统 计信 息 Statistics 类。为 了在 一个 并行 计算 环境 中 进 行安 全的 计算 , Statistics 类 使用 了 java.util.concurrent.atomic 包 中的 原子 变量 属性 ,保 证 线程 安全 的原 子读 写操 作的 同时 ,提 高并 行性 能。 如下 所示 : view plain 1.privateprivateprivateprivateAtomicLongbytesRead=newnewnewnewAtomicLong(); 2.privateprivateprivateprivateAtomicLongbytesWritten=newnewnewnewAtomicLong(); 其 中, bytesRead 是 从统 计数 据中 读取 指定 数量 的字 节, 加到 当前 读取 字节 数上 。同 理 , bytesRead 是 基于 原子 写操 作的 。 另 外一 个统 计数 据属 性 protected Statistics statistics,是 对当 前 (this)的FileSystem 的统 计 信息 实例 。该 属性 是在 该文 件系 统 (this)的 实例 被构 造完 成之 后被 初始 化的 ,通 过调 用 initialize 方 法实 现统 计信 息初 始化 : view plain 1.publicpublicpublicpublicvoidvoidvoidvoidinitialize(URIname,Configurationconf)throwsthrowsthrowsthrowsIOException{ 2.statistics=getStatistics(name.getScheme(),getClass()); 3.} 然 后又 在 initialize 方 法内 部调 用了 getStatistics 方 法获 取到 一个 初始 化的 Statistics 实例。 在 该方 法中 ,在 实例 化一 个 Statistics 实 例以 后, 需要 将它 加入 到统 计信 息实 例的 缓 存 statisticsTable 中 ,以 便能 够通 过给 定的 URI 快 速获 取到 对应 的文 件系 统的 统计 信息 。 为 了便 捷操 作文 件系 统的 统计 信息 ,Filesystem 类 实现 了几 个非 常方 便的 方法 ,下 面只 列出 方 法声 明: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticsynchronizedsynchronizedsynchronizedsynchronizedMapgetStatistics(); 2.publicpublicpublicpublicstaticstaticstaticstaticsynchronizedsynchronizedsynchronizedsynchronizedListgetAllStatistics(); 3.publicpublicpublicpublicstaticstaticstaticstaticsynchronizedsynchronizedsynchronizedsynchronizedStatisticsgetStatistics(Stringscheme,Classcls); 4.publicpublicpublicpublicstaticstaticstaticstaticsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidclearStatistics(); 5.publicpublicpublicpublicstaticstaticstaticstaticsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidprintStatistics()throwsthrowsthrowsthrowsIOException; 这 几个 方法 ,都 是从 statisticsTable 中 获取 到文 件系 统的 统计 信息 。 文件缓存 属性Set deleteOnExit 是 一个 文件 缓存 ,它 用来 收集 当前 缓存 中的 文件 Path。 当文 件 系统 关闭 ,或 者 JVM 退 出的 时候 ,需 要将 缓存 中的 文件 全部 删除 。删 除缓 存文 件的 方法 是在processDeleteOnExit 方 法中 ,如 下所 示: view plain 1./** 2.*删除缓存 deleteOnExit中的全部文件,需要同步 deleteOnExit。 3.*/ 4.protectedprotectedprotectedprotectedvoidvoidvoidvoidprocessDeleteOnExit(){ 5.synchronizedsynchronizedsynchronizedsynchronized(deleteOnExit){ 6.forforforfor(Iteratoriter=deleteOnExit.iterator();iter.hasNext();){ 7.Pathpath=iter.next(); 8.trytrytrytry{ 9.delete(path,truetruetruetrue);//调用,删除目录,及其子目录和文件 10.} 11.catchcatchcatchcatch(IOExceptione){ 12.LOG.info("IgnoringfailuretodeleteOnExitforpath"+path); 13.} 14.iter.remove(); 15.} 16.} 17.} 当 一个 FileSystem 关 闭以 后 ,需 要将 该文 件系 统对 应的 Path 加 入到 文件 缓存 deleteOnExit 中 ,以 便在 文件 系统 关闭 或 JVM 退 出时 ,调 用 processDeleteOnExit 方 法删 除这 些文 件。 向 文件 缓存 中加 入一 个可 能在 文件 系统 关闭 或 JVM 退 出时 删除 的文 件 ,在deleteOnExit 方 法 中实 现的 。 文件系统抽象 下面,从FileSystem 抽 象类 “抽象”的 切面 横向 了解 一个 FileSystem 定 义了 哪些 基于 文件 系 统 的操 作 ,使 我们 能够 知道 如果 实现 一个 基于 文件 系统 ,需 要实 现哪 些基 本操 作 。如 下所 示 , FileSystem 抽 象类 中定 义了 12 个 抽象 方法 : view plain 1./**获取能够唯一标识一个 FileSystem的URI*/ 2.publicpublicpublicpublicabstractabstractabstractabstractURIgetUri(); 3. 4./** 5.*根据给定的 Pathf,打开一个文件的 FSDataInputStream输入流。 6.*@paramf待打开的文件 7.*@parambufferSize缓冲区大小 8.*/ 9.publicpublicpublicpublicabstractabstractabstractabstractFSDataInputStreamopen(Pathf,intintintintbufferSize)throwsthrowsthrowsthrowsIOExce ption; 10. 11./** 12.*为写入进程打开一个 FSDataOutputStream。 13.*@paramf待写入的文件 14.*@parampermission权限 15.*@paramoverwrite是否重写 16.*@parambufferSize缓冲区大小 17.*@paramreplication文件的块副本数量 18.*@paramblockSize块大小 19.*@paramprogress用于报告 Hadoop框架工作状况的进程 20.*@throwsIOException 21.*/ 22.publicpublicpublicpublicabstractabstractabstractabstractFSDataOutputStreamcreate(Pathf, 23.FsPermissionpermission, 24.booleanbooleanbooleanbooleanoverwrite, 25.intintintintbufferSize, 26.shortshortshortshortreplication, 27.longlonglonglongblockSize, 28.Progressableprogress)throwsthrowsthrowsthrowsIOException; 29. 30./** 31.*向一个已经存在的文件中执行追加操作 32.*@paramf存在的文件 33.*@parambufferSize缓冲区大小 34.*@paramprogress报告进程 35.*@throwsIOException 36.*/ 37.publicpublicpublicpublicabstractabstractabstractabstractFSDataOutputStreamappend(Pathf,intintintintbufferSize,Progressab leprogress)throwsthrowsthrowsthrowsIOException; 38. 39./** 40.*重命名文件 src为dst 41.*/ 42.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleanrename(Pathsrc,Pathdst)throwsthrowsthrowsthrowsIOException; 43. 44./** 45.*删除文件 46.*/ 47.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleandelete(Pathf)throwsthrowsthrowsthrowsIOException; 48. 49./** 50.*删除文件 51.*/ 52.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleandelete(Pathf,booleanbooleanbooleanbooleanrecursive)throwsthrowsthrowsthrowsIOException; 53. 54./** 55.*如果 f是一个目录,列出该目录中的文件 56.*/ 57.publicpublicpublicpublicabstractabstractabstractabstractFileStatus[]listStatus(Pathf)throwsthrowsthrowsthrowsIOException; 58. 59./** 60.*为给定的文件系统设置当前工作目录 61.*/ 62.publicpublicpublicpublicabstractabstractabstractabstractvoidvoidvoidvoidsetWorkingDirectory(Pathnew_dir); 63. 64./** 65.*获取文件系统的当前工作目录 66.*/ 67.publicpublicpublicpublicabstractabstractabstractabstractPathgetWorkingDirectory(); 68. 69./** 70.*创建一个目录 f 71.*/ 72.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleanmkdirs(Pathf,FsPermissionpermission)throwsthrowsthrowsthrowsIOExc eption; 73. 74./** 75.*获取与 f对应的统计信息实例 76.*/ 77.publicpublicpublicpublicabstractabstractabstractabstractFileStatusgetFileStatus(Pathf)throwsthrowsthrowsthrowsIOException; 上 面这 些抽 象方 法应 该是 一个 文件 系统 应该 具备 的基 本操 作 ,可 能根 据不 同的 需要 设计 一个 基于FileSystem 抽 象类 的子 类实 现类 ,这 个文 件系 统的 实现 中, 对于 某些 操作 的实 现细 节 可 能因 为文 件系 统的 特点 而不 全相 同。 因此 ,可 以灵 活设 计你 所需 要的 文件 系统 。 文件操作 在Filesystem 文 件系 统上 ,与 文件 相关 的操 作很 多 ,主 要包 括文 件的 创建 、读写、重 命名 、 拷 贝、 删除 这几 个基 本操 作。 文 件的 创建 ,包 括目 录的 创建 和非 目录 文件 的创 建, 创建 目录 的方 法如 下: view plain 1.publicpublicpublicpublicbooleanbooleanbooleanbooleanmkdirs(Pathf)throwsthrowsthrowsthrowsIOException{ 2.returnreturnreturnreturnmkdirs(f,FsPermission.getDefault()); 3.} 4. 5.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleanmkdirs(Pathf,FsPermissionpermission)throwsthrowsthrowsthrowsIOExc eption; Filesystem 抽 象类 没有 实现 如何 创建 目录 的细 节。 另 外, 还有 一个 跨文 件系 统执 行创 建目 录操 作的 实现 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticbooleanbooleanbooleanbooleanmkdirs(FileSystemfs,Pathdir,FsPermissionpermissio n)throwsthrowsthrowsthrowsIOException{ 2.booleanbooleanbooleanbooleanresult=fs.mkdirs(dir);//基于默认权限创建一个目录 ,返回文件输出流对 象 3.fs.setPermission(dir,permission);//设置 fs中创建 dir目录的权限 4.returnreturnreturnreturnresult; 5.} 通 过这 个方 法可 以看 出 ,是 在当 前文 件系 统 (this)中,在 另一 个文 件系 统 fs中 根据 指定 的 权 限来 创建 一个 目录 ,显 然这 是在 分布 式地 进行 目录 的远 程创 建操 作。 对 于非 目录 文件 的创 建 ,主 要是 为了 读或 写操 作而 打开 一个 文件 ,返 回文 件的 流对 象 ,可以 进 行流 式读 写与 追加 。对 创建 文件 的操 作 ,有10 个 重载 的方 法都 是基 于一 个 create 抽 象方 法 的: view plain 1.publicpublicpublicpublicabstractabstractabstractabstractFSDataOutputStreamcreate(Pathf, 2.FsPermissionpermission, 3.booleanbooleanbooleanbooleanoverwrite, 4.intintintintbufferSize, 5.shortshortshortshortreplication, 6.longlonglonglongblockSize, 7.Progressableprogress)throwsthrowsthrowsthrowsIOException; 还 有一 个比 较特 殊的 create 方 法, 如下 所示 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticFSDataOutputStreamcreate(FileSystemfs, 2.Pathfile,FsPermissionpermission)throwsthrowsthrowsthrowsIOException{ 3.FSDataOutputStreamout=fs.create(file);//基于默认权限创建一个文件,返回文 件输出流对象 4.fs.setPermission(file,permission);//设置 fs中创建 file文件的权限 5.returnreturnreturnreturnout; 6.} 通 过这 个方 法的 参数 可以 看出 ,是 在当 前文 件系 统 (this)中,在 另一 个文 件系 统 fs中 根据 指 定的 权限 来创 建一 个文 件 ,显 然这 是在 分布 式地 进行 文件 的远 程创 建操 作 。只 要该 文件 系 统 的的 权限 满足 远程 文件 系统 fs的 创建 要求 ,并 满足 必要 的通 信条 件, 就可 以执 行分 布式 文 件操 作。 另 外还 有两 个 open 方 法是 用来 打开 已经 存在 的文 件而 且返 回文 件流 对象 ;一 个 createNewFile 方 法内 部实 现也 是调 用了 create 方 法。 文 件的 追加 操作 ,是 通过 三个 重载 的 append 方 法实 现的 ,追 加写 操作 成功 完成 之后 ,返回 org.apache.hadoop.fs.FSDataOutputStream 流 对象 。 文 件的 重命 名操 作, 是通 过抽 象方 法 rename(Path, Path)定 义的 。 文 件的 删除 操作 ,是 通过 delete 方 法定 义的 。 本 地文 件的 拷贝 操作 ,主 要是 通过 两组 重载 的方 法实 现的 。一 组是 重载 的 copyFromLocalFile 方法:拷 贝源 文件 到目 的文 件 ,保 留源 文件 (复 制操 作 );另 一组 是重 载的moveFromLocalFile 方 法: 拷贝 源文 件到 目的 文件 ,删 除源 文件 ,这 是文 件的 移动 操 作 (就 是剪 切操 作) 。 文件、块、副本 关 于文 件和 块 ,可 以通 过 Hadoop 的 架构 设计 中了 解到 一些 相关 信息 ,一 些参 数的 含义 及其 设 置。 关 于块 ( Block),FileSystem 中 定义 了如 下两 个方 法: view plain 1./** 2.*获取文件 f的块大小 3.*/ 4.publicpublicpublicpubliclonglonglonglonggetBlockSize(Pathf)throwsthrowsthrowsthrowsIOException{ 5.returnreturnreturnreturngetFileStatus(f).getBlockSize(); 6.} 7. 8./** 9.*获取默认块大小 10.*/ 11.publicpublicpublicpubliclonglonglonglonggetDefaultBlockSize(){ 12.//defaultto32MB 13.returnreturnreturnreturngetConf().getLong("fs.local.block.size",32*1024*1024); 14.} 为 了保 证 Hadoop 分 布式 文件 系统 的可 靠性 与可 用性 ,使 用了 文件 副本 冗余 存储 和流 水线 复 制 技术 。那 么对 于文 件副 本的 设置 也是 有一 定要 求的 。下 面是 关于 副本 的一 些参 数的 操作 : view plain 1./** 2.*设置文件 src的replication因子为 replication 3.*/ 4.publicpublicpublicpublicbooleanbooleanbooleanbooleansetReplication(Pathsrc,shortshortshortshortreplication)hrowsIOException { 5.returnreturnreturnreturntruetruetruetrue; 6.} 7. 8./** 9.*获取文件 src的replication因子 10.*/ 11.@Deprecated 12.publicpublicpublicpublicshortshortshortshortgetReplication(Pathsrc)throwsthrowsthrowsthrowsIOException{ 13.returnreturnreturnreturngetFileStatus(src).getReplication(); 14.} 15. 16./** 17.*获取文件的默认副本个数,亦即 replication因子 18.*/ 19.publicpublicpublicpublicshortshortshortshortgetDefaultReplication(){returnreturnreturnreturn1;} 关 于文 件的 状态 信息 ,可 以通 过一 组重 载的 listStatus 方 法来 获取 ,文 件状 态信 息通 过 org.apache.hadoop.fs.FileStatus 实 体类 来统 计, 该类 实现 了 org.apache.hadoop.io.Writable 接 口, 因此 是可 序列 化的 。它 主要 包含 文件 的下 述信 息: view plain 1.privateprivateprivateprivatePathpath;//文件路径 2.privateprivateprivateprivatelonglonglonglonglength;//文件长度 3.privateprivateprivateprivatebooleanbooleanbooleanbooleanisdir;//是否是目录 4.privateprivateprivateprivateshortshortshortshortblock_replication;//块副本因子 5.privateprivateprivateprivatelonglonglonglongblocksize;//块大小 6.privateprivateprivateprivatelonglonglonglongmodification_time;//修改时间 7.privateprivateprivateprivatelonglonglonglongaccess_time;//访问时间 8.privateprivateprivateprivateFsPermissionpermission;//在指定文件系统中的操作权限 9.privateprivateprivateprivateStringowner;//文件属主 10.privateprivateprivateprivateStringgroup;//所属组 对 于块 ,块 是组 成文 件的 基本 单位 ,那 么给 定一 个文 件 ,它 就应 该具 有一 个块 的列 表 ,可以 通过getFileBlockLocations 方 法获 取到 一个 文件 对应 的块 所在 主机 的列 表、 所在 文件 中的 偏 移位 置等 信息 ,如 下: view plain 1./** 2.*返回一个 BlockLocation[],它包含了主机名列表、偏移位置、文件大小的信息 3.*/ 4.publicpublicpublicpublicBlockLocation[]getFileBlockLocations(FileStatusfile,longlonglonglongstart,lolololo ngngngnglen)throwsthrowsthrowsthrowsIOException{ 5.ifififif(file==nullnullnullnull){ 6.returnreturnreturnreturnnullnullnullnull; 7.} 8. 9.ifififif((start<0)||(len<0)){ 10.throwthrowthrowthrownewnewnewnewIllegalArgumentException("Invalidstartorlenparameter"); 11.} 12. 13.ifififif(file.getLen() 2.io.bytes.per.checksum 3.512 4.Thenumberofbytesperchecksum.Mustnotbelargerthanio. file.buffer.size. 5. 每 个校 验和 文件 的大 小不 能比 一个 文件 所对 应的 的缓 存大 ,一 个文 件的 缓存 大小 通 过 io.file.buffer.size 属 性可 以在 core-default.xml 文 件中 进行 配置 。 verifyChecksum 属 性默 认为 true, 表示 基于 ChecksumFileSystem 文 件系 统的 文件 ,在 读 写 过程 中, 需要 检查 校验 和文 件, 验证 原生 文件 的完 整性 。 2、 校验 和文 件 ChecksumFileSystem 提 供了 与校 验和 文件 相关 的一 些基 本的 操作 : (1) 通过 setConf 方 法, 设置 文件 系统 的校 验和 文件 大小 : view plain 1.publicpublicpublicpublicvoidvoidvoidvoidsetConf(Configurationconf){ 2.supersupersupersuper.setConf(conf); 3.ifififif(conf!=nullnullnullnull){ 4.bytesPerChecksum=conf.getInt("io.bytes.per.checksum",512); 5.} 6.} 从 基于 core-default.xml 配 置文 件的 配置 类 Configuration 的 实例 来获 取, 默认 大小 512 字 节。 (2) 判断 文件 系统 中的 文件 是否 是校 验和 文件 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticbooleanbooleanbooleanbooleanisChecksumFile(Pathfile){ 2.Stringname=file.getName(); 3.returnreturnreturnreturnname.startsWith(".")&&name.endsWith(".crc"); 4.} 一个Path file 文 件的 名称 为 “.crc”时 ,才 被认 为是 校验 和文 件。 (3) 获取 文件 系统 校验 和文 件大 小参 数: view plain 1.publicpublicpublicpublicintintintintgetBytesPerSum(){ 2.returnreturnreturnreturnbytesPerChecksum; 3.} (4) 获取 指定 大小 的文 件对 应的 校验 和文 件大 小: view plain 1.publicpublicpublicpubliclonglonglonglonggetChecksumFileLength(Pathfile,longlonglonglongfileSize){ 2.returnreturnreturnreturngetChecksumLength(fileSize,getBytesPerSum()); 3.} 可 以看 一下 , getChecksumLength 方 法是 如何 计算 一个 校验 和文 件的 大小 的: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticlonglonglonglonggetChecksumLength(longlonglonglongsize,intintintintbytesPerSum){ 2.//这里给出一计算校验和文件大小的公式: 3.//校验和文件大小 =((文件大 小+bytesPerSum+1)/bytesPerSum)*4+CHECKSUM_VERSION长度 +4 4.returnreturnreturnreturn((size+bytesPerSum-1)/bytesPerSum)*4+ 5.CHECKSUM_VERSION.length+4; 6.} (5)计 算校 验和 文件 所分 配的 缓存 的大 小: view plain 1.privateprivateprivateprivateintintintintgetSumBufferSize(intintintintbytesPerSum,intintintintbufferSize){ 2.intintintintdefaultBufferSize=getConf().getInt("io.file.buffer.size",4096);// 获取文件缓存大小,默认为 4096 3.intintintintproportionalBufferSize=bufferSize/bytesPerSum;//计算每一个校验和文 件应该分配的文件缓存大小 4.returnreturnreturnreturnMath.max(bytesPerSum, 5. Math.max(proportionalBufferSize,defaultBufferSize)); 6.} 校 验和 文件 如果 全部 放入 到文 件缓 存中 ,至 少分 配的 缓存 的大 小, 等于 校验 和文 件的 大小 。 但是,可 能文 件缓 存 bufferSize 可 能会 足够 大 ,从 而在 为校 验和 文件 分配 缓存 的时 候 ,可能 会 比一 个校 验和 文件 的大 小要 大 。检 验和 文件 需要 加入 到文 件缓 存中 ,然 后系 统从 缓存 中执 行 读写 操作 操作 。 3、 基本 文件 操作 实 现的 基本 操作 包括 文件 创建 、打开、读写、拷贝、移动。最 主要 的是 真正 实现 了文 件在 不 同 文件 系统 之间 的拷 贝操 作, 如下 所示 : view plain 1.@Override 2.publicpublicpublicpublicvoidvoidvoidvoidcopyFromLocalFile(booleanbooleanbooleanbooleandelSrc,Pathsrc,Pathdst) 3.throwsthrowsthrowsthrowsIOException{ 4.Configurationconf=getConf(); 5.FileUtil.copy(getLocal(conf),src,thisthisthisthis,dst,delSrc,conf); 6.} 7.@Override 8.publicpublicpublicpublicvoidvoidvoidvoidcopyToLocalFile(booleanbooleanbooleanbooleandelSrc,Pathsrc,Pathdst) 9.throwsthrowsthrowsthrowsIOException{ 10.Configurationconf=getConf(); 11.FileUtil.copy(thisthisthisthis,src,getLocal(conf),dst,delSrc,conf); 12.} 其中,文 件工 具类 org.apache.hadoop.fs.FileUtil 的copy 方 法是 实现 拷贝 操作 的核 心方 法 , 实 现过 程比 较复 杂, 可以 从此 处开 始一 步步 跟踪 代码 ,了 解拷 贝的 实现 细节 。 4、 读写 校验 和文 件 当 读校 验和 文件 时 ,是 为了 验证 数据 文件 的完 整性 ,在 该文 件系 统实 现类 中 ,与 检查 校验 和 文 件相 关的 类的 继承 层次 关系 如下 所示 : view plain 1.◦java.io.InputStream 2.◦org.apache.hadoop.fs.FSInputStream(implementsimplementsimplementsimplementsorg.apache.hadoop.fs.Seek able,org.apache.hadoop.fs.PositionedReadable) 3.◦org.apache.hadoop.fs.FSInputChecker 4.◦org.apache.hadoop.fs.ChecksumFileSystem.ChecksumFSInputChecker ChecksumFSInputChecker 类 对一 个打 开的 文件 (以 FSInputStream 输 入流 的形 式) ,验 证 该文 件与 其对 应的 校验 和文 件是 否匹 配, 做一 个匹 配性 检查 。 该 类封 装了 ChecksumFileSystem、FSDataInputStream、FSDataInputStream 三 个类 的对 象 ,如 下所 示: view plain 1.privateprivateprivateprivateChecksumFileSystemfs;//文件系统 2.privateprivateprivateprivateFSDataInputStreamdatas;//原生文件的输入流对象 3.privateprivateprivateprivateFSDataInputStreamsums;//校验和文件的输入流对象 当 一个 原生 文件 需要 被存 储到 指定 的主 机文 件系 统中 ,同 时计 算该 文件 对应 的校 验和 信息 , 并 将该 校验 和信 息流 式写 入到 对应 的校 验和 文件 中 。与 校验 和文 件创 建与 写操 作相 关的 类的 继 承层 次关 系如 下所 示: view plain 1.◦java.io.OutputStream 2.◦org.apache.hadoop.fs.FSOutputSummer 3.◦org.apache.hadoop.fs.ChecksumFileSystem.ChecksumFSOutputSummer ChecksumFSOutputSummer 类 为一 个校 验和 文件 提供 了一 个 FSDataOutputStream 输出 流 对象 ,它 为原 生文 件数 据生 成一 个校 验和 ,并 流式 写入 到 FSDataOutputStream sums 输 出 流中 。 在 读写 校验 和文 件的 时候 ,都 是将 校验 和文 件数 据放 到文 件缓 冲区 ,通 过流 对象 来执 行读 写 操 作。 • HarFileSystem 类 HarFileSystem 是Hadoop 归 档文 件系 统( Hadoop Archive Filesystem) 的实 现, 该文 件 系 统具 有索 引文 件及 其相 关内 容。 用来 标识 归档 文件 系统 的 URI 的 形式 如下 所示 : har://underlyingfsscheme-host:port/archivepath 或者 har:///archivepath 首 先看 该文 件系 统的 属性 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalintintintintVERSION=1;//HarFileSystem的版本号 2.privateprivateprivateprivateURIuri;//标识 HarFileSystem的URI 3.privateprivateprivateprivateintintintintversion;//等于版本号 HarFileSystem.VERSION 4.privateprivateprivateprivateURIunderLyingURI;//HarFileSystem的基本 URI 5.privateprivateprivateprivatePatharchivePath;//归档的路径 Path 6.privateprivateprivateprivatePathmasterIndex;//masterIndex文件 7.privateprivateprivateprivatePatharchiveIndex;//索引文件 8.privateprivateprivateprivateStringharAuth;//HarFileSystem文件系统的 URI授权部分字符串 该 文件 系统 类其 它文 件系 统实 现类 一样 ,实 现了 归档 文件 系统 的中 对文 件的 基本 操作 。 ???? extendsextendsextendsextends ChecksumFileSystemChecksumFileSystemChecksumFileSystemChecksumFileSystem 下 面分 析 ChecksumFileSystem 类 的直 接子 类 LocalFileSystem 类 的实 现。 LocalFileSystem 类 实现 了 FileSystem 的API, 它是 一个 基于 校验 和的 本地 文件 系统 。 通 过构 造方 法可 以看 到 ,它 是以 org.apache.hadoop.fs.RawLocalFileSystem 为 基本 文件 系 统 的, 如下 所示 : view plain 1.publicpublicpublicpublicLocalFileSystem(){ 2.thisthisthisthis(newnewnewnewRawLocalFileSystem()); 3.} 4.publicpublicpublicpublicLocalFileSystem(FileSystemrawLocalFileSystem){ 5.supersupersupersuper(rawLocalFileSystem); 6.rfs=rawLocalFileSystem; 7.} 该 类实 现了 ChecksumFileSystem 类 中定 义但 未实 现的 ,用 于向 文件 系统 报告 校验 和文 件 出 错的 方法 ,同 时把 出错 的校 验和 文件 重命 名后 ,移 动到 指定 的目 录 (bad_files)中,在该 目 录中 的文 件是 不能 够被 重新 使用 的, 如下 所示 : view plain 1.publicpublicpublicpublicbooleanbooleanbooleanbooleanreportChecksumFailure(Pathp,FSDataInputStreamin, 2. longlonglonglonginPos, 3. FSDataInputStreamsums,longlonglonglongsumsPos){ 4.trytrytrytry{ 5.Filef=((RawLocalFileSystem)fs).pathToFile(p).getCanonicalFile();// 得到 Path的规范化抽象路径名称 6.Stringdevice=newnewnewnewDF(f,getConf()).getMount();//根据路径名称 f查询:找 到同一设备上可写的最顶层目录,这里使用了 Unix系统的 df命令获取磁盘使用情况统计数据 , 确定一个有足够空间可以进行写入操作的目录 7.Fileparent=f.getParentFile(); 8.Filedir=nullnullnullnull; 9.whilewhilewhilewhile(parent!=nullnullnullnull&&parent.canWrite()&&parent.toString().startsWith (device)){ 10.dir=parent; 11.parent=parent.getParentFile(); 12.} 13.ifififif(dir==nullnullnullnull){ 14.throwthrowthrowthrownewnewnewnewIOException( 15. "notabletofindthehighestwritableparentdi r"); 16.} 17. 18.FilebadDir=newnewnewnewFile(dir,"bad_files"); 19.ifififif(!badDir.mkdirs()){ 20.ifififif(!badDir.isDirectory()){ 21.throwthrowthrowthrownewnewnewnewIOException("Mkdirsfailedtocreate"+badDir.toString()) ; 22.} 23.} 24.Stringsuffix="."+rand.nextInt(); 25.FilebadFile=newnewnewnewFile(badDir,f.getName()+suffix); 26.LOG.warn("Movingbadfile"+f+"to"+badFile); 27.in.close();//关闭文件输入流 28.f.renameTo(badFile);//renameit 29.//移除校验和文件 30.FilecheckFile=((RawLocalFileSystem)fs).pathToFile(getChecksumFile(p)); 31.checkFile.renameTo(newnewnewnewFile(badDir,checkFile.getName()+suffix)); 32.}catchcatchcatchcatch(IOExceptione){ 33.LOG.warn("Errormovingbadfile"+p+":"+e); 34.} 35.returnreturnreturnreturnfalsefalsefalsefalse; 36.} 以 文件 流作 为一 个切 面 ,阅读Hadoop 源 代码 org.apache.hadoop.fs 包 中源 代码 。关 于流 , 分 为输 入流 和输 出流 两种 ,下 面也 这样 简单 划分 为两 类进 行阅 读分 析。 输入流类 与 输入 流相 关的 接口 和类 的继 承层 次关 系如 下所 示: view plain 1.◦java.io.InputStream(java.io.Closeable) 2.◦java.io.FilterInputStream 3.◦java.io.DataInputStream(implementsimplementsimplementsimplementsjava.io.DataInput) 4.◦org.apache.hadoop.fs.FSDataInputStream(implementsimplementsimplementsimplementsorg.apache.ha doop.fs.Seekable,org.apache.hadoop.fs.PositionedReadable) 5.◦org.apache.hadoop.fs.HarFileSystem.HarFSDataInputStream FSDataInputStream 类 实现 了 Seekable 与PositionedReadable 接 口, 赋予 了 Hadoop 文 件 系统 中的 文件 输入 流分 别能 够进 行流 式搜 索与 定位 流式 读取 的语 义。 Seekable 接 口定 义如 下: view plain 1.packagepackagepackagepackageorg.apache.hadoop.fs; 2. 3.importimportimportimportjava.io.*; 4. 5./**Streamthatpermitsseeking.*/ 6.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceSeekable{ 7./** 8.*从指定文件中的位置 pos,对文件流进行前向搜索。 9.*/ 10.voidvoidvoidvoidseek(longlonglonglongpos)throwsthrowsthrowsthrowsIOException; 11. 12./** 13.*返回文件流中当前偏移位置。 14.*/ 15.longlonglonglonggetPos()throwsthrowsthrowsthrowsIOException; 16. 17./** 18.*从targetPos位置搜索文件数据的一个不同拷贝 ,搜索到则返回 true,否则返回 false。 19.*/ 20.booleanbooleanbooleanbooleanseekToNewSource(longlonglonglongtargetPos)throwsthrowsthrowsthrowsIOException; 21.} Seekable 接 口中 定义 的方 法 ,都 是基 于文 件流 的位 置进 行操 作的 方法 ,使 得在 文件 系统 中 或 文件 系统 之间 进行 流式 操作 更加 方便 。 PositionedReadable 接 口定 义如 下: view plain 1.packageorg.apache.hadoop.fs; 2. 3.importjava.io.*; 4.importorg.apache.hadoop.fs.*; 5. 6.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfacePositionedReadable{ 7./** 8.*读取文件流中最多到 length大小的字节,到字节缓冲区 buffer中,它是从给定 的 position位置开始读取的。 9.*该读取方式不改变文件的当前偏移位置 offset,并且该方法是线程安全的。 10.*/ 11.publicpublicpublicpublicintintintintread(longlonglonglongposition,bytebytebytebyte[]buffer,intintintintoffset,intintintintlength)thro wsIOException; 12. 13./** 14.*读取文件流中 length大小的字节,到字节缓冲区 buffer中,它是从给定的 position位 置开始读取的。 15.*该读取方式不改变文件的当前偏移位置 offset,并且该方法是线程安全的。 16.*/ 17.publicpublicpublicpublicvoidvoidvoidvoidreadFully(longlonglonglongposition,bytebytebytebyte[]buffer,intintintintoffset,intintintintlength) throwsIOException; 18. 19./** 20.*读取文件流中 buffer长度的字节,到字节缓冲区 buffer中,它是从给定的 position位 置开始读取的 21.*该读取方式不改变文件的当前偏移位置 offset,并且该方法是线程安全的。 22.*/ 23.publicpublicpublicpublicvoidvoidvoidvoidreadFully(longlonglonglongposition,bytebytebytebyte[]buffer)throwsIOException; 24.} PositionedReadable 接 口中 定义 了三 个基 于位 置来 进行 流式 读取 的操 作。 接 着, FSDataInputStream 类 继承 自 DataInputStream 类 ,并 实现 上述 这两 个接 口, 必须 实 现接 口中 定义 的操 作: view plain 1.packagepackagepackagepackageorg.apache.hadoop.fs; 2. 3.importimportimportimportjava.io.*; 4. 5.publicpublicpublicpublicclassclassclassclassFSDataInputStreamextendsextendsextendsextendsDataInputStreamimplementsimplementsimplementsimplementsSeekable, PositionedReadable{ 6. 7.publicpublicpublicpublicFSDataInputStream(InputStreamin) 8.throwsthrowsthrowsthrowsIOException{ 9.supersupersupersuper(in);//调用基类的构造方法,初始化一个基本流类属性 InputStreamin 10.ifififif(!(ininstanceofinstanceofinstanceofinstanceofSeekable)||!(ininstanceofinstanceofinstanceofinstanceofPositionedReadable)){ //强制保证 InputStreamin必须实现 Seekable与PositionedReadable这两个接口。 11.throwthrowthrowthrownewnewnewnewIllegalArgumentException( 12."InisnotaninstanceofSeekableorPositionedReadable"); 13.} 14.} 15. 16.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidseek(longlonglonglongdesired)throwsthrowsthrowsthrowsIOException{ 17.((Seekable)in).seek(desired);//设置 从in的desired位置开始搜索输入流 流in 18.} 19. 20.publicpublicpublicpubliclonglonglonglonggetPos()throwsthrowsthrowsthrowsIOException{ 21.returnreturnreturnreturn((Seekable)in).getPos(); 22.} 23. 24.publicpublicpublicpublicintintintintread(longlonglonglongposition,bytebytebytebyte[]buffer,intintintintoffset,intintintintlength) 25.throwsthrowsthrowsthrowsIOException{ 26.returnreturnreturnreturn((PositionedReadable)in).read(position,buffer,offset,length); 27.} 28. 29.publicpublicpublicpublicvoidvoidvoidvoidreadFully(longlonglonglongposition,bytebytebytebyte[]buffer,intintintintoffset,intintintintlength) 30.throwsthrowsthrowsthrowsIOException{ 31.((PositionedReadable)in).readFully(position,buffer,offset,length); 32.} 33. 34.publicpublicpublicpublicvoidvoidvoidvoidreadFully(longlonglonglongposition,bytebytebytebyte[]buffer) 35.throwsthrowsthrowsthrowsIOException{ 36.((PositionedReadable)in).readFully(position,buffer,0,buffer.length); 37.} 38. 39.publicpublicpublicpublicbooleanbooleanbooleanbooleanseekToNewSource(longlonglonglongtargetPos)throwsthrowsthrowsthrowsIOException{ 40.returnreturnreturnreturn((Seekable)in).seekToNewSource(targetPos); 41.} 42.} FSDataInputStream 输 入流 类最 显著 的特 征就 是, 能够 基于 流的 位置 而进 行流 式操 作。 另 外, 在 org.apache.hadoop.fs 包 中还 定义 了基 于 RAF(Random Access File) 风格 的输 入 流类 ,可 以随 机读 取该 流对 象。 继承 关系 如下 所示 : view plain 1.◦java.io.InputStream(implementsjava.io.Closeable) 2.◦org.apache.hadoop.fs.FSInputStream(implementsorg.apache.hadoop.fs.Seek able,org.apache.hadoop.fs.PositionedReadable) 3.◦org.apache.hadoop.fs.FSInputChecker 4.◦org.apache.hadoop.fs.ChecksumFileSystem.ChecksumFSInputChecker 首 先看 抽象 的输 入流 类, 实现 的源 代码 如下 所示 : view plain 1.packagepackagepackagepackageorg.apache.hadoop.fs; 2. 3.importimportimportimportjava.io.*; 4. 5.publicpublicpublicpublicabstractabstractabstractabstractclassclassclassclassFSInputStreamextendsextendsextendsextendsInputStreamimplementsimplementsimplementsimplementsSeekable, PositionedReadable{ 6./** 7.*从给定的偏移位置 pos开始搜索,下一次读取就从该位置开始读取。 8.*/ 9.publicpublicpublicpublicabstractabstractabstractabstractvoidvoidvoidvoidseek(longlonglonglongpos)throwsthrowsthrowsthrowsIOException; 10. 11./** 12.*返回文件的当前前向偏移位置 13.*/ 14.publicpublicpublicpublicabstractabstractabstractabstractlonglonglonglonggetPos()throwsthrowsthrowsthrowsIOException; 15. 16./** 17.*搜索不同的文件数据的拷贝,如果搜索到则返回 true,否则返回 false 18.*/ 19.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleanseekToNewSource(longlonglonglongtargetPos)throwsthrowsthrowsthrowsIOException; 20. 21.publicpublicpublicpublicintintintintread(longlonglonglongposition,bytebytebytebyte[]buffer,intintintintoffset,intintintintlength) 22.throwsthrowsthrowsthrowsIOException{ 23.synchronizedsynchronizedsynchronizedsynchronized(thisthisthisthis){ 24.longlonglonglongoldPos=getPos(); 25.intintintintnread=-1; 26.trytrytrytry{ 27.seek(position); 28.nread=read(buffer,offset,length); 29.}finallyfinallyfinallyfinally{ 30.seek(oldPos); 31.} 32.returnreturnreturnreturnnread; 33.} 34.} 35. 36.publicpublicpublicpublicvoidvoidvoidvoidreadFully(longlonglonglongposition,bytebytebytebyte[]buffer,intintintintoffset,intintintintlength) 37.throwsthrowsthrowsthrowsIOException{ 38.intintintintnread=0; 39.whilewhilewhilewhile(nreadenvironment;//命令行执行所需要的操作系统环境 4.privateprivateprivateprivateFiledir; 5.privateprivateprivateprivateProcessprocess;//执行命令行的子进程 6.privateprivateprivateprivateintintintintexitCode;//执行命令行完成后,退出状态码 dir 属 性表 示当 前执 行命 令所 在的 工作 目录 , environment 属 性表 示当 前命 令执 行的 环境 , 它 们在 Shell 类 中都 提供 了一 个受 保护 的设 置操 作, 可以 在 Shell 抽 象类 的子 类中 根据 需要 设 置不 同工 作目 录和 环境 ,其 中, dir 默 认为 系统 “user.dir”变 量值 , environment 使 用系 统 默 认的 环境 。 通过interval 与lastTime 属 性来 检查 ,是 否有 必要 重新 执行 一次 ,如 果是 就执 行 ,否 则重 置 退 出状 态码 exitCode 为0, 正常 退出 ,可 以在 Shell 类的run 方 法中 看到 : view plain 1.protectedprotectedprotectedprotectedvoidvoidvoidvoidrun()throwsthrowsthrowsthrowsIOException{ 2.ifififif(lastTime+interval>System.currentTimeMillis()) 3.returnreturnreturnreturn;//不需要重新执行命令行,返回 4.exitCode=0; 5.runCommand();//调用:需要重新执行命令行 6.} 通过runCommand 方 法就 可以 执行 指定 的 Shell 命 令, 它是 Shell 类 的核 心。 在分 析 runCommand 方 法之 前, 先了 解一 下与 Shell 命 令执 行相 关的 环境 信息 。 当在Windows 系 统下 ,打 开一 个 cmd 窗 口的 时候 ,执行set 命令,就 能看 到当 前系 统的 环 境 变量 的信 息, 如下 所示 : view plain 1.ALLUSERSPROFILE=C:/DocumentsandSettings/AllUsers 2.APPDATA=C:/DocumentsandSettings/Administrator/ApplicationData 3.CLASSPATH=.;E:/ProgramFiles/Java/jdk1.6.0_14/lib/tools.jar;E:/ProgramFiles /Java/jdk1.6.0_14/lib/dt.jar;E:/ProgramFiles/Java/jdk1.6.0_14/jre/lib/rt.ja r;E:/ProgramFiles/Java/jdk1.6.0_14/jre/lib/charsets.jar 4.CLIENTNAME=Console 5.CCommonProgramFiles=C:/ProgramFiles/CommonFiles 6.COMPUTERNAME=SYJ 7.CComSpec=C:/WINDOWS/system32/cmd.exe 8.DEVMGR_SHOW_NONPRESENT_DEVICES=1 9.FP_NO_HOST_CHECK=NO 10.HERITRIX_HOME=E:/MyEclipse/workspace/heritrix 11.HOME=C:/DocumentsandSettings/Administrator 12.HOMEDRIVE=C: 13.HOMEPATH=/DocumentsandSettings/Administrator 14.JAVA_HOME=E:/ProgramFiles/Java/jdk1.6.0_14 15.JSERV=D:/oracle/ora92/Apache/Jserv/conf 16.LOGONSERVER=//SYJ 17.NUMBER_OF_PROCESSORS=2 18.NUTSUFFIX=1 19.NUT_SUFFIXED_SEARCHING=1 20.OS=Windows_NT 21.Path=D:/oracle/ora92/bin;C:/ProgramFiles/Oracle/jre/1.3.1/bin;C:/ProgramFi les/Oracle/jre/1.1.8/bin;E:/ProgramFiles/CollabNetSubversionClient;E:/Pro gramFiles/Java/jdk1.6.0_14/bin;C:/WINDOWS/system32;C:/WINDOWS;C:/WINDOWS/Sy stem32/Wbem;C:/ProgramFiles/MicrosoftSQLServer/80/Tools/Binn/;C:/Program Files/MicrosoftSQLServer/90/Tools/binn/;C:/ProgramFiles/MyEclipse7.0M1/j re/bin;E:/ProgramFiles/TortoiseSVN/bin;E:/PROGRA~1/F-Secure/SSHTRI~1;D:/Pro gramFiles/MySQL/MySQLServer5.1/bin;F:/ProgramFiles/Rational/common;C:/Pr ogramFiles/StormII/Codec;C:/ProgramFiles/StormII;C:/ProgramFiles/SSHComm unicationsSecurity/SSHSecureShell;C:/ProgramFiles/IDMComputerSolutions /UltraEdit/ 22.PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH 23.PROCESSOR_ARCHITECTURE=x86 24.PROCESSOR_IDENTIFIER=x86Family6Model23Stepping10,GenuineIntel 25.PROCESSOR_LEVEL=6 26.PROCESSOR_REVISION=170a 27.ProgramFiles=C:/ProgramFiles 28.PROMPT=$P$G 29.RATL_RTHOME=F:/ProgramFiles/Rational/RationalTest 30.SESSIONNAME=Console 31.SystemDrive=C: 32.SystemRoot=C:/WINDOWS 33.TEMP=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp 34.TMP=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp 35.TMPDIR=C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp 36.USERDOMAIN=SYJ 37.USERNAME=Administrator 38.USERPROFILE=C:/DocumentsandSettings/Administrator 39.VBOX_INSTALL_PATH=E:/ProgramFiles/Sun/xVMVirtualBox/ 40.VS90COMNTOOLS=d:/MicrosoftVisualStudio9.0/Common7/Tools/ 41.windir=C:/WINDOWS 42.WV_GATEWAY_CFG=D:/oracle/ora92/Apache/modplsql/cfg/wdbsvr.app 这 些环 境变 量的 信息 都是 以键 值对 的形 式出 现的 ,当 在操 作系 统上 运行 相关 的应 用程 序的 时 候,其 实就 是在 上述 环境 变量 所设 置的 一个 上下 文中 运行 ,环 境是 应用 程序 运行 不可 缺少 的 条件。你在Unix 系 统中 执行 env 命令,同 样也 能得 到与 上面 类似 的键 值对 的环 境变 量信 息 。 所以,org.apache.hadoop.util.Shell 作为Shell 命 令的 抽象 ,一 定要 获取 到当 前所 在操 作系 统(Unix 系统)的 环境 变量 ,在 这样 一个 上下 文中 ,才 能如 同从 Unix 系 统中 输入 执行 Shell 命 令进 行执 行一 样。 在Java 中,实 现了 一个 java.lang.ProcessBuilder 类,该 类能 够创 建一 个操 作系 统的 进程 , 通 过为 该进 程设 置运 行环 境变 量, 从而 启动 进行 执行 。默 认情 况下 ProcessBuilder 类 已经 实 现了 设置 当前 操作 系统 环境 的功 能, 可以 通过 environment()方 法查 看它 的实 例所 具有 的 环 境信 息, 这等 价于 使用 System.getenv()获 取到 的环 境变 量, 都是 以键 值对 的形 式存 在 于 ProcessBuilder 类 实例 的上 下文 中。 下 面, 分析 Shell 类 实现 执行 命令 的过 程, 实现 方法 为 runCommand, 如下 所示 : view plain 1.privateprivateprivateprivatevoidvoidvoidvoidrunCommand()throwsthrowsthrowsthrowsIOException{ 2. 3.ProcessBuilderbuilder=newnewnewnewProcessBuilder(getExecString());//方法 getExecString()在该类中式抽象的,需要在实现类中实现,获取到一个命令名称及其参数 ,从 而基于此 构造一个 ProcessBuilder进程实例 4.booleanbooleanbooleanbooleancompleted=falsefalsefalsefalse;//标识执行命令完成情况 5. 6.ifififif(environment!=nullnullnullnull){ 7.builder.environment().putAll(thisthisthisthis.environment);//如果有必要 ,设置命令行执 行环境 8.} 9.ifififif(dir!=nullnullnullnull){ 10.builder.directory(thisthisthisthis.dir);//如果必要,设置命令行执行所在工作目录 11.} 12. 13.process=builder.start();//启动 ProcessBuilderbuilder进程 ,返回一个用来管 理命令行执行情况的子进程 process 14.finalfinalfinalfinalBufferedReadererrReader=newnewnewnewBufferedReader(newnewnewnewInputStreamReader( process.getErrorStream()));//当builder进程启动后,检查提交的命令行是否合法 ,如 果不合法或者执行出错,将出错信息写入到缓冲流中,可以从其中解析读取出来 15.BufferedReaderinReader=newnewnewnewBufferedReader(newnewnewnewInputStreamReader(process. getInputStream()));//执行命令返回执行结果,通过 process管理子线程来获取执行流中 的执行结果信息 16.finalfinalfinalfinalStringBuffererrMsg=newnewnewnewStringBuffer();//存放执行命令出错信息 的 String缓冲区 17. 18.//定义解析线程,解析命令行执行出错信息所在的流,解析完成后释放流缓冲区 19.ThreaderrThread=newnewnewnewThread(){ 20.@Override 21.publicpublicpublicpublicvoidvoidvoidvoidrun(){ 22.trytrytrytry{ 23.Stringline=errReader.readLine(); 24.whilewhilewhilewhile((line!=nullnullnullnull)&&!isInterrupted()){ 25.errMsg.append(line); 26.errMsg.append(System.getProperty("line.separator")); 27.line=errReader.readLine(); 28.} 29.}catchcatchcatchcatch(IOExceptionioe){ 30.LOG.warn("Errorreadingtheerrorstream",ioe); 31.} 32.} 33.}; 34. 35.trytrytrytry{ 36.errThread.start();//启动线程,处理出错信息 37.}catchcatchcatchcatch(IllegalStateExceptionise){} 38. 39.trytrytrytry{ 40.parseExecResult(inReader);//调用:解析执行命令返回的结果信息 41.Stringline=inReader.readLine(); 42.whilewhilewhilewhile(line!=nullnullnullnull){ 43.line=inReader.readLine(); 44.} 45.exitCode=process.waitFor();//等待进程 process处理完毕,置 exitCode状态 码 46.trytrytrytry{ 47.errThread.join();//等待出错信息处理线程执行完成 48.}catchcatchcatchcatch(InterruptedExceptionie){ 49.LOG.warn("Interruptedwhilereadingtheerrorstream",ie); 50.} 51.completed=truetruetruetrue;//置命令行执行完成状态 52.ifififif(exitCode!=0){ 53.throwthrowthrowthrownewnewnewnewExitCodeException(exitCode,errMsg.toString()); 54.} 55.}catchcatchcatchcatch(InterruptedExceptionie){ 56.throwthrowthrowthrownewnewnewnewIOException(ie.toString()); 57.}finallyfinallyfinallyfinally{ 58.trytrytrytry{ 59.inReader.close();//最后,需要关闭流对象 60.}catchcatchcatchcatch(IOExceptionioe){ 61.LOG.warn("Errorwhileclosingtheinputstream",ioe); 62.} 63.ifififif(!completed){ 64.errThread.interrupt();//可能处理错误信息的线程发生异常,未能 置 completed=true,最后终止要该线程 65.} 66.trytrytrytry{ 67.errReader.close();//关闭流对象 68.}catchcatchcatchcatch(IOExceptionioe){ 69.LOG.warn("Errorwhileclosingtheerrorstream",ioe); 70.} 71.process.destroy();//终止子进程 process 72.lastTime=System.currentTimeMillis();//设置当前时间为该命令行执行的最后 时间,为了判断一个命令是否需要被重新执行 73.} 74.} 上 面已 经做 了详 细的 注释 ,基 本上 阐明 了一 个命 令行 的执 行过 程。 在 类中 ,还 提供 了一 个 static 方法execCommand, 为执 行命 令提 供入 口: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticStringexecCommand(String...cmd)throwsthrowsthrowsthrowsIOException{ 2.returnreturnreturnreturnexecCommand(nullnullnullnull,cmd); 3.} 执 行该 方法 ,调 用了 另一 个重 载的 execCommand 方 法, 返回 命令 执行 结果 的信 息。 注意,在Shell 抽 象类 中并 没有 实现 该怎 样获 取一 个命 令名 称及 其参 数的 方法 ,需 要在 实现 类 中给 出 ,因此,在Shell 类 内部 定义 了一 个静 态内 部类 ShellCommandExecutor,该 类实 现 了获 取命 令名 称及 其参 数的 方法 。在 上面 方法 execCommand 中 ,调 用了 一个 重载 的 execCommand 方 法, 该方 法中 通过 实例 化一 个 ShellCommandExecutor 类 ,来 提供 获取 命 令名 称及 其参 数, 进而 构造 一个 ProcessBuilder 实 例, 创建 一个 操作 系统 线程 来执 行命 令 行。 ???? extendsextendsextendsextends ShellShellShellShell 下 面看 实现 Shell 抽 象类 的一 些子 类的 实现 。 • ShellCommandExecutor 类 ShellComandExecutor 类 的实 现如 下所 示: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticclassclassclassclassShellCommandExecutorextendsextendsextendsextendsShell{ 2. 3.privateprivateprivateprivateString[]command;//命令名称及其参数 4.privateprivateprivateprivateStringBufferoutput;//存放执行命令行返回结果的 String缓冲区 5. 6.publicpublicpublicpublicShellCommandExecutor(String[]execString){ 7.command=execString.clone(); 8.} 9. 10.publicpublicpublicpublicShellCommandExecutor(String[]execString,Filedir){ 11.thisthisthisthis(execString); 12.thisthisthisthis.setWorkingDirectory(dir); 13.} 14. 15.publicpublicpublicpublicShellCommandExecutor(String[]execString,Filedir,Mapenv){ 16.thisthisthisthis(execString,dir); 17.thisthisthisthis.setEnvironment(env); 18.} 19. 20./**继承自 Shell基类,执行命令行 */ 21.publicpublicpublicpublicvoidvoidvoidvoidexecute()throwsthrowsthrowsthrowsIOException{ 22.thisthisthisthis.run(); 23.} 24. 25.protectedprotectedprotectedprotectedString[]getExecString(){ 26.returnreturnreturnreturncommand;//输入的就是命令名称 +参数的格式,直接得到 27.} 28. 29./** 30.*解析命令行执行后的输出结果流,存放到 String缓冲区中 31.*/ 32.protectedprotectedprotectedprotectedvoidvoidvoidvoidparseExecResult(BufferedReaderlines)throwsthrowsthrowsthrowsIOException{ 33.output=newnewnewnewStringBuffer(); 34.charcharcharchar[]buf=newnewnewnewcharcharcharchar[512]; 35.intintintintnRead; 36.whilewhilewhilewhile((nRead=lines.read(buf,0,buf.length))>0){ 37.output.append(buf,0,nRead); 38.} 39.} •DF类 org.apache.hadoop.fs.DF 类 实现 了 Unix 系 统中 Shell 命令df,用 来获 取磁 盘使 用情 况的 统 计 数据 。该 Shell 实 现类 中定 义域 df 命 令操 作相 关的 内容 ,可 以从 属性 来看 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinallonglonglonglongDF_INTERVAL_DEFAULT=3*1000;//设置 df命令刷新间 隔为 3s 2.privateprivateprivateprivateStringdirPath;//执行 df命令所在工作目录 3.privateprivateprivateprivateStringfilesystem;//磁盘设备名 4.privateprivateprivateprivatelonglonglonglongcapacity;//磁盘总容量 5.privateprivateprivateprivatelonglonglonglongused;//磁盘使用量 6.privateprivateprivateprivatelonglonglonglongavailable;//磁盘可用量 7.privateprivateprivateprivateintintintintpercentUsed;//磁盘使用率 8.privateprivateprivateprivateStringmount;//磁盘挂载位置 只 需要 实现 Shell 类 定义 的 getExecString 与parseExecResult 方 法即 可。 比较 简单 , getExecString 方 法实 现如 下: view plain 1.protectedprotectedprotectedprotectedString[]getExecString(){ 2.returnreturnreturnreturnnewnewnewnewString[]{"bash","-c","exec'df''-k''"+dirPath+"'2>/dev /null"}; 3.} 该 方法 返回 的字 符串 数组 ,用 来构 造一 个 ProcessBuilder 进 程实 例。 parseExecResult 方 法实 现如 下所 示: view plain 1.protectedprotectedprotectedprotectedvoidvoidvoidvoidparseExecResult(BufferedReaderlines)throwsthrowsthrowsthrowsIOException{ 2.lines.readLine();//去掉流中的首行 3.Stringline=lines.readLine(); 4.ifififif(line==nullnullnullnull){ 5.throwthrowthrowthrownewnewnewnewIOException("Expectingalinenottheendofstream"); 6.} 7.StringTokenizertokens=newnewnewnewStringTokenizer(line,"/t/n/r/f%"); 8. 9.thisthisthisthis.filesystem=tokens.nextToken(); 10.ifififif(!tokens.hasMoreTokens()){ 11.line=lines.readLine(); 12.ifififif(line==nullnullnullnull){ 13.throwthrowthrowthrownewnewnewnewIOException("Expectingalinenottheendofstream"); 14.} 15.tokens=newnewnewnewStringTokenizer(line,"/t/n/r/f%"); 16.} 17./** 18.*下面处理并设置执行 df-k命令的结果信息 19.*/ 20.thisthisthisthis.capacity=Long.parseLong(tokens.nextToken())*1024; 21.thisthisthisthis.used=Long.parseLong(tokens.nextToken())*1024; 22.thisthisthisthis.available=Long.parseLong(tokens.nextToken())*1024; 23.thisthisthisthis.percentUsed=Integer.parseInt(tokens.nextToken()); 24.thisthisthisthis.mount=tokens.nextToken(); 25.} •DU类 DU类 实现 了 Unix 的du 命 令, 显示 目录 或者 文件 大小 的信 息, 具体 实现 可以 参 考 org.apache.hadoop.fs.DU 类 ,这 里跳 过。 • CygPathCommand 类 CygPathCommand 类是org.apache.hadoop.fs.FileUtil 类 的一 个内 部静 态类 ,实 现 了 Windows 系 统上 模拟 Unix 系 统的 Cygwin 系 统的 cygpath 命 令, 这里 跳过 。 前 面分 析了 与操 作系 统有 关的 Shell 命 令, 它们 用于 与操 作系 统进 行命 令行 方式 的交 互 。在 Hadoop 中 ,自 定义 了 FileSystem 文 件系 统, 这是 基于 Unix 操 作系 统之 上的 文件 系统 ,为 了 方便 对 FileSystem 的 管理 ,通 过 org.apache.hadoop.fs.FsShell 类 定义 了对 Hadoop FileSystem 文 件系 统进 行命 令行 方式 管理 的命 令实 现。 先 给出 对 Hadoop 文 件系 统进 行管 理的 命令 实现 类的 继承 层次 关系 : view plain 1.◦org.apache.hadoop.conf.Configured(implementsimplementsimplementsimplementsorg.apache.hadoop.conf.Configu rable) 2.◦org.apache.hadoop.fs.FsShell(implementsimplementsimplementsimplementsorg.apache.hadoop.util.Tool) 3.◦org.apache.hadoop.hdfs.tools.DFSAdmin 由于DFSAdmin 类 是对 HDFS 分 布式 文件 系统 提供 基于 命令 行的 管理 功能 ,这 里先 不 对 DFSAdmin 进 行分 析, 在后 面分 析 HDFS 实 现的 时候 ,进 行详 细分 析理 解。 Configured 就 不用 多说 了, 是 Hadoop 配 置类 的高 层抽 象。 Tool 接 口支 持命 令行 方式 的处 理, 如果 需要 通过 命令 行方 式来 执行 一定 的任 务, 都可 以实 现 该接 口, 通过 该接 口定 义的 run 方 法来 运行 命令 行。 由于 它继 承自 Configurable 接 口, 使 得实 现 Tool 的 接口 可以 对特 定的 待执 行的 任务 进行 详细 配置 ,满 足执 行一 个命 令能 够完 成 任务 的要 求。 下面 是接 口的 定义 : view plain 1.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceToolextendsextendsextendsextendsConfigurable{ 2.intintintintrun(String[]args)throwsthrowsthrowsthrowsException; 3.} 在Hadoop 中,Tool 接 口主 要是 为进 行 MapReduce 并 行计 算而 定义 的 ,这里FsShell 类实 现 了该 接口 ,其 实也 是使 得命 令行 执行 与任 务关 联起 来 ,通 过执 行命 令行 ,而 执行 设置 的待 完 成的 任务 。 下 面来 看 FsShell 类 的具 体实 现。 既然,FsShell 是 与命 令行 有关 的 ,那 么我 们就 从其 中对 指定 的命 令实 现的 角度 来看 ,分别 对 每个 命令 的实 现进 行阅 读分 析。 在分 析每 个命 令实 现过 程之 前, 先看 一下 该类 中 printUsage 方 法的 执行 ,该 方法 能够 打印 出全 部命 令用 法的 信息 ,如 下所 示: view plain 1.Usage:javaFsShell 2.[-ls] 3.[-lsr] 4.[-du] 5.[-dus] 6.[-count[-q]] 7.[-mv] 8.[-cp] 9.[-rm] 10.[-rmr] 11.[-expunge] 12.[-put...] 13.[-copyFromLocal...] 14.[-moveFromLocal...] 15.[-get[-ignoreCrc][-crc]] 16.[-getmerge[addnl]] 17.[-cat] 18.[-text] 19.[-copyToLocal[-ignoreCrc][-crc]] 20.[-moveToLocal[-crc]] 21.[-mkdir] 22.[-setrep[-R][-w]] 23.[-touchz] 24.[-test-[ezd]] 25.[-stat[format]] 26.[-tail[-f]] 27.[-chmod[-R]PATH...] 28.[-chown[-R][OWNER][:[GROUP]]PATH...] 29.[-chgrp[-R]GROUPPATH...] 30.[-help[cmd]] 31. 32.Genericoptionssupportedare 33.-confspecifyanapplicationconfigurationfile 34.-Dusevalueforforforforgivenproperty 35.-fsspecifyanamenode 36.-jtspecifyajobtracker 37.-filesspecifycommaseparatedfilestob ecopiedtothemapreducecluster 38.-libjarsspecifycommaseparatedjarfiles toincludeintheclasspath. 39.-archivesspecifycommaseparatedarch ivestobeunarchivedonthecomputemachines. 40. 41.Thegeneralcommandlinesyntaxis 42.bin/hadoopcommand[genericOptions][commandOptions] 非 常清 晰明 了 ,FsShell 所 支持 的命 令行 ,及 其该 命令 的可 以设 置的 参数 ,都 在上 述列 表中 显 示出 来。 另 外, 对于 每个 命令 的帮 助信 息, 都可 以通 过 printHelp 方 法得 到, 例如 ,如 果想 要得 到命 令“ls”的 帮助 信息 ,调 用 printHelp("ls");即 可。 如果 想要 得到 全部 命令 的帮 助信 息, 只要 给 printHelp 随 便传 入一 个非 命令 字符 串 ,如printHelp("hashyes3532333");,将 打印 出全 部命 令 帮助 信息 ,下 面是 一个 帮助 信息 的片 段: view plain 1.hadoopfsisthecommandtoexecutefscommands.Thefullsyntaxis: 2. 3.hadoopfs[-fs>>>][-conf>>>] 4.[-D>>>][-ls][-lsr][-du] 5.[-dus][-mv][-cp][-rm] 6.[-rmr][-put...][-copyFromLocal... ] 7.[-moveFromLocal...][-get[-ignoreCrc][-crc]ocaldst>ocaldst>ocaldst> 8.[-getmerge[addnl]][-cat] 9.[-copyToLocal[-ignoreCrc][-crc]][-moveToLocal ] 10.[-mkdir][-report][-setrep[-R][-w]>>>] 11.[-touchz][-test-[ezd]][-stat[format]] 12.[-tail[-f]][-text] 13.[-chmod[-R]>>>PATH...] 14.[-chown[-R][OWNER][:[GROUP]]PATH...] 15.[-chgrp[-R]GROUPPATH...] 16.[-count[-q]] 17.[-help[cmd]] 18. 19.-fs[local|>>>]:Specifythefilesystemtouse. 20.Ifnotspecified,thecurrentconfigurationisused, 21.takenfromthefollowing,inincreasingprecedence: 22.core-default.xmlinsidethehadoopjarfile 23.core-site.xmlin$HADOOP_CONF_DIR 24.'local'meansusethelocalfilesystemasyourDFS. 25.>>>specifiesaparticularfilesystemto 26.contact.Thisargumentisoptionalbutifusedmustappear 27.appearfirstonthecommandline.Exactlyoneadditional 28.argumentmustbespecified. 下 面介 绍每 个命 令的 实现 : • ls 与lsr 命令 执行ls 命 令, 能够 列出 匹配 指定 Path 下 的全 部文 件, 并且 不递 归列 出子 目录 中文 件; lsr 能 够列 出指 定 Path 下 的所 有文 件, 并且 如果 存在 子目 录, 也会 递归 列出 子目 录中 的文 件 。 实 现这 两个 命令 的方 法均 为 ls 方 法, 如下 所示 : view plain 1./** 2.*列出满足模式 srcf的全部文件 3.*@paramsrcf文件模式 4.*@paramrecursive是否递归列出 5.*/ 6.privateprivateprivateprivateintintintintls(Stringsrcf,booleanbooleanbooleanbooleanrecursive)throwsthrowsthrowsthrowsIOException{ 7.PathsrcPath=newnewnewnewPath(srcf); 8.FileSystemsrcFs=srcPath.getFileSystem(thisthisthisthis.getConf());//通过构 造Path类 实例,获取它所属的 FileSystem文件系统 9.FileStatus[]srcs=srcFs.globStatus(srcPath);//获取到文件系统 srcFs中匹 配 srcPath模式的全部按照文件名称排好序的文件(不包括校验和文件),每个文件对应一 个 FileStatus 10.ifififif(srcs==nullnullnullnull||srcs.length==0){ 11.throwthrowthrowthrownewnewnewnewFileNotFoundException("Cannotaccess"+srcf+":Nosuchfi leordirectory."); 12.} 13.booleanbooleanbooleanbooleanprintHeader=(srcs.length==1)?truetruetruetrue:falsefalsefalsefalse;//两种情况 :如果获取 到一个文件 FileStatus,表示只有一个目录或者文件,需要打印出列表头部信息;否则返回多 个FileStatus,需要循环并递归遍历,不打印出列表头部信息 14.intintintintnumOfErrors=0; 15.forforforfor(intintintinti=0;imaxReplication)maxReplication=replication;//有可 能一个文件的副本数超过指定的最大 replication因子值 24.ifififif(len>maxLen)maxLen=len;//超过文件最大长度的情况 25.ifififif(owner>maxOwner)maxOwner=owner;//超过最大属主数 26.ifififif(group>maxGroup)maxGroup=group;//超过最大属组数 27.} 28. 29.forforforfor(intintintinti=0;i0) 36.System.out.printf("%-"+maxOwner+"s",stat.getOwner());//输出 stat对应文件的属主数到输出流中 37.ifififif(maxGroup>0) 38.System.out.printf("%-"+maxGroup+"s",stat.getGroup());//输出 stat对应文件的属组数到输出流中 39.System.out.printf("%"+maxLen+"d",stat.getLen());//输出stat对应文 件的最大长度信息到输出流中 40.System.out.print(mdate+"");//输出格式化的文件修改时间 41.System.out.println(cur.toUri().getPath());//输出 stat对应文件的路径信 息 42.ifififif(recursive&&stat.isDir()){//如果 stat对应的是目录,而且要求递归列 出 43.numOfErrors+=ls(stat,srcFs,recursive,printHeader);//递归调 用 ls 44.} 45.} 46.returnreturnreturnreturnnumOfErrors; 47.} 48.} lsr 命 令是 递归 列出 满足 给定 模式 的全 部文 件, 也是 基于 上述 方法 实现 的。 通 过上 面的 ls 的 实现 可知 ,列出FileSystem 文 件系 统中 的数 据 ,是 通过 获取 到该 文件 系统 中 保存 的文 件的 FileStatus 实例,因为FileStatus 描 述了 位于 该文 件系 统中 对应 文件 的详 细 信 息, 然后 通过 它来 打印 出文 件类 表( 包含 必要 的文 件属 性信 息) 。 • du 与dus 命令 du 命 令列 出满 足给 定模 式的 全部 文件 对应 的长 度信 息, dus 执 行后 列出 了满 足给 定模 式的 每 个文 件或 目录 的磁 盘使 用情 况摘 要信 息, 比 du 命 令执 行得 到的 结果 信息 要详 细。 du 命 令实 现是 通过 du 方 法, 如下 : view plain 1.voidvoidvoidvoiddu(Stringsrc)throwsthrowsthrowsthrowsIOException{ 2.PathsrcPath=newnewnewnewPath(src); 3.FileSystemsrcFs=srcPath.getFileSystem(getConf());//获取到 Path对应 的 FileSystem文件系统 4.Path[]pathItems=FileUtil.stat2Paths(srcFs.globStatus(srcPath),srcPath); //调用 :将从srcFs文件系统中获取到经 过srcPath过滤 的FileStatus[]转换 为Path数组 5.FileStatusitems[]=srcFs.listStatus(pathItems);//根据得到的满足过滤条件 的Path得到对应的 FileStatus 6.ifififif((items==nullnullnullnull)||((items.length==0)&&(!srcFs.exists(srcPath)))){ 7.throwthrowthrowthrownewnewnewnewFileNotFoundException("Cannotaccess"+src+":Nosuchfile ordirectory."); 8.}elseelseelseelse{ 9.System.out.println("Found"+items.length+"items"); 10.intintintintmaxLength=10; 11. 12.longlonglonglonglength[]=newnewnewnewlonglonglonglong[items.length];//length数组用来保存每个文件对应 的长度信息 13.forforforfor(intintintinti=0;imaxLength)maxLength=len; 19.} 20.forforforfor(intintintinti=0;i3){ 8.Pathdst=newnewnewnewPath(dest);//创建目录 9.FileSystemdstFs=dst.getFileSystem(getConf());//得到该目录所在的文件系 统dstFs 10.ifififif(!dstFs.isDirectory(dst)){//如果文件系统 dstFs中存在 dst,而且它不是一 个目录,出错 11.throwthrowthrowthrownewnewnewnewIOException("Whenmovingmultiplefiles,"+"destination" +dest+"shouldbeadirectory."); 12.} 13.} 14.//循环遍历多个输入源文件,也就是在命令名称与最后一个参数之间的参数字符串 15.forforforfor(;i1&&!srcFs.isDirectory(dst)){//输入源文件大于 1个,如果 目的文件不是目录,出错 14.throwthrowthrowthrownewnewnewnewIOException("Whenmovingmultiplefiles,"+"destinationshou ldbeadirectory."); 15.} 16.forforforfor(intintintinti=0;iparameters=c.parse(cmd,pos);//解析 cmd的参数 8.src=parameters.get(0);//文件参数 9.}catchcatchcatchcatch(IllegalArgumentExceptioniae){ 10.System.err.println("Usage:javaFsShell"+TAIL_USAGE); 11.throwthrowthrowthrowiae; 12.} 13.booleanbooleanbooleanbooleanfoption=c.getOpt("f")?truetruetruetrue:falsefalsefalsefalse;//判断是否设置了 -f选项 14.path=newnewnewnewPath(src); 15.FileSystemsrcFs=path.getFileSystem(getConf());//获取到 Path对应的文件系 统 16.ifififif(srcFs.isDirectory(path)){//若path是目录,出错 17.throwthrowthrowthrownewnewnewnewIOException("Sourcemustbeafile."); 18.} 19. 20.longlonglonglongfileSize=srcFs.getFileStatus(path).getLen();//计算 path文件的长度 21.longlonglonglongoffset=(fileSize>1024)?fileSize-1024:0;//计算开始的偏移位 置 22. 23.whilewhilewhilewhile(truetruetruetrue){ 24.FSDataInputStreamin=srcFs.open(path);//打开文件 25.in.seek(offset);//定位到 offset位置 26.IOUtils.copyBytes(in,System.out,1024,falsefalsefalsefalse);//将输入流 in拷贝 到 Syste.out标准输出流中 27.offset=in.getPos();//重新设置开始偏移位置 28.in.close();//关闭输入流 in 29.ifififif(!foption){//如果没有设置 -f选项,直接退出 30.breakbreakbreakbreak; 31.} 32.fileSize=srcFs.getFileStatus(path).getLen();//设置了 -f选项,显示向文 件 path追加写入数据的起始位置 33.offset=(fileSize>offset)?offset:fileSize; 34.trytrytrytry{ 35.Thread.sleep(5000); 36.}catchcatchcatchcatch(InterruptedExceptione){ 37.breakbreakbreakbreak; 38.} 39.} 40.} • setrep 命令 该 命令 是设 置满 足给 定模 式的 文件 的副 本因 子( replication factor) 。不 仅可 以通 过该 类实 现的setReplication 方 法对 单个 文件 设置 副本 因子 ,也 可以 递归 设置 某个 目录 的所 有文 件的 副 本因 子 。实 现设 置副 本因 子的 方法 在该 类中 有多 个 ,包 括重 载的 方法 ,先 队下 面的 方法 来 分 析: view plain 1.privateprivateprivateprivatevoidvoidvoidvoidsetReplication(String[]cmd,intintintintpos)throwsthrowsthrowsthrowsIOException{ 2.CommandFormatc=newnewnewnewCommandFormat("setrep",2,2,"R","w");//解析命令 行 3.Stringdst=nullnullnullnull; 4.shortshortshortshortrep=0;//初始化副本因子 5.trytrytrytry{ 6.Listparameters=c.parse(cmd,pos);//从位置 pos出开始,解析出命 令行中的全部参数列表 7.rep=Short.parseShort(parameters.get(0));//第一个参数就是副本因子的值 8.dst=parameters.get(1);//第二个参数是带设置副本因子的文件 9.}catchcatchcatchcatch(NumberFormatExceptionnfe){ 10.System.err.println("Illegalreplication,apositiveintegerexpected"); 11.throwthrowthrowthrownfe; 12.} 13.catchcatchcatchcatch(IllegalArgumentExceptioniae){ 14.System.err.println("Usage:javaFsShell"+SETREP_SHORT_USAGE); 15.throwthrowthrowthrowiae; 16.} 17. 18.ifififif(rep<1){//不能将副本因子设置为负数 19.System.err.println("Cannotsetreplicationto:"+rep); 20.throwthrowthrowthrownewnewnewnewIllegalArgumentException("replicationmustbe>=1"); 21.} 22. 23.ListwaitList=c.getOpt("w")?newnewnewnewArrayList():nullnullnullnull;//如果设 置了 -w选项,会将待设置副本因子完成的文件 Path暂时缓存到列表 ArrayList中 24.setReplication(rep,dst,c.getOpt("R"),waitList);//调用重载 的 setReplication方法,设置副本因子 25. 26.ifififif(waitList!=nullnullnullnull){ 27.waitForReplication(waitList,rep);//更新 waitList中文件的块的副本因子信 息 28.} 29.} 看 一下 重载 的 setReplication 方 法设 置副 本因 子的 实现 过程 : view plain 1.voidvoidvoidvoidsetReplication(shortshortshortshortnewRep,Stringsrcf,booleanbooleanbooleanbooleanrecursive,List waitingList)throwsthrowsthrowsthrowsIOException{ 2.PathsrcPath=newnewnewnewPath(srcf); 3.FileSystemsrcFs=srcPath.getFileSystem(getConf());//获取到 srcf所在的文 件系统 srcFs 4.Path[]srcs=FileUtil.stat2Paths(srcFs.globStatus(srcPath),srcPath);// 得到满足 srcf模式的全部 Path文件 5.forforforfor(intintintinti=0;iwaitingList)throwsthrowsthrowsthrowsIOException{ 5.ifififif(!srcFs.getFileStatus(src).isDir()){//递归出口:如果 src是一个普通文件 (而非目录) 6.setFileReplication(src,srcFs,newRep,waitingList);//调用 setFileReplication方法设置文件 src的副本因子 7.returnreturnreturnreturn; 8.} 9.FileStatusitems[]=srcFs.listStatus(src);//如果 src是目录,获取该目录中所 有的文件 FileStatus数组 10.ifififif(items==nullnullnullnull){ 11.throwthrowthrowthrownewnewnewnewIOException("Couldnotgetlistingfor"+src); 12.}elseelseelseelse{ 13.forforforfor(intintintinti=0;iwaitList)throwsthrowsthrowsthrowsIOException{ 2.ifififif(srcFs.setReplication(file,newRep)){//调用文件系统 srcFs的设置副本因子 的方法,设置副本因子 3.ifififif(waitList!=nullnullnullnull){ 4.waitList.add(file);//将设置副本因子完成的文件 file加入到 waitList列表 5.} 6.System.out.println("Replication"+newRep+"set:"+file); 7.}elseelseelseelse{ 8.System.err.println("Couldnotsetreplicationfor:"+file); 9.} 10.} 我 们再 回到 最前 面重 载的 setReplication 方法,已 经完 成了 设置 副本 因子 的任 务 ,然 后需 要 执行waitForReplication(waitList, rep);语句。此时,全 部需 要设 置副 本因 子的 文件 都已 经缓 存到waitList 列 表中 ,下 面看 调用 该方 法对 waitList 列 表中 的文 件执 行的 操作 : view plain 1./** 2.*等待在 waitList列表中的文件 ,所对应的每一个块的副本因子,都设置为指定的值 rep 3.*/ 4.voidvoidvoidvoidwaitForReplication(ListwaitList,intintintintrep)throwsthrowsthrowsthrowsIOException{ 5.forforforfor(Pathf:waitList){ 6.System.out.print("Waitingfor"+f+"..."); 7.System.out.flush(); 8. 9.booleanbooleanbooleanbooleanprintWarning=falsefalsefalsefalse;//如果文件 f对应的块超过 rep,是否给出警告信息 (需要减少块副本数量,直到等于 rep) 10.FileStatusstatus=fs.getFileStatus(f);//获取当前文件系统 fs上文件 f对应 的FileStatus信息 11.longlonglonglonglen=status.getLen();//文件 f的长度 12. 13.forforforfor(booleanbooleanbooleanboolean done=falsefalsefalsefalse;!done;){ 14.BlockLocation[]locations=fs.getFileBlockLocations(status,0,len); //在当前 fs上获取文件 f对应的全部块的位置信息对象(一个数组) 15.intintintint i=0; 16.forforforfor(;irep){//如果 文件 f的某个块的位置信息 locations[i]中,主机列表长度(其实就是副本因子的值)大于待 设置的副本因子 rep 18.System.out.println("/nWARNING:thewaitingtimemaybelongfor" +"DECREASINGthenumberofreplication.");//打印警告信息,需要适当删除该块副 本,以满足副本因子要求 19.printWarning=truetruetruetrue;//对于同一个文件 f,只打印一次警告信息 (如果满足 f 中的条件时) 20.} 21.}//for 22.done=i==locations.length;//对文件 f对应的块都检查过以后 ,设置检查完成 标志 done 23.ifififif (!done){//没有经过上述检查(文件 f对应的块副本小于 0的情况下) 24.System.out.print("."); 25.System.out.flush(); 26.trytrytrytry {Thread.sleep(10000);}catchcatchcatchcatch (InterruptedExceptione){} 27.} 28.} 29. 30.System.out.println("done"); 31.} 32.} 这里,有 必要 了解 一下 org.apache.hadoop.fs.BlockLocation 的 含义 ,可 以看 BlockLocation 类 定义 的属 性, 如下 所示 : view plain 1.privateprivateprivateprivate String[]hosts;//hostnamesofdatanodes 2.privateprivateprivateprivate String[]names;//hostname:portNumberofdatanodes 3.privateprivateprivateprivate String[]topologyPaths;//fullpathnameinnetworktopology 4.privateprivateprivateprivate longlonglonglong offset;//offsetoftheoftheblockinthefile 5.privateprivateprivateprivate longlonglonglong length; 可见,一个BlockLocation 包 含了 一个 文件 的一 个块 的详 细信 息 ,包 括这 个块 对应 的全 部副 本 (包 含它 本身 ) , 比如 上述 定义 的有 :所 在主 机、 所在 主机 及其 端口 号、 在网 络拓 扑结 构 中的 全路 径名 称 、块 在文 件中 的偏 移位 置 、块 长度 。显然,这 些块 副本 长度 和在 文件 中的 偏 移位 置都 是相 同的 ,可 以共 享( 分别 对应 length 和offset 属 性) ,其 他三 个属 性的 信息 就 不相 同了 (可 能存 在某 两个 相同 的情 况) 。 Hadoop 文 件系 统中 ,一 个文 件对 应多 个块 (Block),每 个块 默认 大小 设置 为 64M。那么, 对 于由 多个 块组 成的 文件 来说 ,如 果想 要获 取到 该文 件的 全部 块及 其块 副本 的信 息 ,就 需要 通 过文 件系 统中 文件 的统 计信 息 FileStatus 来 获取 到一 个 BlockLocation[],该 数组 中对 应的 全 部快 就能 够构 成完 整的 该文 件。 下 面通 过形 式化 语言 来表 达一 下上 面的 含义 : 假 设一 个文 件 F由n个 块组 成, 则分 别为 : B(1),B(2),……,B(n) 假 设默 认块 的大 小为 BS, 那么 B(1)~B(n-1)一 定是 大小 相同 的块 ,大 小都 等于 BS,而 B(i)<=BS, 这是 显而 易见 的。 文件F的 每个 块 B(i)都 被存 储在 指定 主机 的文 件系 统中 ,假 设存 储到 了主 机 H(i)上。为 了快 速 计算 ,需 要快 速定 位到 文件 F的Bi 块上,也 就是 需要 进行 流式 读取 获取 到 ,那么F的块 B(i)需 要有 一个 记录 其详 细信 息的 结构 ,也 就是 Hadoop 定 义的 BlockLocation。 假设 Bi 对 应 的描 述信 息对 象为 BL(i), 那么 BL(i)就 包含 了与 块 B(i)相 关的 全部 块副 本的 信息 ,当 然每 个 块副 本同 样包 含与 BL(i), 相同 的描 述信 息的 属性 ,只 是属 性值 不同 而已 。 假 设文 件 F对 应的 块 B(i)一 共具 有 m个 副本 : BR1(i),BR2(i),……,BRm(i) 这 些块 副本 分别 存储 在对 应如 下的 主机 上: H1(i),H2(i),……,Hm(i) 这 些块 副本 分别 对应 指定 主机 的端 口号 分别 如下 : H1(i):P1(i),H2(i):P2(i),……,Hm(i):Pm(i) 这 些块 副本 对应 的拓 扑网 络中 的完 整路 径分 别为 : U1(i),U2(i),……,Um(i) 假 设块 Bi 的 长度 为 LENGTH(i), 偏移 位置 为 OFFSET(i), 那么 ,通 过该 文件 的 FileStatus 获 取的 BlockLocation[i]的 内容 ,形 式化 的可 以描 述为 : view plain 1.newnewnewnewBlockLocation[]{ 2. newnewnewnewString[m]{H1(i),H2(i),……,Hm(i)}, 3. newnewnewnewString[m]{H1(i):P1(i),H2(i):P2(i),……,Hm(i):Pm( i)}, 4. newnewnewnewString[m]{U1(i),U2(i),……,Um(i)}, 5. LENGTH(i), 6. OFFSET(i) 7.} 关 于获 取到 一个 文件 (对 应的 FileStatus)的BlockLocation[], 可以 看到 FileSystem 类中 getFileBlockLocations 方 法的 实现 ,如 下所 示: view plain 1.publicpublicpublicpublicBlockLocation[]getFileBlockLocations(FileStatusfile,longlonglonglongstart,lolololo ngngngnglen)throwsIOException{//根据文件 F对应的 FileStatus,及其位置 start和长度 信息 len就能获取到 2.ifififif(file==nullnullnullnull){ 3.returnreturnreturnreturnnullnullnullnull; 4.} 5. 6.ifififif((start<0)||(len<0)){ 7.throwthrowthrowthrownewnewnewnewIllegalArgumentException("Invalidstartorlenparameter"); 8.} 9. 10.ifififif(file.getLen()3){ 8.Pathdst=newnewnewnewPath(dest); 9.ifififif(!fs.isDirectory(dst)){//最后一个参数必须是目录 10.throwthrowthrowthrownewnewnewnewIOException("Whencopyingmultiplefiles,"+"destination" +dest+"shouldbeadirectory."); 11.} 12.} 13.//循环对每一个文件进行拷贝操作 14.forforforfor(;i1&&!dstFs.isDirectory(dstPath)){ 8.throwthrowthrowthrownewnewnewnewIOException("Whencopyingmultiplefiles,"+"destinationsho uldbeadirectory."); 9.} 10.forforforfor(intintintinti=0;i=0){//确实读取到了字节 7.out.write(buf,0,bytesRead);//将从 in中读取到的字节 ,通过 buf缓冲区写入 到输出流 out中 8.ifififif((ps!=nullnullnullnull)&&ps.checkError()){//如果 ps=(PrintStream)out,测试 内部标志,并自动刷新 9.throwthrowthrowthrownewnewnewnewIOException("Unabletowritetooutputstream."); 10.} 11.bytesRead=in.read(buf);//继续从 in读取字节 12.} 13.}finallyfinallyfinallyfinally{ 14.ifififif(close){ 15.out.close();//关闭 out 16.in.close();//关闭 in 17.} 18.} 19.} 上 面在 从 InputStream in 拷 贝到 OutputStream out 中 的过 程中 ,使 用了 更加 高效 的 PrintStream 流 类, 它能 够为 OutputStream 增 加方 便打 印各 种数 据值 的表 示形 式, 而且 , 它 不会 抛出 IO 异常,而 是将 流式 拷贝 过程 中发 生的 异常 ,设 置为 通过 调用 checkError 方法 来 检测 内部 的标 志 。另外,它 还可 以实 现自 动刷 新 ,在 向输 出流 中写 入字 节 (通 过字 节缓 冲 区 )之 后, 自动 刷新 。 cp命 令的 具体 实现 都在 上面 进行 分析 了, 应该 能够 理解 在 Hadoop 中 如何 在不 同文 件系 统 之 间执 行流 式拷 贝文 件的 过程 。 • copyFromLocal 命令 该 命令 实现 了从 本地 文件 系统 拷贝 文件 的操 作。 实现 方法 为, 如下 所示 : view plain 1./** 2.*从本地文件系统 (srcs在本地文件系统中 )拷贝 srcs到目的文件系统 (对应 Path为dstf) 3.*/ 4.voidvoidvoidvoidcopyFromLocal(Path[]srcs,Stringdstf)throwsthrowsthrowsthrowsIOException{ 5.PathdstPath=newnewnewnewPath(dstf); 6.FileSystemdstFs=dstPath.getFileSystem(getConf());//获取到目的文件系 统 dstFs 7.ifififif(srcs.length==1&&srcs[0].toString().equals("-"))//如果只指定了一个 参数 “-” 8.copyFromStdin(dstPath,dstFs);//调用:从标准输入流中进行流式拷贝操作 9.elseelseelseelse//否则 10.dstFs.copyFromLocalFile(falsefalsefalsefalse,falsefalsefalsefalse,srcs,dstPath);//调用目的文件系 统 dstFs的copyFromLocalFile方法执行拷贝操作 11.} 我 们关 注一 下 copyFromStdin 方 法拷 贝的 实现 ,如 下所 示: view plain 1.privateprivateprivateprivatevoidvoidvoidvoidcopyFromStdin(Pathdst,FileSystemdstFs)throwsthrowsthrowsthrowsIOException{ 2.ifififif(dstFs.isDirectory(dst)){//如果目的文件是目录,不支持源为标准输入流的情 况 3.throwthrowthrowthrownewnewnewnewIOException("Whensourceisstdin,destinationmustbeafile. "); 4.} 5.ifififif(dstFs.exists(dst)){//如果目的文件系统 dstFs中存在文件 dst,出错 6.throwthrowthrowthrownewnewnewnewIOException("Target"+dst.toString()+"alreadyexists."); 7.} 8.FSDataOutputStreamout=dstFs.create(dst);//满足拷贝要求,执行流式拷贝操 作 9.trytrytrytry{ 10.IOUtils.copyBytes(System.in,out,getConf(),falsefalsefalsefalse);//调用 IOUtils类的 copyBytes方法实现,前面已经分析过拷贝过程 11.} 12.finallyfinallyfinallyfinally{ 13.out.close();//拷贝完成,关闭输出流 out 14.} 15.} 再 看一 下, 如果 指定 的是 待拷 贝的 文件 源不 是标 准输 入流 的情 况, 文件 系统 FileSystem 是 如 何实 现拷 贝操 作的 。实 现的 方法 copyFromLocalFile 如 下所 示: view plain 1./** 2.*将本地的 srcs,拷贝到目的文件系统中的 dst 3.*delSrc指示了拷贝文件完成之后,是否删除源文件 srcs 4.*/ 5.publicpublicpublicpublicvoidvoidvoidvoidcopyFromLocalFile(booleanbooleanbooleanbooleandelSrc,booleanbooleanbooleanbooleanoverwrite,Path[]srcs, Pathdst) 6.throwsthrowsthrowsthrowsIOException{ 7.Configurationconf=getConf(); 8.FileUtil.copy(getLocal(conf),srcs,thisthisthisthis,dst,delSrc,overwrite,conf);/ /调用 FileUtil工具类实现拷贝操作 9.} 关于FileUtil 的copy 方 法, 前面 已经 详细 分析 过, 不再 累述 。 像moveFromLocal、moveFromLocal、copyToLocal、moveToLocal、copyMergeToLocal 等 命令 的实 现都 非常 类似 ,也 不做 过多 的解 释了 。 通 过前 面, 对 Hadoop 的org.apache.hadoop.fs 包 中内 容进 行分 析, 已经 基本 了解 到, 一 个 文件 系统 应该 具备 哪些 基本 要素 和基 本操 作。 最显 著的 一个 特点 就是 , FileSystem 文件 系 统是 基于 流式 数据 访问 的 ,并且,可 以基 于命 令行 的方 式来 对文 件系 统的 文件 进行 管理 与 操 作。 而且 ,基 于 FileSystem 文 件系 统的 抽象 定义 ,我 们可 以了 解到 ,继 承自 该抽 象的 一 切 具体 实现 的文 件系 统, 都具 有统 一的 文件 访问 接口 。 对于HDFS(Hadoop Distributed FileSystem),同 样也 是基 于 FileSystem 的 抽象 实现 的 , 下 面看 org.apache.hadoop.hdfs 包 中的 代码 ,该 包中 的类 都是 与一 个基 本的 HDFS 相 关的 。 先 看继 承自 FileSystem 的 实现 类 DistributedFileSystem。 该分 布式 文件 系统 是可 配置 的, 在 继承 自 FileSystem 的 配置 的基 础上 ,还 有它 专有 的 DFS 配 置文 件, 如下 所示 : view plain 1.staticstaticstaticstatic{ 2.Configuration.addDefaultResource("hdfs-default.xml"); 3.Configuration.addDefaultResource("hdfs-site.xml"); 4.} 第 一个 是 DFS 默 认的 配置 文件 ,第 二个 是用 户可 以根 据需 要进 行定 制的 配置 选项 ,如 果 hdfs-site.xml 中 定义 的属 性在 hdfs-default.xml 中 已经 存在 ,会 覆盖 掉 hdfs-default.xml 中默 认 的属 性值 。 该 类中 有一 个内 部类 DiskStatus, 用来 表示 一个 DistributedFileSystem 文 件系 统中 磁盘 使 用 的统 计信 息, 如下 所示 : view plain 1.publicpublicpublicpublicstaticstaticstaticstaticclassclassclassclassDiskStatus{ 2.privateprivateprivateprivatelonglonglonglongcapacity;//总容量 3.privateprivateprivateprivatelonglonglonglongdfsUsed;//DFS使用量 4.privateprivateprivateprivatelonglonglonglongremaining;//可用量 5.publicpublicpublicpublicDiskStatus(longlonglonglongcapacity,longlonglonglongdfsUsed,longlonglonglongremaining){ 6.thisthisthisthis.capacity=capacity; 7.thisthisthisthis.dfsUsed=dfsUsed; 8.thisthisthisthis.remaining=remaining; 9.} 10. 11.publicpublicpublicpubliclonglonglonglonggetCapacity(){ 12.returnreturnreturnreturncapacity; 13.} 14.publicpublicpublicpubliclonglonglonglonggetDfsUsed(){ 15.returnreturnreturnreturndfsUsed; 16.} 17.publicpublicpublicpubliclonglonglonglonggetRemaining(){ 18.returnreturnreturnreturnremaining; 19.} 20.} 在DistributedFileSystem 实 现中 ,主 要是 通过 其定 义的 DFSClient dfs; 来 完成 文件 系 统 FileSystem 抽 象的 基本 操作 。DFSClient 能 够连 接到 Hadoop 文 件系 统 ,执 行基 本的 文件 操 作 。对 于 HDFS 的 用户 如果 想要 对 HDFS 执 行指 定的 操作 ,必 须获 取到 一 个 DistributedFileSystem 实 例, 才能 通过 DistributedFileSystem 内 部的 DFSClient 来 处理 任 务。 这 里, 先回 顾一 下 HDFS 的 架构 要点 ,以 便能 对 HDFS 有 个深 入的 了解 。 HDFS 的 架构 采用 master/slave 模 式, 一个 HDFS 集 群是 由一 个 Namenode 和多个 Datanode 组 成。 在HDFS 集 群中 ,只 有一 个 Namenode 结 点。 Namenode 作为HDFS 集 群的 中心 服务 器 , 主 要负 责: 1、 管理 HDFS 集 群中 文件 系统 的名 字空 间( Namespace) ,例 如打 开文 件系 统、 关闭 文 件 系统 、重 命名 文件 或者 目录 等 ;另外,对 任何 请求 对文 件系 统名 字空 间或 者属 性进 行修 改 的 操作 ,都 被 Namenode 记 录下 来。 2、管 理客 户端 对 HDFS 集 群中 的文 件系 统中 的文 件的 访问 ,实 际上 文件 以块 的形 式存 储 在 Datanode 上,文 件系 统客 户端 向 Namenode 请 求所 要执 行操 作的 文件 块 (该 块存 储在 指定 的Dadanode 数 据结 点上 ),然 后通 过与 Datanode 结 点交 互来 完成 文件 读写 的操 作 。那么, 文 件系 统客 户端 与 Namenode 交 互的 过程 中 ,只 有从 Namenode 中 获取 到了 所请 求的 文件 块 所对 应的 Datanode 结点,才 能执 行文 件的 读写 操作 。也 就是 说 ,Namenode 结 点还 负责 确 定指 定的 文件 块到 具体 的 Datanode 结 点的 映射 关系 。 3、 管理 Datanode 结 点的 状态 报告 ,包 括 Datanode 结 点的 健康 状态 报告 和其 所在 结点 上 数 据块 状态 报告 ,以 便能 够及 时处 理失 效的 数据 结点 。 在HDFS 集 群中 ,一个Datanode 结 点可 以存 在多 个 ,一 般是 一个 结点 上对 应一 个Datanode 实 例。 Datanode 数 据结 点进 程的 任务 是: 1、负 责管 理它 所在 结点 上存 储的 数据 的读 写 。一 般是 文件 系统 客户 端需 要请 求对 指定 数据 结 点进 行读 写操 作 ,Datanode 作 为数 据结 点的 服务 进程 来与 文件 系统 客户 端打 交道 。同时, 是 否需 要执 行对 文件 块的 创建 、删 除、 复制 等操 作, Datanode 数 据结 点进 程还 要 在 Namenode 的 统一 指挥 调度 下完 成, 当与 Namenode 交 互过 程中 收到 了可 以执 行文 件块 的 创建、删 除或 复制 操作 的命 令后 ,才 开始 让文 件系 统客 户端 执行 指定 的操 作 。具 体文 件的 操 作 并不 是 Datanode 来 实际 完成 的 ,而 是经 过 Datanode 许 可后 ,文 件系 统客 户端 进程 来执 行 实际 操作 。 2、向Namenode 结 点报 告状 态。 每个 Datanode 结 点会 周期 性地 向 Namenode 发 送心 跳 信 号和 文件 块状 态报 告, 以便 Namenode 获 取到 工作 集群 中 Datanode 结 点状 态的 全局 视 图,从 而掌 握它 们的 状态 。如 果存 在 Datanode 结 点失 效的 情况 时 ,Namenode 会 调度 其 它 Datanode 执 行失 效结 点上 文件 块的 复制 处理 ,保 证文 件块 的副 本数 达到 规定 数量 。 3、执 行数 据的 流水 线复 制 。当 文件 系统 客户 端从 Namenode 服 务器 进程 获取 到要 进行 复制 的 数据 块列 表 (列 表中 包含 指定 副本 的存 放位 置 ,亦 即某 个 Datanode 结点)后,会 首先 将 客 户端 缓存 的文 件块 复制 到第 一个 Datanode 结 点上 ,此 时并 非整 个块 都复 制到 第一 个 Datanode 完 成以 后才 复制 到第 二个 Datanode 结 点上 ,而 是由 第一 个 Datanode 向 第二 个 Datanode 结 点复 制, ……, 如此 下去 完成 文件 块及 其块 副本 的流 水线 复制 。 通 过上 面的 叙述 ,可 以看 到, 在 HDFS 集 群中 ,存 在三 个主 要的 进程 : Namenode 进 程、 Datanode 进 程和 文件 系统 客户 端进 程, 这三 个进 程之 间都 是基 于 Hadoop 实 现的 RPC 机 制 进行 通信 的, 该 IPC 模 型基 于 Client/Server 模 式进 行通 信。 因此 上述 三个 进程 之间 存在 如 下端 到端 通信 与交 互: 1、(Client)Datanode / Namenode(Server) 2、(Client)DFS Client / Namenode(Server) 3、(Client)DFS Client / Datanode(Server) 4、(Client)Datanode A/ Datanode B(Server) 接 下来 ,我 们看 DistributedFileSystem 分 布式 文件 系统 的实 现, 首先 看 DistributedFileSystem 是 如何 初始 化的 initialize 方 法: view plain 1.publicpublicpublicpublicvoidvoidvoidvoidinitialize(URIuri,Configurationconf)throwsthrowsthrowsthrowsIOException{ 2.supersupersupersuper.initialize(uri,conf);//继承自 FileSystem 3.setConf(conf); 4. 5.Stringhost=uri.getHost();//根据 URI获取到 Namenode主机 6.ifififif(host==nullnullnullnull){ 7.throwthrowthrowthrownewnewnewnewIOException("IncompleteHDFSURI,nohost:"+uri); 8.} 9. 10.InetSocketAddressnamenode=NameNode.getAddress(uri.getAuthority());//获 取到 Namenode的Socket地址 11.thisthisthisthis.dfs=newnewnewnewDFSClient(namenode,conf,statistics);//构造一个 DFSClient 实例 12.thisthisthisthis.uri=NameNode.getUri(namenode); 13.thisthisthisthis.workingDir=getHomeDirectory(); 14.} 通 过这 个初 始化 的方 法可 以看 到, 一个 DFSClient 实 例要 想执 行 DFS 上 的任 务, 必须 与 Namenode 建 立连 接, 在与 Namenode 通 信获 取许 可的 情况 下才 能执 行任 务。 DistributedFileSystem 类 的定 义基 本操 作, 主要 是从 它与 FilesSstem 抽 象类 定义 的不 同操 作 来了 解。 如下 所示 : view plain 1./** 2.*获取文件系统的磁盘使用情况统计数据 3.*/ 4.publicpublicpublicpublicDiskStatusgetDiskStatus()throwsthrowsthrowsthrowsIOException{ 5.returnreturnreturnreturndfs.getDiskStatus(); 6.} 7. 8./** 9.*设置安全模式状态 10.*/ 11.publicpublicpublicpublicbooleanbooleanbooleanbooleansetSafeMode(FSConstants.SafeModeActionaction) 12.throwsthrowsthrowsthrowsIOException{ 13.returnreturnreturnreturndfs.setSafeMode(action); 14.} 15. 16./** 17.*对文件系统名字空间映像进行保存 18.*/ 19.publicpublicpublicpublicvoidvoidvoidvoidsaveNamespace()throwsthrowsthrowsthrowsAccessControlException,IOException{ 20.dfs.saveNamespace(); 21.} 22. 23./** 24.*刷新主机列表 25.*/ 26.publicpublicpublicpublicvoidvoidvoidvoidrefreshNodes()throwsthrowsthrowsthrowsIOException{ 27.dfs.refreshNodes(); 28.} 29. 30./** 31.*返回标识为失效状态的块副本的数目 32.*/ 33.publicpublicpublicpubliclonglonglonglonggetCorruptBlocksCount()throwsthrowsthrowsthrowsIOException{ 34.returnreturnreturnreturndfs.getCorruptBlocksCount(); 35.} 36. 37./** 38.*返回每个 Datanode结点的状态信息 39.*/ 40.publicpublicpublicpublicDatanodeInfo[]getDataNodeStats()throwsthrowsthrowsthrowsIOException{ 41.returnreturnreturnreturndfs.datanodeReport(DatanodeReportType.ALL); 42.} 43. 44./* 45.*请求 Namenode结点 ,将文件系统元数据写入到指定文件 pathname中,如果该文件已经存在 , 则追加写入。 46.*/ 47.publicpublicpublicpublicvoidvoidvoidvoidmetaSave(Stringpathname)throwsthrowsthrowsthrowsIOException{ 48.dfs.metaSave(pathname); 49.} 可 见, 实际 上还 是通 过 org.apache.hadoop.hdfs.DFSClient 来 实现 保存 的操 作的 。还 有一 个 用来 报告 块副 本的 校验 和出 错的 方法 : view plain 1.publicpublicpublicpublicbooleanbooleanbooleanbooleanreportChecksumFailure(Pathf,FSDataInputStreamin,longlonglonglonginPo s,FSDataInputStreamsums,longlonglonglongsumsPos) 目 前实 现是 ,如 果某 个文 件块 及其 对应 的块 副本 的校 验和 不匹 配 ,只 能够 知道 该文 件块 状态 有 问题 ,没 有实 现报 告具 体是 哪个 块副 本出 了问 题。 一 个分 布式 文件 系统 实例 ,是 通过 DFS Client 来 进行 文件 的创 建 、删除、复 制等 等操 作的 。 因 此, 如果 希望 了解 DFSClient 如 何执 行这 些操 作的 ,就 需要 对 DFSClient 的 实现 进行 分 析 了。 DFSClient 是 分布 式文 件系 统客 户端 ,它 能够 连接 到 Hadoop 文 件系 统执 行指 定任 务 ,那么 它 要与 Namenode 与Datanode 基 于一 定的 协议 来进 行通 信。 这个 通信 过程 中, 涉及 到不 同 进程 之间 的通 信 。在org.apache.hadoop.ipc 包中,定 义了 进程 间通 信 的Client 端与Server 端 的抽 象 ,也 就是 基于 C/S 模 式进 行通 信 。这 里先 对 org.apache.hadoop.ipc 包 中有 关类 的 源 代码 阅读 分析 。 首 先看 该包 中类 的继 承关 系, 如下 所示 : view plain 1.。java.lang.Object 2.。org.apache.hadoop.ipc.Client 3.。org.apache.hadoop.ipc.RPC 4.。org.apache.hadoop.ipc.Server 5.。org.apache.hadoop.ipc.RPC.Server 6.。java.lang.Throwable(implementsimplementsimplementsimplementsjava.io.Serializable) 7.。java.lang.Exception 8.。java.io.IOException 9. 。org.apache.hadoop.ipc.RemoteException 10.。org.apache.hadoop.ipc.RPC.VersionMismatch 我 阅读 该包 源程 序的 方法 是 ,先从C/S 通 讯的 两端 Client 类与Server 类 来阅 读分 析 ,然后 再 对实 现的 一个 RPC 类 进行 分析 。 Client Client Client Client 类 首 先从 Client 客 户端 类的 实现 开始 ,该 类定 义了 如下 属性 : view plain 1.privateprivateprivateprivateHashtableconnections=newnewnewnewHashtable();//客户端维护到服务端的一组连接 2.privateprivateprivateprivateClassvalueClass;//classofcallvalues 3.privateprivateprivateprivateintintintintcounter;//counterforcallids 4.privateprivateprivateprivateAtomicBooleanrunning=newnewnewnewAtomicBoolean(truetruetruetrue);//客户端进程是否在运 行 5.finalfinalfinalfinalprivateprivateprivateprivateConfigurationconf;//配置类实例 6.finalfinalfinalfinalprivateprivateprivateprivateintintintintmaxIdleTime;//连接的最大空闲时间 7.finalfinalfinalfinalprivateprivateprivateprivateintintintintmaxRetries;//Socket连接时,最大 Retry次数 8.privateprivateprivateprivatebooleanbooleanbooleanbooleantcpNoDelay;//设置 TCP连接是否延迟 9.privateprivateprivateprivateintintintintpingInterval;//ping服务端的间隔 10. 11.privateprivateprivateprivateSocketFactorysocketFactory;//Socket工厂,用来创建 Socket 连接 12.privateprivateprivateprivateintintintintrefCount=1; 13. 14.finalfinalfinalfinalprivateprivateprivateprivatestaticstaticstaticstaticStringPING_INTERVAL_NAME="ipc.ping.interval";//通过 配置文件读取 ping间隔 15.finalfinalfinalfinalstaticstaticstaticstaticintintintintDEFAULT_PING_INTERVAL=60000;//默认 ping间隔为 1分钟 16.finalfinalfinalfinalstaticstaticstaticstaticintintintintPING_CALL_ID=-1; 从 属性 可以 看出 ,一 个 Clinet 主 要处 理的 是与 服务 端进 行连 接的 工作 ,包 括连 接的 创建 、 监 控等 。为 了能 够了 解到 Client 如 何实 现它 所抽 象的 操作 ,先 分别 看一 下该 类定 义的 5个 内 部类 : • Client.Call 内 部类 该 内部 类 ,是 客户 端调 用的 一个 抽象 ,主 要定 义了 一次 调用 所需 要的 条件 ,以 及修 改 Client 客 户端 的一 些全 局统 计变 量, 如下 所示 : view plain 1.privateprivateprivateprivateclassclassclassclassCall{ 2.intintintintid; //调用 ID 3.Writableparam;//调用参数 4.Writablevalue;//调用返回的值 5.IOExceptionerror;//异常信息 6.booleanbooleanbooleanbooleandone;//调用是否完成 7. 8.protectedprotectedprotectedprotectedCall(Writableparam){ 9.thisthisthisthis.param=param; 10.synchronizedsynchronizedsynchronizedsynchronized(Client.thisthisthisthis){ 11.thisthisthisthis.id=counter++;//互斥修改法:对多个连接的调用线程进行统计 12.} 13.} 14. 15./**调用完成,设置标志,唤醒其它线程 */ 16.protectedprotectedprotectedprotectedsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidcallComplete(){ 17.thisthisthisthis.done=truetruetruetrue; 18.notify();//唤醒其它调用者 19.} 20. 21./** 22.*调用出错,同样置调用完成标志,并设置出错信息 23.*/ 24.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidsetException(IOExceptionerror){ 25.thisthisthisthis.error=error; 26.callComplete(); 27.} 28. 29./** 30.*调用完成,设置调用返回的值 31.*/ 32.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidsetValue(Writablevalue){ 33.thisthisthisthis.value=value; 34.callComplete(); 35.} 36.} 上 面的 Call 内 部类 主要 是对 一次 调用 的实 例进 行监 视与 管理 ,即 使获 取调 用返 回值 ,如 果 出 错则 获取 出错 信息 ,同 时修 改 Client 全 局统 计变 量。 • Client.ConnectionId 内 部类 该 内部 类是 一个 连接 的实 体类 ,标 识了 一个 连接 实例 的 Socket 地 址、 用户 信 息 UserGroupInformation、连 接的 协议 类 。每 个连 接都 是通 过一 个该 类的 实例 唯一 标识 。如下 所 示: view plain 1.InetSocketAddressaddress; 2.UserGroupInformationticket; 3.Classprotocol; 4.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalintintintintPRIME=16777619; 该 类中 有一 个用 来判 断两 个连 接 ConnectionId 实 例是 否相 等的 equals 方 法: view plain 1.@Override 2.publicpublicpublicpublicbooleanbooleanbooleanbooleanequals(Objectobj){ 3.ifififif(objinstanceofinstanceofinstanceofinstanceofConnectionId){ 4.ConnectionIdid=(ConnectionId)obj; 5.returnreturnreturnreturnaddress.equals(id.address)&&protocol==id.protocol&&ticket== id.ticket; 6.} 7.returnreturnreturnreturnfalsefalsefalsefalse; 8.} 只 有当 Socket 地址、用 户信 息 UserGroupInformation、连 接的 协议 类这 三个 属性 的值 相等 时 ,才 被认 为是 同一 个 ConnectionId 实 例。 • Client.ParallelResults 内 部类 该 内部 类是 用来 收集 在并 行调 用环 境中 结果 的实 体类 ,如 下所 示: view plain 1.privateprivateprivateprivatestaticstaticstaticstaticclassclassclassclassParallelResults{ 2.privateprivateprivateprivateWritable[]values;//并行调用对应多次调用,对应多个返回值 3.privateprivateprivateprivateintintintintsize;//并行调用返回值个数统计 4.privateprivateprivateprivateintintintintcount;//并行调用次数 5. 6.publicpublicpublicpublicParallelResults(intintintintsize){ 7.thisthisthisthis.values=newnewnewnewWritable[size]; 8.thisthisthisthis.size=size; 9.} 10. 11./**收集并行调用返回值 */ 12.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidcallComplete(ParallelCallcall){ 13.values[call.index]=call.value;//存储返回值 14.count++;//统计返回值个数 15.ifififif(count==size)//并行调用的多个调用完成 16.notify();//唤醒下一个实例 17.} 18.} • Client.ParallelCall 内 部类 该 内部 类继 承自 上面 的内 部类 Call, 只是 返回 值使 用上 面定 义的 ParallelResults 实 体类 来 封 装, 如下 所示 : view plain 1.privateprivateprivateprivateclassclassclassclassParallelCallextendsextendsextendsextendsCall{ 2.privateprivateprivateprivateParallelResultsresults; 3.privateprivateprivateprivateintintintintindex; 4. 5.publicpublicpublicpublicParallelCall(Writableparam,ParallelResultsresults,intintintintindex){ 6.supersupersupersuper(param); 7.thisthisthisthis.results=results; 8.thisthisthisthis.index=index; 9.} 10. 11./**收集并行调用返回结果值 */ 12.protectedprotectedprotectedprotectedvoidvoidvoidvoidcallComplete(){ 13.results.callComplete(thisthisthisthis); 14.} 15.} • Client.Connection 内 部类 该 类是 一个 连接 管理 内部 线程 类 ,该 内部 类是 一个 连接 线程 ,继 承自 Thread 类。它 读取 每 一个Call 调 用实 例执 行后 从服 务端 返回 的响 应信 息, 并通 知其 他调 用实 例。 每一 个连 接具 有 一个 连接 到远 程主 机的 Socket,该Socket 能 够实 现多 路复 用, 使得 多个 调用 复用 该 Socket, 客户 端收 到的 调用 得到 的响 应可 能是 无序 的。 该 类定 义的 属性 如下 所示 : view plain 1.privateprivateprivateprivateInetSocketAddressserver;//服务端 ip:port 2.privateprivateprivateprivateConnectionHeaderheader;//连接头信息,该实体类封装了连接 协议与用户信息 UserGroupInformation 3.privateprivateprivateprivateConnectionIdremoteId;//连接 ID 4. 5.privateprivateprivateprivateSocketsocket=nullnullnullnull;//客户端已连接的 Socket 6.privateprivateprivateprivateDataInputStreamin; 7.privateprivateprivateprivateDataOutputStreamout; 8. 9.privateprivateprivateprivateHashtablecalls=newnewnewnewHashtable();// 当前活跃的调用列表 10.privateprivateprivateprivateAtomicLonglastActivity=newnewnewnewAtomicLong();//最后 I/O活跃的时间 11.privateprivateprivateprivateAtomicBooleanshouldCloseConnection=newnewnewnewAtomicBoolean();//连接是 否关闭 12.privateprivateprivateprivateIOExceptioncloseException;//连接关闭原因 上 面使 用到 了java.util.concurrent.atomic包 中的 一些 工具 ,像AtomicLong、AtomicBoolean, 这 些类 能够 以原 子方 式更 新其 值 ,支 持在 单个 变量 上解 除锁 二实 现线 程的 安全 。这 些类 能够 使用get 方 法读 取 volatile 变 量的 内存 效果 , set 方 法可 以设 置对 应变 量的 内存 值。 通过 后 面 的代 码可 以看 到该 类工 具类 的使 用。 例如 : view plain 1.privateprivateprivateprivatevoidvoidvoidvoidtouch(){ 2.lastActivity.set(System.currentTimeMillis()); 3.} 上 面定 义的 calls 集 合, 是用 来保 存当 前活 跃的 调用 实例 ,以 键值 对的 形式 保存 ,键 是一 个 Call 的id, 值是 Call 的 实例 ,因 此, 该类 一定 提供 了向 该集 合中 添加 新的 调用 实例 、移 除 调 用实 例等 等操 作, 分别 将方 法签 名列 表如 下: view plain 1./** 2.*向calls集合中添加一个 3.*/ 4.privateprivateprivateprivatesynchronizedsynchronizedsynchronizedsynchronizedbooleanbooleanbooleanbooleanaddCall(Callcall); 5. 6./* 7.*等待某个调用线程唤醒自己,可能开始如下操作: 8.*1、读取 RPC响应数据 9.*2、idle时间过长 10.*3、被标记为应该关闭 11.*4、客户端已经终止 12.*/ 13.privateprivateprivateprivatesynchronizedsynchronizedsynchronizedsynchronizedbooleanbooleanbooleanbooleanwaitForWork(); 14. 15./* 16.*接收到响应(因为每次从 DataInputStreamin中读取响应信息只有一个,无需同步) 17.*/ 18.privateprivateprivateprivatevoidvoidvoidvoidreceiveResponse(); 19. 20./* 21.*关闭连接,需要迭代 calls集合,清除连接 22.*/ 23.privateprivateprivateprivatesynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidclose(); 可 以看 到, 当每 次调 用 touch 方 法的 时候 ,都 会将 lastActivity 原 子变 量设 置为 系统 的当 前 时 间, 更新 了变 量的 值。 该操 作是 对多 个线 程进 行互 斥的 ,也 就是 每次 修改 lastActivity 的 值 的时 候 ,都 会对 该变 量加 锁 ,从 内存 中读 取该 变量 的当 前值 ,因 此可 能会 出现 阻塞 的情 况 。 下 面看 一个 Connection 实 例的 构造 实现 : view plain 1.publicpublicpublicpublicConnection(ConnectionIdremoteId)throwsthrowsthrowsthrowsIOException{ 2.thisthisthisthis.remoteId=remoteId;//远程服务端连接 3.thisthisthisthis.server=remoteId.getAddress();//远程服务器地址 4.ifififif(server.isUnresolved()){ 5.throwthrowthrowthrownewnewnewnewUnknownHostException("unknownhost:"+remoteId.getAddress(). getHostName()); 6.} 7. 8.UserGroupInformationticket=remoteId.getTicket();//用户信息 9.Classprotocol=remoteId.getProtocol();//协议 10.header=newnewnewnewConnectionHeader(protocol==nullnullnullnull?nullnullnullnull:protocol.getName(), ticket);//连接头信息 11.thisthisthisthis.setName("IPCClient("+socketFactory.hashCode()+")connectionto" +remoteId.getAddress().toString()+"from"+((ticket==nullnullnullnull)?"anunknown user":ticket.getUserName())); 12.thisthisthisthis.setDaemon(truetruetruetrue);//并设置一个连接为后台线程 13.} 通过Collection 实 例的 构造 ,可 以看 到, 客户 端所 拥有 的 Connection 实 例, 通过 一个 远 程 ConnectionId 实 例来 建立 到客 户端 到服 务端 的连 接 。接 着看 一下 Connection 线 程类 线程 体 代 码: view plain 1.publicpublicpublicpublicvoidvoidvoidvoidrun(){ 2.ifififif(LOG.isDebugEnabled()) 3.LOG.debug(getName()+":starting,havingconnections"+connections.si ze()); 4. 5.whilewhilewhilewhile(waitForWork()){//等待某个连接实例空闲,如果存在则唤醒它执行一些任务 6.receiveResponse();//接收 RPC响应 7.} 8. 9.close();//关闭 10. 11.ifififif(LOG.isDebugEnabled()) 12.LOG.debug(getName()+":stopped,remainingconnections"+connections. size()); 13.} 客 户端 Client 类 提供 的最 基本 的功 能就 是执 行 RPC 调 用, 其中 ,提 供了 两种 调用 方式 ,一 种 就是 串行 单个 调用 ,另 一种 就是 并行 调用 ,分 别介 绍如 下 。首 先是 串行 单个 调用 的实 现方 法call, 如下 所示 : view plain 1./* 2.*执行一个调用,通过传递参数值 param到运行在 addr上的 IPC服务器, IPC服务器基 于 protocol与用户的 ticket来认证,并响应客户端 3.*/ 4.publicpublicpublicpublicWritablecall(Writableparam,InetSocketAddressaddr,Classprotoc ol,UserGroupInformationticket)throwsthrowsthrowsthrowsInterruptedException,IOException{ 5.Callcall=newnewnewnewCall(param);//使用请求参数值构造一个 Call实例 6.Connectionconnection=getConnection(addr,protocol,ticket,call);//从 连接池 connections中获取到一个连接(或可能创建一个新的连接) 7.connection.sendParam(call);//向IPC服务器发送参数 8.booleanbooleanbooleanbooleaninterrupted=falsefalsefalsefalse; 9.synchronizedsynchronizedsynchronizedsynchronized(call){ 10.whilewhilewhilewhile(!call.done){ 11.trytrytrytry{ 12.call.wait();//等待 IPC服务器响应 13.}catchcatchcatchcatch(InterruptedExceptionie){ 14.interrupted=truetruetruetrue; 15.} 16.} 17. 18.ifififif(interrupted){ 19.//settheinterruptflagnowthatwearedonewaiting 20.Thread.currentThread().interrupt(); 21.} 22. 23.ifififif(call.error!=nullnullnullnull){ 24.ifififif(call.errorinstanceofinstanceofinstanceofinstanceofRemoteException){ 25.call.error.fillInStackTrace(); 26.throwthrowthrowthrowcall.error; 27.}elseelseelseelse{//localexception 28.throwthrowthrowthrowwrapException(addr,call.error); 29.} 30.}elseelseelseelse{ 31.returnreturnreturnreturncall.value;//调用返回的响应值 32.} 33.} 34.} 然 后, 就是 并行 调用 的实 现 call 方 法, 如下 所示 : view plain 1./* 2.*执行并行调用 3.*每个参数都被发送到相关的 IPC服务器,然后等待服务器响应信息 4.*/ 5.publicpublicpublicpublicWritable[]call(Writable[]params,InetSocketAddress[]addresses,Cla ssprotocol,UserGroupInformationticket)throwsthrowsthrowsthrowsIOException{ 6.ifififif(addresses.length==0)returnreturnreturnreturnnewnewnewnewWritable[0]; 7.ParallelResultsresults=newnewnewnewParallelResults(params.length);//根据待调用 的参数个数来构造一个用来封装并行调用返回值的 ParallelResults对象 8.synchronizedsynchronizedsynchronizedsynchronized(results){ 9.forforforfor(intintintinti=0;iparamClass;//调用的参数的类,必须实 现 Writable序列化接口 5.privateprivateprivateprivateintintintintmaxIdleTime;//当一个客户端断开连接后的最大 空闲时间 6.privateprivateprivateprivateintintintintthresholdIdleConnections;//可维护的最大连接数量 7.intintintintmaxConnectionsToNuke;//themaxnumberofconnect ionstonukeduringacleanup 8.protectedprotectedprotectedprotectedRpcMetricsrpcMetrics;//维护 RPC统计数据 9.privateprivateprivateprivateConfigurationconf;//配置类实例 10. 11.privateprivateprivateprivateintintintintmaxQueueSize;//处理器 Handler实例队列大 小 12.privateprivateprivateprivateintintintintsocketSendBufferSize;//SocketBuffer大小 13.privateprivateprivateprivatefinalfinalfinalfinalbooleanbooleanbooleanbooleantcpNoDelay;//ifTthendisableNagle's Algorithm 14. 15.volatilevolatilevolatilevolatileprivateprivateprivateprivatebooleanbooleanbooleanbooleanrunning=truetruetruetrue;//Server是否运行 16.privateprivateprivateprivateBlockingQueuecallQueue;//维护调用实例的队列 17. 18.privateprivateprivateprivateListconnectionList=Collections.synchronizedList(newnewnewnewL inkedList());//维护客户端连接的列表 19.privateprivateprivateprivateListenerlistener=nullnullnullnull;//监听 ServerSocket的线程, 为处理器 Handler线程创建任务 20.privateprivateprivateprivateResponderresponder=nullnullnullnull;//响应客户端 RPC调用的线程 ,向 客户端调用发送响应信息 21.privateprivateprivateprivateintintintintnumConnections=0;//连接数量 22.privateprivateprivateprivateHandler[]handlers=nullnullnullnull;//处理器 Handler线程数组 一个Server 实 例的 构造 基本 上基 于上 面的 属性 信息 的 ,构 造方 法对 一个 Server 实 例进 行初 始 化, 包括 一些 静态 信息 如绑 定地 址、 维护 连接 数量 、队 列等 ,还 有一 些用 来处 理 Server 端 事务 的线 程等 等。 先对Server 类 中定 义的 几个 内部 类来 分析 ,这 些类 都是 与 Server 端 一些 重要 的事 务的 处理 类 。然 后再 分析 Server 类 提供 的全 部基 本操 作。 • Server.Call 内 部类 该类Server 端 使用 队列 维护 的调 用实 体类 ,如 下所 示: view plain 1.privateprivateprivateprivatestaticstaticstaticstaticclassclassclassclassCall{ 2.privateprivateprivateprivateintintintintid; //客户端调用 Call的ID 3.privateprivateprivateprivateWritableparam;//客户端调用传递的参数 4.privateprivateprivateprivateConnectionconnection;//到客户端的连接实例 5.privateprivateprivateprivatelonglonglonglongtimestamp;//向客户端调用发送响应的时间 戳 6.privateprivateprivateprivateByteBufferresponse;//向客户端调用响应的字节缓冲 区 7. 8.publicpublicpublicpublicCall(intintintintid,Writableparam,Connectionconnection){ 9.thisthisthisthis.id=id; 10.thisthisthisthis.param=param; 11.thisthisthisthis.connection=connection; 12.thisthisthisthis.timestamp=System.currentTimeMillis(); 13.thisthisthisthis.response=nullnullnullnull; 14.} 15. 16.@Override 17.publicpublicpublicpublicStringtoString(){ 18.returnreturnreturnreturnparam.toString()+"from"+connection.toString(); 19.} 20. 21.publicpublicpublicpublicvoidvoidvoidvoidsetResponse(ByteBufferresponse){ 22.thisthisthisthis.response=response; 23.} 24.} • Server.Connection 内 部类 该 类表 示服 务端 一个 连接 的抽 象, 主要 是读 取从 Client 发 送的 调用 ,并 把读 取到 的调 用 Client.Call 实 例加 入到 待处 理的 队列 。 看 如何 构造 一个 Server.Connection 对 象: view plain 1.publicpublicpublicpublicConnection(SelectionKeykey,SocketChannelchannel,longlonglonglonglastContact) { 2.thisthisthisthis.channel=channel;//Socket通道 3.thisthisthisthis.lastContact=lastContact;//最后连接时间 4.thisthisthisthis.data=nullnullnullnull; 5.thisthisthisthis.dataLengthBuffer=ByteBuffer.allocate(4);// 6.thisthisthisthis.socket=channel.socket();//获取到与通道 channel关联的 Socket 7.InetAddressaddr=socket.getInetAddress();//获取 Socket地址 8.ifififif(addr==nullnullnullnull){ 9.thisthisthisthis.hostAddress="*Unknown*"; 10.}elseelseelseelse{ 11.thisthisthisthis.hostAddress=addr.getHostAddress(); 12.} 13.thisthisthisthis.remotePort=socket.getPort();//获取到远程连接的端口号 14.thisthisthisthis.responseQueue=newnewnewnewLinkedList();//服务端待处理调用的队列 15.ifififif(socketSendBufferSize!=0){ 16.trytrytrytry{ 17.socket.setSendBufferSize(socketSendBufferSize);//设置 SocketBuffer大 小 18.}catchcatchcatchcatch(IOExceptione){ 19.LOG.warn("Connection:unabletosetsocketsendbuffersizeto"+soc ketSendBufferSize); 20.} 21.} 22.} 另 外, Server.Connection 内 部类 中还 定义 了如 下几 个属 性: view plain 1.privateprivateprivateprivatebooleanbooleanbooleanbooleanversionRead=falsefalsefalsefalse;//是否初始化签名,并读取了版本信息 2.privateprivateprivateprivatebooleanbooleanbooleanbooleanheaderRead=falsefalsefalsefalse;//是否读取了头信息 3.privateprivateprivateprivateintintintintdataLength;//数据长度 4.ConnectionHeaderheader=newnewnewnewConnectionHeader();//连接头信息 5.Classprotocol;//协议类 6.Subjectuser=nullnullnullnull;//用户的 Subject信息 该 内部 类中 ,readAndProcess()方 法读 取远 程过 程调 用的 数据 ,从 一个 Server.Connection 的Socket 通 道中 读取 数据 ,并 将调 用任 务加 入到 callQueue,转 交给 Handler 线 程去 处理 。 下 面看 下该 方法 的实 现: view plain 1.publicpublicpublicpublicintintintintreadAndProcess()throwsthrowsthrowsthrowsIOException,InterruptedException{ 2.whilewhilewhilewhile(truetruetruetrue){ 3.intintintintcount=-1; 4.//从通道 channel中读取字节,加入到 dataLengthBuffer字节缓冲区 5.ifififif(dataLengthBuffer.remaining()>0){ 6.count=channelRead(channel,dataLengthBuffer);//如果通道已经达 到了流的末尾,会返回 -1的 7.ifififif(count<0||dataLengthBuffer.remaining()>0)//读取不成功 , 直接返回读取的字节数(读取失败可能返回 0或-1) 8.returnreturnreturnreturncount; 9.} 10. 11.ifififif(!versionRead){//如果版本号信息还没有读取 12.ByteBufferversionBuffer=ByteBuffer.allocate(1); 13.count=channelRead(channel,versionBuffer);//读取版本号信息 14.ifififif(count<=0){//没有从通道 channel中读取到版本号信息 ,直接返回 15.returnreturnreturnreturncount; 16.} 17.intintintintversion=versionBuffer.get(0);//读取到了版本号信息,从字节缓 冲区中获取出来 18. 19.dataLengthBuffer.flip();//反转 dataLengthBuffer缓冲区 20.//如果读取到的版本号信息不匹配,返回 -1 (HEADER=ByteBuffer.wrap("hrpc".getBytes()),CURRENT_VERSION=3) 21.ifififif(!HEADER.equals(dataLengthBuffer)||version!=CURRENT_VERSI ON){ 22.//Warningisoksincethisisnotsupposedtohappen. 23.LOG.warn("Incorrectheaderorversionmismatchfrom" 24.+hostAddress+":"+remotePort 25.+"gotversion"+version 26.+"expectedversion"+CURRENT_VERSION); 27.returnreturnreturnreturn-1; 28.} 29.//成功读取到了版本号信息,清空 dataLengthBuffer以便重用,同时设 置 versionRead为true 30.dataLengthBuffer.clear(); 31.versionRead=truetruetruetrue; 32.continuecontinuecontinuecontinue; 33.} 34. 35.ifififif(data==nullnullnullnull){ 36.dataLengthBuffer.flip(); 37.dataLength=dataLengthBuffer.getInt();//读取数据长度信息 ,以便分 配data字节缓冲区 38. 39.ifififif(dataLength==Client.PING_CALL_ID){//如果是 Client端的 ping 调用,不需要处理数据,清空 dataLengthBuffer,返回 40.dataLengthBuffer.clear(); 41.returnreturnreturnreturn0; 42.} 43.data=ByteBuffer.allocate(dataLength);//分配 data数据缓冲区,准 备接收调用参数数据 44.incRpcCount();//增加 RPC调用统计计数 45.} 46. 47.count=channelRead(channel,data);//从通道 channel中读取字节到 data 字节缓冲区中 48. 49.ifififif(data.remaining()==0){//如果 data已经如期读满 50.dataLengthBuffer.clear();//清空 dataLengthBuffer 51.data.flip();//反转 dat字节缓冲区,准备从 data缓冲区读取数据 52.ifififif(headerRead){//如果头信息已经读过了 ,读取到的一定是 RPC调用参数 数据 53.processData();//调用:处理读取到的调用数据,通过反序列化操作从 网络字节流中冲重构调用参数数据对象 ,并构造 Server.Call对象 ,同时加入 callQueue队列 , 等待 Server.Handler线程进行处理 54.data=nullnullnullnull; 55.returnreturnreturnreturncount;//处理完成后返回 56.}elseelseelseelse{//如果头信息未读 57.processHeader();//读取版本号后面的连接头信息 58.headerRead=truetruetruetrue;//设置连接头信息已经读取过 59.data=nullnullnullnull;//重置 data字节缓冲区,以备下一个连接到来时缓冲字 节 60. 61.//通过调 用processHeader()方法 ,已经将用户 的Subject信息 从header 中读取到 user中 62.trytrytrytry{ 63.authorize(user,header);//为客户端到来的连接进行授权 64. 65.ifififif(LOG.isDebugEnabled()){ 66.LOG.debug("Successfullyauthorized"+header); 67.} 68.}catchcatchcatchcatch(AuthorizationExceptionae){ 69.authFailedCall.connection=thisthisthisthis; 70.setupResponse(authFailedResponse,authFailedCall, 71. Status.FATAL,nullnullnullnull, 72. ae.getClass().getName(),ae.getMessage()); 73.responder.doRespond(authFailedCall); 74. 75.//Closethisconnection 76.returnreturnreturnreturn-1; 77.} 78. 79.continuecontinuecontinuecontinue; 80.} 81.} 82.returnreturnreturnreturncount; 83.} 84.} 上 面方 法是 接收 调用 数据 的核 心方 法, 实现 了如 何从 SocketChannel 通 道中 读取 数据 。其 中processHeader 方 法与 processData 方 法已 经在 上面 种详 细分 析了 ,不 再多 说。 另外,作为Server.Connection 是 连接 到客 户端 的 ,与 客户 端调 用进 行通 信 ,所 以一 个连 接 定 义了 关闭 的操 作, 关闭 的时 候需 要关 闭与 客户 端 Socket 关 联的 SocketChannel 通 道。 • Server.Listener 内 部类 该 类是 继承 自 Thread 线 程类 ,用 来监 听服 务器 Socket,并未Handler 处 理器 线程 创建 处理 任 务。 从一 个 Listener 线 程类 的构 造来 它需 要初 始化 哪些 必要 信息 : view plain 1./* 2.*构造一个 Listener实例,初始化线程数据 3.*/ 4.publicpublicpublicpublicListener()throwsthrowsthrowsthrowsIOException{ 5.address=newnewnewnewInetSocketAddress(bindAddress,port);//根据 bindAddress和port创建一个 Socket地址 6.acceptChannel=ServerSocketChannel.open();//创建一 个 ServerSocket通道( ServerSocketChannel) 7.acceptChannel.configureBlocking(falsefalsefalsefalse);//设置 ServerSocket通道为非阻塞模式 8.bind(acceptChannel.socket(),address,backlogLength);//绑定 9.port=acceptChannel.socket().getLocalPort();//Socket绑定 端口 10.selector=Selector.open();//创建一个选择 器(使用选择器,可以使得指定的通道多路复用) 11.acceptChannel.register(selector,SelectionKey.OP_ACCEPT);//向通 道 acceptChannel注册上述 selector选择器,选择器的键为 ServerSocket接受的操作集合 12.thisthisthisthis.setName("IPCServerlisteneron"+port);//设置监听线程 名称 13.thisthisthisthis.setDaemon(truetruetruetrue);//设置为后台线 程 14.} 该 线程 类定 义的 方法 如下 所示 : view plain 1./** 2.*根据连接的空闲时间来清除 connectionList中维护的连接 3.*/ 4.privateprivateprivateprivatevoidvoidvoidvoidcleanupConnections(booleanbooleanbooleanbooleanforce); 5. 6./** 7.*根据 key获取到与该 key关联的连接,并关闭它 8.*/ 9.privateprivateprivateprivatevoidvoidvoidvoidcloseCurrentConnection(SelectionKeykey,Throwablee); 10. 11./** 12.*根据 key关联的 ServerSocket通道,接收该通道上 Client端到来的连接 13.*/ 14.voidvoidvoidvoiddoAccept(SelectionKeykey)throwsthrowsthrowsthrowsIOException,OutOfMemoryError{ 15.Connectionc=nullnullnullnull; 16.ServerSocketChannelserver=(ServerSocketChannel)key.channel();//获取 到 ServerSocket通道 17.forforforfor(intintintinti=0;i<10;i++){//选择的该通道最多接受 10个连接 18.SocketChannelchannel=server.accept();//ClientSocket通道 19.ifififif(channel==nullnullnullnull)returnreturnreturnreturn; 20.channel.configureBlocking(falsefalsefalsefalse);//设置为非阻塞模式 21.channel.socket().setTcpNoDelay(tcpNoDelay);//设置 TCP连接是否延迟 22.SelectionKeyreadKey=channel.register(selector,SelectionKey.OP_READ); //向选择器 selector注册读操作集合,返回键 23.c=newnewnewnewConnection(readKey,channel,System.currentTimeMillis());//创建 连接 24.readKey.attach(c);//使连接实例与注册到选择 器selector相关的读操作集合键相关 联 25.synchronizedsynchronizedsynchronizedsynchronized(connectionList){ 26.connectionList.add(numConnections,c);//加入 Server端连接维护列表 27.numConnections++;//修改连接计数 28.} 29.ifififif(LOG.isDebugEnabled()) 30.LOG.debug("Serverconnectionfrom"+c.toString()+ 31.";#activeconnections:"+numConnections+ 32.";#queuedcalls:"+callQueue.size()); 33.} 34.} 上 面方 法, 是一 个收 集来 自客 户端 的连 接的 实现 。下 面看 一下 监听 线程 的线 程体 部分 实现 : view plain 1.@Override 2.publicpublicpublicpublicvoidvoidvoidvoidrun(){ 3.LOG.info(getName()+":starting"); 4.SERVER.set(Server.thisthisthisthis);//设置当前监听线程本地变量的拷贝 5.whilewhilewhilewhile(running){//如果服务器正在运行中 6.SelectionKeykey=nullnullnullnull; 7.trytrytrytry{ 8.selector.select();//选择一组 key集合,这些选择的 key相关联的通道已 经为 I/O操作做好准备 9.Iteratoriter=selector.selectedKeys().iterator(); 10.whilewhilewhilewhile(iter.hasNext()){ 11.key=iter.next();//迭代出一个 key 12.iter.remove(); 13.trytrytrytry{ 14.ifififif(key.isValid()){ 15.ifififif(key.isAcceptable())//如果 该key对应的通道已经准备 好接收新的 Socket连接 16. doAccept(key);//调用 ,接收与该 key关联的通道上的连 接 17.elseelseelseelseifififif(key.isReadable())//如果该通道为读取数据做好 准备 18. doRead(key);//从通道读取数据,主要调用 了 Server.Connection的readAndProcess方法来读取数据,并设置该连接的最后连接时间 19.} 20.}catchcatchcatchcatch(IOExceptione){ 21.} 22.key=nullnullnullnull; 23.} 24.}catchcatchcatchcatch(OutOfMemoryErrore){ 25.//wecanrunoutofmemoryifwehavetoomanythreads 26.//logtheeventandsleepforaminuteandgive 27.//somethread(s)achancetofinish 28.LOG.warn("OutofMemoryinserverselect",e); 29.closeCurrentConnection(key,e); 30.cleanupConnections(truetruetruetrue); 31.trytrytrytry{ 32.Thread.sleep(60000); 33.}catchcatchcatchcatch(Exceptionie){ 34.} 35.}catchcatchcatchcatch(InterruptedExceptione){ 36.ifififif(running){//unexpected--logit 37.LOG.info(getName()+"caught:" 38.+StringUtils.stringifyException(e)); 39.} 40.}catchcatchcatchcatch(Exceptione){ 41.closeCurrentConnection(key,e); 42.} 43.cleanupConnections(falsefalsefalsefalse); 44.} 45.LOG.info("Stopping"+thisthisthisthis.getName()); 46. 47.//跳出 while循环,即 running=false,服务器已经不再运行,需要关闭通道、选择器 、 全部连接 48.synchronizedsynchronizedsynchronizedsynchronized(thisthisthisthis){ 49.trytrytrytry{ 50.acceptChannel.close();//关闭通道 51.selector.close();//关闭通道选择器 52.}catchcatchcatchcatch(IOExceptione){ 53.} 54. 55.selector=nullnullnullnull; 56.acceptChannel=nullnullnullnull; 57. 58.//关闭全部连接 59.whilewhilewhilewhile(!connectionList.isEmpty()){ 60.closeConnection(connectionList.remove(0));//关闭一个连接 61.} 62.} 63.} 可见,Server.Listener 主 要负 责两 个阶 段的 任务 :当 服务 器运 行时 ,不 断地 通过 选择 器来 选 择 继续 的通 道, 处理 基于 该选 择的 通道 上通 信; 当服 务器 不再 运行 以后 ,需 要关 闭通 道 、选 择 器、 全部 链接 ,释 放一 切资 源。 • Server.Handler 内 部类 该 类是 一个 处理 线程 类, 负责 处理 客户 端的 全部 调用 。 该 类的 源代 码如 下所 示: view plain 1.privateprivateprivateprivateclassclassclassclassHandlerextendsextendsextendsextendsThread{ 2.publicpublicpublicpublicHandler(intintintintinstanceNumber){ 3.thisthisthisthis.setDaemon(truetruetruetrue);//作为后台线程运行 4.thisthisthisthis.setName("IPCServerhandler"+instanceNumber+"on"+port); 5.} 6. 7.@Override 8.publicpublicpublicpublicvoidvoidvoidvoidrun(){ 9.LOG.info(getName()+":starting"); 10.SERVER.set(Server.thisthisthisthis);//设置当前处理线程的本地变量的拷贝 11.ByteArrayOutputStreambuf=newnewnewnewByteArrayOutputStream(10240);//存放 响应信息的缓冲区 12.whilewhilewhilewhile(running){ 13.trytrytrytry{ 14.finalfinalfinalfinalCallcall=callQueue.take();//出队操作 ,获取到一个调 用 Server.Callcall 15. 16.ifififif(LOG.isDebugEnabled()) 17.LOG.debug(getName()+":has#"+call.id+"from"+c all.connection); 18. 19.StringerrorClass=nullnullnullnull; 20.Stringerror=nullnullnullnull; 21.Writablevalue=nullnullnullnull; 22. 23.CurCall.set(call);//设置当前线程本地变量拷贝的值为出队得到的一 个call调用实例 24.trytrytrytry{ 25.//根据调用 Server.Call关联的连接 Server.Connection,所对应 的用户 Subject,来执行 IPC调用过程 26.value=Subject.doAs(call.connection.user, 27. newnewnewnewPrivilegedExceptionAction(){ 28. @Override 29. publicpublicpublicpublicWritablerun()throwsthrowsthrowsthrowsException{ 30. //执行调用 31. returnreturnreturnreturncall(call.connection.protocol,ca ll.param,call.timestamp); 32. 33. } 34. }); 35. 36.}catchcatchcatchcatch(PrivilegedActionExceptionpae){ 37.Exceptione=pae.getException(); 38.LOG.info(getName()+",call"+call+":error:"+e, e); 39.errorClass=e.getClass().getName(); 40.error=StringUtils.stringifyException(e); 41.}catchcatchcatchcatch(Throwablee){ 42.LOG.info(getName()+",call"+call+":error:"+e, e); 43.errorClass=e.getClass().getName(); 44.error=StringUtils.stringifyException(e); 45.} 46.CurCall.set(nullnullnullnull);//当前 Handler线程处理完成一个调用 call,回 收当前线程的局部变量拷贝 47.//处理当前获取到的调用的响应 48.setupResponse(buf,call,(error==nullnullnullnull)?Status.SUCCESS: Status.ERROR,value,errorClass,error); 49.responder.doRespond(call);//将调用 call加入到响应队列中,等待 客户端读取响应信息 50.}catchcatchcatchcatch(InterruptedExceptione){ 51.ifififif(running){//unexpected--logit 52.LOG.info(getName()+"caught:"+StringUtils.stringify Exception(e)); 53.} 54.}catchcatchcatchcatch(Exceptione){ 55.LOG.info(getName()+"caught:"+StringUtils.stringifyExce ption(e)); 56.} 57.} 58.LOG.info(getName()+":exiting"); 59.} 60. 61.} 该 线程 主要 的任 务是 :真 正地 实现 了处 理来 自客 户端 的调 用, 并设 置每 个相 关调 用的 响应 。 关 于响 应的 实现 ,有 个具 体实 现的 线程 类 Server.Responder。 • Server.Responder 内 部类 该 线程 类实 现发 送 RPC 响 应到 客户 端。 我 们先 对该 线程 类中 方法 进行 阅读 分析 ,然 后再 看线 程体 的实 现过 程。 view plain 1./** 2.*处理一个通道上调用的响应数据 3.*如果一个通道空闲,返回 true 4.*/ 5.privateprivateprivateprivatebooleanbooleanbooleanbooleanprocessResponse(LinkedListresponseQueue,booleanbooleanbooleanbooleaninHa ndler)throwsthrowsthrowsthrowsIOException{ 6.booleanbooleanbooleanbooleanerror=truetruetruetrue; 7.booleanbooleanbooleanbooleandone=falsefalsefalsefalse;//一个通道 channel有更多的数据待读取 8.intintintintnumElements=0; 9.Callcall=nullnullnullnull; 10.trytrytrytry{ 11.synchronizedsynchronizedsynchronizedsynchronized(responseQueue){ 12.//如果该通道 channel空闲,处理响应完成 13.numElements=responseQueue.size(); 14.ifififif(numElements==0){ 15.error=falsefalsefalsefalse; 16.returnreturnreturnreturntruetruetruetrue;//完成响应的处理,返回 17.} 18.//从队列中取出第一个调用 call 19.call=responseQueue.removeFirst(); 20.SocketChannelchannel=call.connection.channel;//获取该调用对 应的通道 channel 21.ifififif(LOG.isDebugEnabled()){ 22.LOG.debug(getName()+":respondingto#"+call.id+"from "+call.connection); 23.} 24.//Sendasmuchdataaswecaninthenon-blockingfashion 25.intintintintnumBytes=channelWrite(channel,call.response);//向通 道 channel中写入响应信息(响应信息位于 call.response字节缓冲区中) 26.ifififif(numBytes<0){//如果写入字节数为 0,说明已经没有字节可写 ,返回 27.returnreturnreturnreturntruetruetruetrue; 28.} 29.ifififif(!call.response.hasRemaining()){//如果call.response字节缓冲 区中没有响应字节数据,说明已经全部写入到相关量的通道中 30.call.connection.decRpcCount();//该调 用call对应 的RPC连接计数 减1 31.ifififif(numElements==1){//最后一个调用已经处理完成 32.done=truetruetruetrue;//该通道 channel没有更多的数据 33.}elseelseelseelse{ 34.done=falsefalsefalsefalse;//否则 ,还存在尚未处理的调用 ,要向给通道发送数 据 35.} 36.ifififif(LOG.isDebugEnabled()){ 37.LOG.debug(getName()+":respondingto#"+call.id+" from"+call.connection+"Wrote"+numBytes+"bytes."); 38.} 39.}elseelseelseelse{//如果 call.response字节缓冲区中还存在未被写入通道响应字节 数据 40.call.connection.responseQueue.addFirst(call);//如果不能够将 全部的响应字节数据写入到通道中,需要暂时插入到 Selector选择其队列中 41.ifififif(inHandler){//如果指定 :现在就对调用 call进行处理 (该调用的 响应还没有进行处理) 42.call.timestamp=System.currentTimeMillis();//设置调用 时间戳 43.incPending();//增加未被处理响应信息的调用计数 44.trytrytrytry{ 45.writeSelector.wakeup();//唤醒阻塞在该通 道 writeSelector上的线程 46.channel.register(writeSelector,SelectionKey.OP_WRIT E,call);//调用 call注册通道 writeSelector 47.}catchcatchcatchcatch(ClosedChannelExceptione){ 48.done=truetruetruetrue; 49.}finallyfinallyfinallyfinally{ 50.decPending();//经过上面处理,不管在处理过程中正常处理 , 或是发生通道已关闭异常,最后,都将设置该调用完成,更新计数 51.} 52.} 53.ifififif(LOG.isDebugEnabled()){ 54.LOG.debug(getName()+":respondingto#"+call.id+" from"+call.connection+"Wrotepartial"+numBytes+"bytes."); 55.} 56.} 57.error=falsefalsefalsefalse;//设置出错标志:完成 58.} 59.}finallyfinallyfinallyfinally{ 60.ifififif(error&&call!=nullnullnullnull){ 61.LOG.warn(getName()+",call"+call+":outputerror"); 62.done=truetruetruetrue;//error.nomoredataforthischannel. 63.closeConnection(call.connection); 64.} 65.} 66.returnreturnreturnreturndone; 67.} 上 面方 法主 要实 现的 是 ,处 理响 应队 列 responseQueue 中 的全 部调 用 Call,对 应的 响应 数 据 。关 于处 理响 应的 调用 队列 ,是 指类 似 call.connection.responseQueue 的 响应 队列 ,可 以 理解 为某 个通 道上 调用 的集 合所 对应 的待 处理 响应 数据 的队 列。 看 下面 的 doRespond 方 法: view plain 1.voidvoidvoidvoiddoRespond(Callcall)throwsthrowsthrowsthrowsIOException{ 2.synchronizedsynchronizedsynchronizedsynchronized(call.connection.responseQueue){ 3.call.connection.responseQueue.addLast(call);//将执行完成的调用加入队 列,准备响应客户端 4.ifififif(call.connection.responseQueue.size()==1){ 5.processResponse(call.connection.responseQueue,truetruetruetrue);//如果队列 中只有一个调用,直接进行处理 6.} 7.} 8.} 当 某个 通道 上可 写的 时候 ,可 以执 行异 步写 响应 数据 的操 作, 实现 方法 为: view plain 1.privateprivateprivateprivatevoidvoidvoidvoiddoAsyncWrite(SelectionKeykey)throwsthrowsthrowsthrowsIOException{ 2.Callcall=(Call)key.attachment(); 3.ifififif(call==nullnullnullnull){ 4.returnreturnreturnreturn; 5.} 6.ifififif(key.channel()!=call.connection.channel){ 7.throwthrowthrowthrownewnewnewnewIOException("doAsyncWrite:badchannel"); 8.} 9. 10.synchronizedsynchronizedsynchronizedsynchronized(call.connection.responseQueue){ 11.ifififif(processResponse(call.connection.responseQueue,falsefalsefalsefalse)){//调用 processResponse处理与调用关联的响应数据 12.trytrytrytry{ 13.key.interestOps(0); 14.}catchcatchcatchcatch(CancelledKeyExceptione){ 15.LOG.warn("Exceptionwhilechangingops:"+e); 16.} 17.} 18.} 19.} 再看doPurge 方 法: view plain 1./** 2.*如果未被处理响应的调用在队列中滞留超过指定时限,要定时清除掉 3.*/ 4.privateprivateprivateprivatevoidvoidvoidvoiddoPurge(Callcall,longlonglonglongnow)throwsthrowsthrowsthrowsIOException{ 5.LinkedListresponseQueue=call.connection.responseQueue; 6.synchronizedsynchronizedsynchronizedsynchronized(responseQueue){ 7.Iteratoriter=responseQueue.listIterator(0); 8.whilewhilewhilewhile(iter.hasNext()){ 9.call=iter.next(); 10.ifififif(now>call.timestamp+PURGE_INTERVAL){ 11.closeConnection(call.connection); 12.breakbreakbreakbreak; 13.} 14.} 15.} 16.} 最 后, 看一 个 Responder 线 程启 动后 ,是 如何 工作 的, 在线 程体 run 方 法中 可以 看到 : view plain 1.@Override 2.publicpublicpublicpublicvoidvoidvoidvoidrun(){ 3.LOG.info(getName()+":starting"); 4.SERVER.set(Server.thisthisthisthis); 5.longlonglonglonglastPurgeTime=0;//最后一次清除过期调用的时间 6.whilewhilewhilewhile(running){//如果服务器处于运行状态 7.trytrytrytry{ 8.waitPending();//等待一个通道中,接收到来的调用进行注册 9.writeSelector.select(PURGE_INTERVAL);//设置超时时限 10.Iteratoriter=writeSelector.selectedKeys().itera tor(); 11.whilewhilewhilewhile(iter.hasNext()){//迭代选择器 writeSelector选择的 key集 合 12.SelectionKeykey=iter.next(); 13.iter.remove(); 14.trytrytrytry{ 15.ifififif(key.isValid()&&key.isWritable()){//如果合法 ,并且 通道可写 16.doAsyncWrite(key);//执行异步写操作 ,向通道中写入调用执 行的响应数据 17.} 18.}catchcatchcatchcatch(IOExceptione){ 19.LOG.info(getName()+":doAsyncWritethrewexception"+ e); 20.} 21.} 22.longlonglonglongnow=System.currentTimeMillis(); 23.ifififif(nowcalls; 31.synchronizedsynchronizedsynchronizedsynchronized(writeSelector.keys()){ 32.calls=newnewnewnewArrayList(writeSelector.keys().size()); 33.iter=writeSelector.keys().iterator(); 34.whilewhilewhilewhile(iter.hasNext()){ 35.SelectionKeykey=iter.next(); 36.Callcall=(Call)key.attachment(); 37.ifififif(call!=nullnullnullnull&&key.channel()==call.connection.chan nel){ 38.calls.add(call); 39.} 40.} 41.} 42. 43.forforforfor(Callcall:calls){ 44.trytrytrytry{ 45.doPurge(call,now);//执行清除 46.}catchcatchcatchcatch(IOExceptione){ 47.LOG.warn("Errorinpurgingoldcalls"+e); 48.} 49.} 50.}catchcatchcatchcatch(OutOfMemoryErrore){ 51.LOG.warn("OutofMemoryinserverselect",e); 52.trytrytrytry{ 53.Thread.sleep(60000); 54.}catchcatchcatchcatch(Exceptionie){ 55.} 56.}catchcatchcatchcatch(Exceptione){ 57.LOG.warn("ExceptioninResponder"+StringUtils.stringifyExcept ion(e)); 58.} 59.} 60.LOG.info("Stopping"+thisthisthisthis.getName()); 61.} 通 过线 程执 行可 以看 到 ,调 用的 相应 数据 的处 理 ,是 在服 务器 运行 过程 中处 理的 ,而 且分 为 两 种情 况: 1、 一种 情况 是: 如果 某些 调用 超过 了指 定的 时限 而一 直未 被处 理, 这些 调用 被视 为过 期 , 服 务器 不会 再为 这些 调用 处理 ,而 是直 接清 除掉 ; 2、另 一种 情况 是 :如 果所 选择 的通 道上 ,已 经注 册的 调用 是合 法的 ,并 且通 道可 写 ,会直 接 将调 用的 相应 数据 写入 到通 道, 等待 客户 端读 取。 上 面实 现的 Server 的 内部 类, 基本 上定 义了 一个 Server 应 该实 现的 基本 操作 ,下 面再 看 Server 类 中就 比较 容易 了。 启 动服 务器 : view plain 1.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidstart()throwsthrowsthrowsthrowsIOException{ 2.responder.start();//启动调用的响应数据处理线程 3.listener.start();//启动监听线程 4.handlers=newnewnewnewHandler[handlerCount];//启动多个处理器线程 5.forforforfor(intintintinti=0;iclients=newnewnewnewHashMap(); 通 过客 户端 org.apache.hadoop.ipc.Client 的SocketFactory 可 以快 速取 出对 应的 Client 实 例。 该 内部 类中 实现 了获 取一 个 Client 的 方法 : view plain 1./** 2.*从缓存 Map中取出一个 IPCClient实例,如果缓存够中不存在,就创建一个兵加入到缓 存 Map中 3.*/ 4.privateprivateprivateprivatesynchronizedsynchronizedsynchronizedsynchronizedClientgetClient(Configurationconf,SocketFactoryfact ory){ 5.Clientclient=clients.get(factory); 6.ifififif(client==nullnullnullnull){ 7.client=newnewnewnewClient(ObjectWritable.classclassclassclass,conf,factory);//通过反射实例 化一个 ObjectWritable对象,构造 Client实例 8.clients.put(factory,client);//加入缓存 Map 9.}elseelseelseelse{ 10.client.incCount();//增加客户端 client实例的引用计数 11.} 12.returnreturnreturnreturnclient; 13.} 终 止一 个 RPC 客 户端 连接 ,实 现方 法为 : view plain 1.privateprivateprivateprivatevoidvoidvoidvoidstopClient(Clientclient){ 2.synchronizedsynchronizedsynchronizedsynchronized(thisthisthisthis){ 3.client.decCount();//该client实例的引用计数减 1 4.ifififif(client.isZeroReference()){//如果 client实例的引用计数此时为 0 5.clients.remove(client.getSocketFactory());//从缓存中删除 6.} 7.} 8.ifififif(client.isZeroReference()){//如果 client实例引用计数为 0,需要关闭 9.client.stop();//停止所有与该 client实例相关的线程 10.} 11.} • RPC.VersionMismatch 内 部类 该 内部 类主 要是 用来 :当 检测 到版 本与 RPC 协 议不 匹配 的时 候 ,作 为异 常来 处理 信息 的类 。 • RPC.Invoker 内 部类 该 内部 类实 现了 java.lang.reflect.InvocationHandler 接口,是 一个 代理 实例 的调 用处 理程 序 实 现类 。 该 内部 类实 现如 下所 示: view plain 1.privateprivateprivateprivatestaticstaticstaticstaticclassclassclassclassInvokerimplementsimplementsimplementsimplementsInvocationHandler{ 2. 3.privateprivateprivateprivateInetSocketAddressaddress;//远程服务器地址 4.privateprivateprivateprivateUserGroupInformationticket;//客户端用户身份信息 5.privateprivateprivateprivateClientclient;//客户端实例 6.privateprivateprivateprivatebooleanbooleanbooleanbooleanisClosed=falsefalsefalsefalse;//客户端是否关闭 7. 8.publicpublicpublicpublicInvoker(InetSocketAddressaddress,UserGroupInformationticket,Con figurationconf,SocketFactoryfactory){ 9.thisthisthisthis.address=address; 10.thisthisthisthis.ticket=ticket; 11.thisthisthisthis.client=CLIENTS.getClient(conf,factory); 12.} 13. 14./** 15.*StopaRPCclientconnection 16.*@paramproxy代理实例 17.*@parammethod某个类的方法实例 18.*@paramargs方法参数 19.*/ 20.publicpublicpublicpublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsthrowsthrowsthrowsTh rowable{ 21.finalfinalfinalfinalbooleanbooleanbooleanbooleanlogDebug=LOG.isDebugEnabled(); 22.longlonglonglongstartTime=0; 23.ifififif(logDebug){ 24.startTime=System.currentTimeMillis(); 25.} 26.//构造一 个RPC.Invocation实例作为参数传递给调用程序 ,执行调用 ,返回值 为value 27.ObjectWritablevalue=(ObjectWritable)client.call(newnewnewnewInvocation(method, args),address,method.getDeclaringClass(),ticket); 28.ifififif(logDebug){ 29.longlonglonglongcallTime=System.currentTimeMillis()-startTime; 30.LOG.debug("Call:"+method.getName()+""+callTime); 31.} 32.returnreturnreturnreturnvalue.get();//返回调用处理结果( value的实例) 33.} 34. 35./*关闭 client*/ 36.synchronizedsynchronizedsynchronizedsynchronizedprivateprivateprivateprivatevoidvoidvoidvoidclose(){ 37.ifififif(!isClosed){ 38.isClosed=truetruetruetrue; 39.CLIENTS.stopClient(client); 40.} 41.} 42.} 该 内部 类出 列了 客户 端调 用, 并返 回调 用处 理结 果。 RPC.Server 内 部类 该 内部 类是 RPC 最 核心 的, 它继 承自 org.apache.hadoop.ipc.Server 抽 象类 ,实 现的 最核 心 的是 调用 处理 方法 : view plain 1.publicpublicpublicpublicWritablecall(Classprotocol,Writableparam,longlonglonglongreceivedTime)tttt hrowshrowshrowshrowsIOException{ 2.trytrytrytry{ 3.Invocationcall=(Invocation)param; 4.ifififif(verbose)log("Call:"+call); 5. 6.Methodmethod=protocol.getMethod(call.getMethodName(),call.getParamet erClasses());//通过反射,根据调用方法名和方法参数类型得到 Method实例 7.method.setAccessible(truetruetruetrue);//设置反射的对象在使用时取消 Java语言访问检查, 提高效率 8. 9.longlonglonglongstartTime=System.currentTimeMillis(); 10.Objectvalue=method.invoke(instance,call.getParameters());//执行调用 (instance是调用底层方法的对象,第二个参数是方法调用的参数) 11.intintintintprocessingTime=(intintintint)(System.currentTimeMillis()-startTime); 12.intintintintqTime=(intintintint)(startTime-receivedTime); 13.ifififif(LOG.isDebugEnabled()){ 14.LOG.debug("Served:"+call.getMethodName()+"queueTime="+qTime +"procesingTime="+processingTime); 15.} 16.rpcMetrics.rpcQueueTime.inc(qTime); 17.rpcMetrics.rpcProcessingTime.inc(processingTime); 18. 19.MetricsTimeVaryingRatem=(MetricsTimeVaryingRate)rpcMetrics.registry. get(call.getMethodName()); 20.ifififif(m==nullnullnullnull){ 21.trytrytrytry{ 22.m=newnewnewnewMetricsTimeVaryingRate(call.getMethodName(), 23. rpcMetrics.registry); 24.}catchcatchcatchcatch(IllegalArgumentExceptioniae){ 25.//themetricshasbeenregistered;re-fetchthehandle 26.LOG.info("Errorregister"+call.getMethodName(),iae); 27.m=(MetricsTimeVaryingRate)rpcMetrics.registry.get(call.getMethodN ame()); 28.} 29.} 30.m.inc(processingTime); 31. 32.ifififif(verbose)log("Return:"+value); 33. 34.returnreturnreturnreturnnewnewnewnewObjectWritable(method.getReturnType(),value);//返回:调用的 返回值对象 35. 36.}catchcatchcatchcatch(InvocationTargetExceptione){ 37.Throwabletarget=e.getTargetException(); 38.ifififif(targetinstanceofinstanceofinstanceofinstanceofIOException){ 39.throwthrowthrowthrow(IOException)target; 40.}elseelseelseelse{ 41.IOExceptionioe=newnewnewnewIOException(target.toString()); 42.ioe.setStackTrace(target.getStackTrace()); 43.throwthrowthrowthrowioe; 44.} 45.}catchcatchcatchcatch(Throwablee){ 46.IOExceptionioe=newnewnewnewIOException(e.toString()); 47.ioe.setStackTrace(e.getStackTrace()); 48.throwthrowthrowthrowioe; 49.} 50.} 还 有一 个方 法实 现了 对用 户的 授权 ,这 在 org.apache.hadoop.ipc.Server 抽 象类 中并 没实 现 ,如 下所 示: view plain 1.@Override 2.publicpublicpublicpublicvoidvoidvoidvoidauthorize(Subjectuser,ConnectionHeaderconnection) 3.throwsthrowsthrowsthrowsAuthorizationException{ 4.ifififif(authorize){//authorize默认为 false,除非在 Configuration配置类实例中获 取到的为 true。可见,该简单的 RPC默认不需要对用户进行授权操作 5.Classprotocol=nullnullnullnull; 6.trytrytrytry{ 7.protocol=getProtocolClass(connection.getProtocol(),getConf()); 8.}catchcatchcatchcatch(ClassNotFoundExceptioncfne){ 9.throwthrowthrowthrownewnewnewnewAuthorizationException("Unknownprotocol:"+connection.get Protocol()); 10.} 11.ServiceAuthorizationManager.authorize(user,protocol);//执行授权操作,使 得用户可以访问被使用的 Protocol 12.} 13.} 上 面, 已经 对 RPC 类 的内 部类 的实 现进 行了 阅读 分析 ,现 在看 RPC 类 提供 的操 作。 获 取到 一个 到远 程服 务器 的代 理: view plain 1./** 2.*获取到一个到远程服务器的代理连接 3.*@paramprotocol协议类 4.*@paramclientVersion客户端版本 5.*@paramaddr远程地址 6.*@paramconf使用配置类实例 7.*@paramtimeout超时时间 8.*@return返回代理 9.*/ 10.staticstaticstaticstaticVersionedProtocolwaitForProxy(Classprotocol, 11. longlonglonglongclientVersion, 12. InetSocketAddressaddr, 13. Configurationconf, 14. longlonglonglongtimeout 15. )throwsthrowsthrowsthrowsIOException; 另 外, 还有 几个 获取 代理 的 getProxy 方 法: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticVersionedProtocolgetProxy(Classprotocol,longlonglonglongclientVersi on,InetSocketAddressaddr,Configurationconf)throwsthrowsthrowsthrowsIOException; 2.publicpublicpublicpublicstaticstaticstaticstaticVersionedProtocolgetProxy(Classprotocol,longlonglonglongclientVersi on,InetSocketAddressaddr,Configurationconf,SocketFactoryfactory)throwthrowthrowthrow ssssIOException; 3.publicpublicpublicpublicstaticstaticstaticstaticVersionedProtocolgetProxy(Classprotocol,longlonglonglongclientVersi on,InetSocketAddressaddr,UserGroupInformationticket,Configurationconf, SocketFactoryfactory)throwsthrowsthrowsthrowsIOException; 下 面是 RPC 服 务器 处理 调用 的过 程实 现: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticObject[]call(Methodmethod,Object[][]params, 2. InetSocketAddress[]addrs, 3. UserGroupInformationticket,Configurationconf) 4.throwsthrowsthrowsthrowsIOException{ 5. 6.Invocation[]invocations=newnewnewnewInvocation[params.length];//一组方法调用实 例 7.forforforfor(intintintinti=0;i 4.*TheNameNodesetsreplicationtothenewvalueandreturns. 5.*Theactualblockreplicationisnotexpectedtobeperformedduring 6.*thismethodcall.Theblockswillbepopulatedorremovedinthe 7.*backgroundastheresultoftheroutineblockmaintenanceprocedures. 8.* 9.*@paramsrc文件名称 10.*@paramreplication新的副本因子 11.*/ 12.publicpublicpublicpublicbooleanbooleanbooleanbooleansetReplication(Stringsrc,shortshortshortshortreplication)throwsthrowsthrowsthrowsIOExcept ion; setReplication 方 法的 主要 功能 说明 如下 : 该 方法 为一 个指 定的 文件 修改 块副 本因 子 。Namenode 会 为指 定文 件设 置副 本因 子 ,但是, 不 期望 在调 用该 方法 的过 程修 改实 际块 的副 本因 子 ,而 是由 后台 块维 护进 程来 执行 :如 果当 前 副本 因子 小于 设置 的新 副本 因子 ,需 要增 加一 些块 副本 ,如 果当 前副 本因 子大 于设 置的 新 副 本因 子, 就会 删除 一些 块副 本。 另 外, 基于 文件 操作 的方 法, 还有 如下 一些 : view plain 1./** 2.*为已经存在的目录或者文件,设置给定的操作权限 3.*/ 4.publicpublicpublicpublicvoidvoidvoidvoidsetPermission(Stringsrc,FsPermissionpermission)throwsthrowsthrowsthrowsIOExce ption; 5. 6./** 7.*设置文件或目录属主 8.*/ 9.publicpublicpublicpublicvoidvoidvoidvoidsetOwner(Stringsrc,Stringusername,Stringgroupname)throwsthrowsthrowsthrowsI OException; 10. 11./** 12.*客户端放弃对指定块的操作 13.*/ 14.publicpublicpublicpublicvoidvoidvoidvoidabandonBlock(Blockb,Stringsrc,Stringholder)throwsthrowsthrowsthrowsIOExcept ion; 15. 16./** 17.*客户端向一个当前为写操作打开的文件写入数据块 18.*/ 19.publicpublicpublicpublicLocatedBlockaddBlock(Stringsrc,StringclientName)throwsthrowsthrowsthrowsIOExcepti on; 20. 21./** 22.*客户端完成对指定文件的写操作,并期望能够写完,在写完以后关闭文件 23.*/ 24.publicpublicpublicpublicbooleanbooleanbooleanbooleancomplete(Stringsrc,StringclientName)throwsthrowsthrowsthrowsIOException; 25. 26./** 27.*客户端向 Namenode报告 corrupted块的信息 (块在 Datanode上的位置信息 ) 28.*@paramblocks待报告的块信息的数组 29.*/ 30.publicpublicpublicpublicvoidvoidvoidvoidreportBadBlocks(LocatedBlock[]blocks)throwsthrowsthrowsthrowsIOException; 31. 32./** 33.*在文件系统命令空间中重命名一个文件或目录 34.*/ 35.publicpublicpublicpublicbooleanbooleanbooleanbooleanrename(Stringsrc,Stringdst)throwsthrowsthrowsthrowsIOException; 36. 37./** 38.*删除文件或目录 src 39.*/ 40.publicpublicpublicpublicbooleanbooleanbooleanbooleandelete(Stringsrc)throwsthrowsthrowsthrowsIOException; 41. 42./** 43.*删除文件或目录 src,根据 recursive选项来执行 44.*/ 45.publicpublicpublicpublicbooleanbooleanbooleanbooleandelete(Stringsrc,booleanbooleanbooleanbooleanrecursive)throwsthrowsthrowsthrowsIOException; 46. 47./** 48.*创建目录 src,并赋予目录 src指定的 nasked权限 49.*/ 50.publicpublicpublicpublicbooleanbooleanbooleanbooleanmkdirs(Stringsrc,FsPermissionmasked)throwsthrowsthrowsthrowsIOException; 51. 52./** 53.*获取指定目录 src中的文件列表 54.*/ 55.publicpublicpublicpublicFileStatus[]getListing(Stringsrc)throwsthrowsthrowsthrowsIOException; 下 面是 接口 ClientProtocol 定 义的 与系 统的 管理 相关 的方 法的 定义 : 1、 监听 客户 端 方法renewLease 定 义为 : view plain 1.publicpublicpublicpublicvoidvoidvoidvoidrenewLease(StringclientName)throwsthrowsthrowsthrowsIOException; 该 方法 主要 是 Namenode 监 听到 某个 客户 端发 送的 心跳 状态 ,如 果再 一段 时间 内无 法获 取 到 某个 客户 端的 心跳 状态 ,很 可能 是该 客户 端因 为某 些异 常崩 溃掉 了 ,被 加上 了不 能继 续正 常 工作 的状 态锁 ,Namenode 进 程会 周期 地调 用该 方法 来确 定指 定的 客户 端 clientName 是 否 确实 挂掉 了, 如果 又重 新接 收到 该客 户端 发送 的心 跳报 告, 则为 该客 户端 进行 解锁 操作 , 恢 复其 正常 的工 作。 2、 获取 文件 系统 的状 态统 计数 据 定 义的 方法 为 getStats, 如下 所示 : view plain 1./** 2.*返回一组标识文件系统不同信息的索引数组: 3.*
    4.*
  • [0]包含文件系统总存储容量 (按字节计算 )
  • 5.*
  • [1]包含文件系统已使用空间 (按字节计算 )
  • 6.*
  • [2]包含文件系统可使用空间 (按字节计算 )
  • 7.*
  • [3]包含文件系统中不满足副本因子数量的块的数量
  • 8.*
  • [4]包含 corrupt副本的块的数量
  • 9.*
  • [5]包含没有任何可以用的块副本的块的数量
  • 10.*
11.*/ 12.publicpublicpublicpubliclonglonglonglong[]getStats()throwsthrowsthrowsthrowsIOException; 上 面返 回的 long[]对 应定 义的 如下 几个 常量 : view plain 1.publicpublicpublicpublicintintintintGET_STATS_CAPACITY_IDX=0; 2.publicpublicpublicpublicintintintintGET_STATS_USED_IDX=1; 3.publicpublicpublicpublicintintintintGET_STATS_REMAINING_IDX=2; 4.publicpublicpublicpublicintintintintGET_STATS_UNDER_REPLICATED_IDX=3; 5.publicpublicpublicpublicintintintintGET_STATS_CORRUPT_BLOCKS_IDX=4; 6.publicpublicpublicpublicintintintintGET_STATS_MISSING_BLOCKS_IDX=5; 3、 安全 模式 开关 操作 定 义的 方法 为 setSafeMode, 如下 所示 : view plain 1.publicpublicpublicpublicbooleanbooleanbooleanbooleansetSafeMode(FSConstants.SafeModeActionaction)throwsthrowsthrowsthrowsIOExcep tion; 通 过调 用该 方法 可以 执行 如下 操作 :进 入安 全模 式、 退出 安全 模式 、获 取安 全模 式。 当 一个 Namenode 启 动时 候, 首先 会进 入到 安全 模式 这种 特殊 状态 ,在 该状 态下 不能 够进 行 数据 块的 复制 操作 。 Namenode 接收HDFS 集 群中 所有 的 Datanode 结 点的 心跳 状态 报 告 和数 据块 状态 报告 ,根 据状 态报 告来 决定 是否 开始 进入 工作 状态 。如 果某 些 Datanode 结 点 发送 的心 跳状 态报 告不 正常 或者 根本 无从 接收 到 ,Namenode 会 将这 些 Datanode 视 为故 障 结点 ,在 进入 工作 状态 的时 候 ,将 这些 故障 结点 排除 在工 作集 群之 外 。如 果某 些 Datanode 结 点上 的数 据块 状态 报告 存在 问题 ,会 根据 要求 进行 处理 ,比 如某 Datanode 据 结点 上数 据 块 的块 副本 未达 到副 本因 子, 则会 在退 出安 全模 式之 后, 进行 块复 制操 作, 满足 副本 要求 。 4、 保存 命名 空间 映像 定 义的 方法 为 saveNamespace, 如下 所示 : view plain 1./** 2.*保存命名空间映像 3.* 4.*保存 FsImage映像,同时将更新同步到 EditLog中。 5.*要求具有超级权限,并且在安全模式下进行。 6.*/ 7.publicpublicpublicpublicvoidvoidvoidvoidsaveNamespace()throwsthrowsthrowsthrowsIOException; 5、 持久 化文 件系 统元 数据 定 义的 方法 为 metaSave, 如下 所示 : view plain 1./** 2.*将Namenode结点上的数据结构写入到指定的文件中 ,如果指定文件已经存在 ,则追加到该文 件中 3.*/ 4.publicpublicpublicpublicvoidvoidvoidvoidmetaSave(Stringfilename)throwsthrowsthrowsthrowsIOException; 这 里, 引用 Hadoop 架 构设 计要 点中 的一 段文 字来 描述 一下 ,文 件系 统元 数据 的持 久化 : “Namenode 上 保存 着 HDFS 的 名字 空间 。对 于任 何对 文件 系统 元数 据产 生修 改的 操作 , Namenode 都 会使 用一 种称 为 EditLog 的 事务 日志 记录 下来 。例 如, 在 HDFS 中 创建 一个 文件,Namenode 就 会在 Editlog 中 插入 一条 记录 来表 示 ;同 样地 ,修 改文 件的 副本 系数 也 将往Editlog 插 入一 条记 录。 Namenode 在 本地 操作 系统 的文 件系 统中 存储 这个 Editlog。整 个 文件 系统 的名 字空 间, 包括 数据 块到 文件 的映 射、 文件 的属 性等 ,都 存储 在一 个称 为 FsImage 的 文件 中, 这个 文件 也是 放在 Namenode 所 在的 本地 文件 系统 上。 Namenode 在 内存 中保 存着 整个 文件 系统 的名 字空 间和 文件 数据 块映 射 (Blockmap)的映 像 。这 个关 键的 元数 据结 构设 计得 很紧 凑, 因而 一个 有 4G 内 存的 Namenode 足 够支 撑大 量 的文 件和 目录 。当Namenode 启 动时 ,它 从硬 盘中 读 取Editlog 和FsImage,将所有Editlog 中 的事 务作 用在 内存 中的 FsImage 上,并 将这 个新 版本 的 FsImage 从 内存 中保 存到 本地 磁 盘 上, 然后 删除 旧的 Editlog, 因为 这个 旧的 Editlog 的 事务 都已 经作 用在 FsImage 上 了。 这 个过 程称 为一 个检 查点 (checkpoint)。在 当前 实现 中 ,检 查点 只发 生在 Namenode 启 动时 , 在 不久 的将 来将 实现 支持 周期 性的 检查 点。 Datanode 将HDFS 数 据以 文件 的形 式存 储在 本地 的文 件系 统中 ,它 并不 知道 有关 HDFS 文 件的 信息 。它 把每 个 HDFS 数 据块 存储 在本 地文 件系 统的 一个 单独 的文 件中 。Datanode 并 不在 同一 个目 录创 建所 有的 文件 ,实 际上 ,它 用试 探的 方法 来确 定每 个目 录的 最佳 文件 数 目,并 且在 适当 的时 候创 建子 目录 。在 同一 个目 录中 创建 所有 的本 地文 件并 不是 最优 的选 择 , 这 是因 为本 地文 件系 统可 能无 法高 效地 在单 个目 录中 支持 大量 的文 件 。当 一个 Datanode 启 动 时, 它会 扫描 本地 文件 系统 ,产 生一 个这 些本 地文 件对 应的 所有 HDFS 数 据块 的列 表, 然 后作 为报 告发 送到 Namenode, 这个 报告 就是 块状 态报 告。 ” 另 外, ClientProtocol 协 议接 口还 定义 了其 它一 些方 法, 如下 所示 : view plain 1./** 2.*获取集群系统中当前的 Datanode的状态报告 3.*每个 Datanode返回一个 DatanodeInfo对象 ,包括 Datanode的类型 LIVE、DEAD或ALL. 4.*/ 5.publicpublicpublicpublicDatanodeInfo[]getDatanodeReport(FSConstants.DatanodeReportTypetype) throwsthrowsthrowsthrowsIOException; 6. 7./** 8.*获取指定文件的块大小 9.*/ 10.publicpublicpublicpubliclonglonglonglonggetPreferredBlockSize(Stringfilename)throwsthrowsthrowsthrowsIOException; 11. 12./** 13.*告诉 Namenode重新读取集群结点列表 14.*/ 15.publicpublicpublicpublicvoidvoidvoidvoidrefreshNodes()throwsthrowsthrowsthrowsIOException; 16. 17./** 18.*完成之前的升级操作 19.*删除在升级期间保存的文件系统的状态,一旦执行该方法,改变将不可逆转。 20.*/ 21.publicpublicpublicpublicvoidvoidvoidvoidfinalizeUpgrade()throwsthrowsthrowsthrowsIOException; 22. 23./** 24.*报告分布式升级进程,或强制使当前升级执行 25.*/ 26.publicpublicpublicpublicUpgradeStatusReportdistributedUpgradeProgress(UpgradeActionaction) throwsthrowsthrowsthrowsIOException; 27. 28./** 29.*获取指定文件或目录的状态 30.*/ 31.publicpublicpublicpublicFileStatusgetFileInfo(Stringsrc)throwsthrowsthrowsthrowsIOException; 32. 33./** 34.*获取某个目录的 ContentSummary信息 35.*/ 36.publicpublicpublicpublicContentSummarygetContentSummary(Stringpath)throwsthrowsthrowsthrowsIOException; 37. 38./** 39.*将指定文件的全部元数据写入到持久存储中 40.*其中,必须保证文件为写操作而打开。 41.*/ 42.publicpublicpublicpublicvoidvoidvoidvoidfsync(Stringsrc,Stringclient)throwsthrowsthrowsthrowsIOException; 43. 44./** 45.*设置指定文件的修改和访问时间 46.*/ 47.publicpublicpublicpublicvoidvoidvoidvoidsetTimes(Stringsrc,longlonglonglongmtime,longlonglonglongatime)throwsthrowsthrowsthrowsIOException; 48. 49./** 50.*为指定目录设置分配配额信息 51.*/ 52.publicpublicpublicpublicvoidvoidvoidvoidsetQuota(Stringpath,longlonglonglongnamespaceQuota,longlonglonglongdiskspaceQuota) throwsthrowsthrowsthrowsIOException; • NamenodeProtocol NamenodeProtocol NamenodeProtocol NamenodeProtocol 协议 该 协议 接口 定义 了次 级 Namenode(Secondary NameNode)与Namenode 进 行通 信所 需 进 行的 操作 。其 中, Secondary NameNode 是 一个 用来 辅助 Namenode 的 服务 器端 进程 , 主 要是 对映 像文 件执 行特 定的 操作 ,另 外还 包括 获取 指定 Datanode 上 块的 操作 。 该 接口 定义 的操 作如 下所 示: view plain 1./** 2.*获取 datanode上大小为 size的块 3.*/ 4.publicpublicpublicpublicBlocksWithLocationsgetBlocks(DatanodeInfodatanode,longlonglonglongsize)throwthrowthrowthrow ssssIOException; 5. 6./** 7.*获取当前 EditLog文件的大小 (inbytes) 8.*/ 9.publicpublicpublicpubliclonglonglonglonggetEditLogSize()throwsthrowsthrowsthrowsIOException; 10. 11./** 12.*关闭当前 EditLog文件,并重新打开一个新的 13.*当系统处于安全模式下,执行该方法会失败 14.*/ 15.publicpublicpublicpublicCheckpointSignaturerollEditLog()throwsthrowsthrowsthrowsIOException; 16. 17./** 18.*回滚 FsImage日志 :删除旧的 FsImage,拷贝新的映像到 FsImage文件中 ,删除旧的 EditLog 文件并重命名 edits.new为edits。 19.*/ 20.publicpublicpublicpublicvoidvoidvoidvoidrollFsImage()throwsthrowsthrowsthrowsIOException; • ClientDatanodeProtocol ClientDatanodeProtocol ClientDatanodeProtocol ClientDatanodeProtocol 协议 当 客户 端进 程需 要与 Datanode 进 程进 行通 信的 时候 ,需 要基 于该 协议 。该 协议 接口 定义 数 据 块恢 复的 方法 ,如 下所 示: view plain 1.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceClientDatanodeProtocolextendsextendsextendsextendsVersionedProtocol{ 2.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalLogLOG=LogFactory.getLog(ClientDatanodeProtocol.claclaclacla ssssssss); 3. 4./** 5.*3:addkeepLengthparameter. 6.*/ 7.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinallonglonglonglongversionID=3L; 8. 9./** 10.*@paramblock指定的数据块 11.*@paramkeepLength保持数据块的长度 12.*@paramtargets指定的块的可能位置列表 13.*@return如果恢复成功,返回块 ID和时间戳 14.*/ 15.LocatedBlockrecoverBlock(Blockblock,booleanbooleanbooleanbooleankeepLength,DatanodeInfo[] targets)throwsthrowsthrowsthrowsIOException; 16.} • DatanodeProtocol DatanodeProtocol DatanodeProtocol DatanodeProtocol 协议 该 协议 用于 一个 DFS Datanode 用 户与 Namenode 进 行通 信的 协议 。该 接口 定义 如下 所示 : view plain 1.packagepackagepackagepackageorg.apache.hadoop.hdfs.server.protocol; 2. 3.importimportimportimportjava.io.*; 4. 5.importimportimportimportorg.apache.hadoop.hdfs.protocol.Block; 6.importimportimportimportorg.apache.hadoop.hdfs.protocol.DatanodeID; 7.importimportimportimportorg.apache.hadoop.hdfs.protocol.LocatedBlock; 8.importimportimportimportorg.apache.hadoop.ipc.VersionedProtocol; 9. 10.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceDatanodeProtocolextendsextendsextendsextendsVersionedProtocol{ 11./** 12.*19:发送心跳,返回一个 DatanodeCommand对象数组 13.*/ 14.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinallonglonglonglongversionID=19L; 15. 16.//定义错误代码 17.finalfinalfinalfinalstaticstaticstaticstaticintintintintNOTIFY=0; 18.finalfinalfinalfinalstaticstaticstaticstaticintintintintDISK_ERROR=1; 19.finalfinalfinalfinalstaticstaticstaticstaticintintintintINVALID_BLOCK=2; 20. 21./** 22.*当接收到 Datanode的命令的时候 ,根据下述状态码确定 Datanode应该执行何种操作 , 23.*/ 24.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_UNKNOWN=0;//未知 25.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_TRANSFER=1;//将数据块从一个 Datanode转移到另一 个 Datanode 26.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_INVALIDATE=2;//未验证数据块 27.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_SHUTDOWN=3;//关闭 Datanode 28.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_REGISTER=4;//重新注册 29.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_FINALIZE=5;//完成先前执行的升级操作 30.finalfinalfinalfinalstaticstaticstaticstaticintintintintDNA_RECOVERBLOCK=6;//数据块恢复操作请求 31. 32./** 33.*注册 Datanode 34.* 35.*@return更新后的 {@linkorg.apache.hadoop.hdfs.server.protocol.DatanodeRegistration},包含:一 个新的 storageID(如果 Datanode没有 storageID)、新的 registrationID 36.*/ 37.publicpublicpublicpublicDatanodeRegistrationregister(DatanodeRegistrationregistration)thththth rowsrowsrowsrowsIOException; 38./** 39.*Datanode向Namenode发送心跳状态报告 40.*/ 41.publicpublicpublicpublicDatanodeCommand[]sendHeartbeat(DatanodeRegistrationregistration, longlonglonglongcapacity,longlonglonglongdfsUsed,longlonglonglongremaining,intintintintxmitsInProgress,intintintintxceive rCount)throwsthrowsthrowsthrowsIOException; 42. 43./** 44.*Datanode向Namenode发送块状态报告 45.*/ 46.publicpublicpublicpublicDatanodeCommandblockReport(DatanodeRegistrationregistration,longlonglonglong []blocks)throwsthrowsthrowsthrowsIOException; 47. 48./** 49.*Datanode向Namenode报告最近接收到的数据块、删除的多余块副本 50.*/ 51.publicpublicpublicpublicvoidvoidvoidvoidblockReceived(DatanodeRegistrationregistration,Blockblocks[] ,String[]delHints)throwsthrowsthrowsthrowsIOException; 52. 53./** 54.*向Namenode报告错误信息 55.*/ 56.publicpublicpublicpublicvoidvoidvoidvoiderrorReport(DatanodeRegistrationregistration,intintintinterrorCode, Stringmsg)throwsthrowsthrowsthrowsIOException; 57. 58.publicpublicpublicpublicNamespaceInfoversionRequest()throwsthrowsthrowsthrowsIOException; 59. 60./** 61.*Datanode向Namenode发送一个升级命令 62.*/ 63.UpgradeCommandprocessUpgradeCommand(UpgradeCommandcomm)throwsthrowsthrowsthrowsIOExcepti on; 64. 65./** 66.*Datanode向Namenode报告 BadBlocks 67.*/ 68.publicpublicpublicpublicvoidvoidvoidvoidreportBadBlocks(LocatedBlock[]blocks)throwsthrowsthrowsthrowsIOException; 69. 70./** 71.*为数据块生成新的时间戳 72.*/ 73.publicpublicpublicpubliclonglonglonglongnextGenerationStamp(Blockblock)throwsthrowsthrowsthrowsIOException; 74. 75./** 76.*在恢复数据块期间,提交事务:数据块同步 77.*/ 78.publicpublicpublicpublicvoidvoidvoidvoidcommitBlockSynchronization(Blockblock,longlonglonglongnewgenerationstam p,longlonglonglongnewlength,booleanbooleanbooleanbooleancloseFile,booleanbooleanbooleanbooleandeleteblock,DatanodeID[]newt argets)throwsthrowsthrowsthrowsIOException; 79.} 一 般来 说 Namenode 不 直接 对 Datanode 进行RPC 调 用, 如果 一个 Namenode 需要与 Datanode 通 信, 唯一 的方 式就 通过 调用 该协 议接 口定 义的 方法 。 • InterDatanodeProtocol InterDatanodeProtocol InterDatanodeProtocol InterDatanodeProtocol 协议 该 协议 用于 Datanode 进 程之 间进 行通 信。 该 接口 的定 义如 下所 示: view plain 1.packagepackagepackagepackageorg.apache.hadoop.hdfs.server.protocol; 2. 3.importimportimportimportjava.io.IOException; 4. 5.importimportimportimportorg.apache.commons.logging.Log; 6.importimportimportimportorg.apache.commons.logging.LogFactory; 7.importimportimportimportorg.apache.hadoop.hdfs.protocol.Block; 8.importimportimportimportorg.apache.hadoop.ipc.VersionedProtocol; 9. 10.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceInterDatanodeProtocolextendsextendsextendsextendsVersionedProtocol{ 11.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalLogLOG=LogFactory.getLog(InterDatanodeProtocol.clasclasclasclas ssss); 12. 13./** 14.*3:向更新块中增加一个表示完成的参数 15.*/ 16.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinallonglonglonglongversionID=3L; 17. 18./** 19.*获取指定块的元数据 20.*/ 21.BlockMetaDataInfogetBlockMetaDataInfo(Blockblock)throwsthrowsthrowsthrowsIOException; 22. 23./** 24.*更新数据块 25.*/ 26.voidvoidvoidvoidupdateBlock(Blockoldblock,Blocknewblock,booleanbooleanbooleanbooleanfinalize)throwsthrowsthrowsthrows IOException; 27.} 上面,对 不同 进程 之间 通信 所使 用的 协议 的接 口进 行了 阅读 分析 ,应 该能 够了 解每 种协 议应 用 的场 景, 如果 想要 基于 某种 场景 实现 端端 通信 ,可 以选 择合 适的 协议 接口 来实 现它 。 Hadoop 集 群中 ,不 同进 程之 间通 信需 要使 用合 适的 协议 才能 够进 行交 互, 之前 对 Hadoop 给 出的 协议 接口 做了 分析 。在 协议 接口 中约 定了 通信 双方 的特 定行 为 ,那么,在 实现 这些 通 信 协议 的实 现类 中 ,就 能看 到指 定进 程是 如何 实现 协议 接口 中约 定的 行为 的 。这里,阅 读分 析org.apache.hadoop.hdfs.server.namenode.Namenode 实 现类 。 首 先, 看一 下 Namenode 类 实现 的接 口, 下面 是该 类声 明: view plain 1.publicpublicpublicpublicclassclassclassclassNameNodeimplementsimplementsimplementsimplementsClientProtocol,DatanodeProtocol,NamenodeP rotocol,FSConstants,RefreshAuthorizationPolicyProtocol; 可 以看 到 ,Namenode 主 要实 现了 ClientProtocol,DatanodeProtocol,NamenodeProtocol 这 三个 用来 通信 的协 议 。其中,RefreshAuthorizationPolicyProtocol 接 口是 定义 所使 用的 认 证 策略 ,并 能根 据不 同的 应用 场景 来自 动刷 新其 级别 ,以 适应 实际 应用 的需 要。 Namenode 是HDFS 集 群中 的中 心服 务器 ,对 于服 务器 的配 置选 项, 可以 通过 加载 配置 文 件 来进 行配 置, 所以 该类 中有 如下 加载 配置 资源 的两 行代 码: view plain 1.staticstaticstaticstatic{ 2.Configuration.addDefaultResource("hdfs-default.xml");//默认配置文件 3.Configuration.addDefaultResource("hdfs-site.xml");//用户配置文件 4.} Namenode 类 中定 义属 性如 下: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalintintintintDEFAULT_PORT=8020;//默认端口号 2.publicpublicpublicpublicFSNamesystemnamesystem;//用来维护与 Datanode相关的重要列 表 3.privateprivateprivateprivateServerserver;//RPC服务器 4.privateprivateprivateprivateInetSocketAddressserverAddress=nullnullnullnull;//RPC服务器地址 5.privateprivateprivateprivateHttpServerhttpServer;//内嵌的 HTTP服务器 ,用来应答到来 的 HTTP请求 6.privateprivateprivateprivateInetSocketAddresshttpAddress=nullnullnullnull;//HTTP服务器地址 7.privateprivateprivateprivateThreademptier;//回收站清理线程 8.privateprivateprivateprivatebooleanbooleanbooleanbooleanstopRequested=falsefalsefalsefalse;//是否阻止请求 9.privateprivateprivateprivatebooleanbooleanbooleanbooleanserviceAuthEnabled=falsefalsefalsefalse;//是否启动服务认证级别 10. 11.staticstaticstaticstaticNameNodeMetricsmyMetrics;//用来维护 Namenode服务器活动状况 的统计数据实体 对 上面 几个 重要 的属 性简 单说 明一 下。 FSNamesystem 也是org.apache.hadoop.hdfs.server.namenode 包 中的 类 ,在Namenode 类 中是 核心 的、 最重 要的 。该 类主 要的 功能 是对 Datanode 结 点的 一些 状态 等进 行登 记 ,便 于Namenode 进 程能 够快 速获 取到 指定 的 Datanode 结 点的 状态 等的 详细 信息 ,以 便进 行 任 务的 调度 与分 配。 它主 要跟 踪如 下几 个重 要的 数据 表: 1) 文件 系统 到块 的映 射表 (有 效文 件系 统名 字 -->块 列表 ); 2) 全部 有效 块的 列表 ; 3) 块到 主机 的映 射表 (块 -->主 机列 表) ; 4) 主机 到块 的映 射表 (主 机 -->块 列表 ); 5)LRU 缓 存( 已经 更新 心跳 状态 的主 机都 放到 LRU Cache 中 )。 HttpServer 在org.apache.hadoop.http 包 中, 提供 了将 Jetty 服 务器 内置 于 Namenode 服 务 器中 ,以 便可 以通 过 HTTP 请 求的 方式 来获 取当 前 Namenode 提 供服 务的 信息 。 Namenode 提 供了 一个 执行 初始 化的 方法 ,如 下所 示: view plain 1./** 2.*初始化 Namenode 3.*/ 4.privateprivateprivateprivatevoidvoidvoidvoidinitialize(Configurationconf)throwsthrowsthrowsthrowsIOException{ 5.InetSocketAddresssocAddr=NameNode.getAddress(conf);//从配置 conf中获取 到Namenode服务器所使用的 Socket地址 6.intintintinthandlerCount=conf.getInt("dfs.namenode.handler.count",10);//服务器 上处理器 Handler线程的数量 7. 8.ifififif(serviceAuthEnabled=conf.getBoolean(erviceAuthorizationManager.SERVIC E_AUTHORIZATION_CONFIG,falsefalsefalsefalse)){ 9.PolicyProviderpolicyProvider= 10.(PolicyProvider)(ReflectionUtils.newInstance(conf.getClass(PolicyProvi der.POLICY_PROVIDER_CONFIG,HDFSPolicyProvider.classclassclassclass,PolicyProvider.classclassclassclass), conf)); 11.SecurityUtil.setPolicy(newnewnewnewConfiguredPolicy(conf,policyProvider));// 设置安全策略 12.} 13. 14.thisthisthisthis.server=RPC.getServer(thisthisthisthis,socAddr.getHostName(),socAddr.getPort(), handlerCount,falsefalsefalsefalse,conf);//创建 RPC服务器 15. 16.thisthisthisthis.serverAddress=thisthisthisthis.server.getListenerAddress(); 17.FileSystem.setDefaultUri(conf,getUri(serverAddress)); 18.LOG.info("Namenodeupat:"+thisthisthisthis.serverAddress); 19. 20.myMetrics=newnewnewnewNameNodeMetrics(conf,thisthisthisthis);//初始化 NameNodeMetrics 21. 22.thisthisthisthis.namesystem=newnewnewnewFSNamesystem(thisthisthisthis,conf); 23.startHttpServer(conf); 24.thisthisthisthis.server.start();//启动 RPC服务器 25.startTrashEmptier(conf); 26.} 在Namenode 类 实现 中 ,其 中实 现的 操作 基本 上都 是由 FSNamesystem 来 完成 的 。在 这里 , 我 们先 不关 心 Namenode 具 体是 如何 实现 那些 基本 操作 的 ,而 只是 关注 Namenode 的 功能 特性,在 后面 再对 FSNamesystem 类 进行 分析 。这 里使 用方 式就 是 ,根据Namenode 所实 现 的接 口中 定义 的操 作, 来分 析 Namenode 服 务器 所具 备的 基本 功能 ,或 者说 提供 的基 本 服 务。 NamenodeProtocol NamenodeProtocol NamenodeProtocol NamenodeProtocol 实现 在Namenode 类 中, 实现 了 NamenodeProtocol 协 议接 口中 定义 的 getBlocks 方 法, 如下 所 示: view plain 1./** 2.*返回指定大小为 size的Datanode上块及其所在位置列表 3.*/ 4.publicpublicpublicpublicBlocksWithLocationsgetBlocks(DatanodeInfodatanode,longlonglonglongsize)throwthrowthrowthrow ssssIOException{ 5.ifififif(size<=0){ 6.throwthrowthrowthrownewnewnewnewIllegalArgumentException("Unexpectednotpositivesize:"+size) ; 7.} 8.returnreturnreturnreturnnamesystem.getBlocks(datanode,size);//调用namesystem的getBlocks 方法来真正实现 9.} 给定DatanodeInfo datanode, 它是 一个 描述 Datanode 的 状态 的实 体类 对象 ,通 过 getBlocks 方 法, 可以 获取 到总 的块 大小 为 size 的BlocksWithLocations, 即描 述这 些块 的 位 置信 息的 BlocksWithLocations 对 象。 ClientProtocol ClientProtocol ClientProtocol ClientProtocol 实现 实现ClientProtocol 协 议接 口中 定义 的方 法, 如下 所示 : view plain 1./** 2.*获取到指定文件 src的全部块的信息 3.* 4.*@paramsrc指定的文件 5.*@paramoffset起始偏移位置 6.*@paramlength长度 7.*/ 8.publicpublicpublicpublicLocatedBlocksgetBlockLocations(Stringsrc,longlonglonglongoffset,longlonglonglonglengt h)throwsthrowsthrowsthrowsIOException{ 9.myMetrics.numGetBlockLocations.inc(); 10.returnreturnreturnreturnnamesystem.getBlockLocations(getClientMachine(),src,offset,lengt h);//调用:获取到指定文件的全部块信息 11.} 12. 13./** 14.*在制定的文件系统命名空间中创建一个文件入口( entry) 15.*/ 16.publicpublicpublicpublicvoidvoidvoidvoidcreate(Stringsrc,FsPermissionmasked,StringclientName,booleboolebooleboole ananananoverwrite,shortshortshortshortreplication,longlonglonglongblockSize)throwsthrowsthrowsthrowsIOException{ 17.StringclientMachine=getClientMachine(); 18.ifififif(stateChangeLog.isDebugEnabled()){ 19.stateChangeLog.debug("*DIR*NameNode.create:file"+src+"for"+clientN ame+"at"+clientMachine); 20.} 21.ifififif(!checkPathLength(src)){ 22.throwthrowthrowthrownewnewnewnewIOException("create:Pathnametoolong.Limit"+MAX_PATH_LE NGTH+"characters,"+MAX_PATH_DEPTH+"levels."); 23.} 24.namesystem.startFile(src, 25.newnewnewnewPermissionStatus(UserGroupInformation.getCurrentUGI().getUserName() , 26.nullnullnullnull,masked), 27.clientName,clientMachine,overwrite,replication,blockSize);//调用 : 执行文件的创建操作 28.myMetrics.numFilesCreated.inc(); 29.myMetrics.numCreateFileOps.inc(); 30.} 31. 32./** 33.*对指定文件执行追加写操作 34.*/ 35.publicpublicpublicpublicLocatedBlockappend(Stringsrc,StringclientName)throwsthrowsthrowsthrowsIOException { 36.StringclientMachine=getClientMachine(); 37.ifififif(stateChangeLog.isDebugEnabled()){ 38.stateChangeLog.debug("*DIR*NameNode.append:file"+src+"for"+clientN ame+"at"+clientMachine); 39.} 40.LocatedBlockinfo=namesystem.appendFile(src,clientName,clientMachine); //调用:执行文件的追加写操作 41.myMetrics.numFilesAppended.inc(); 42.returnreturnreturnreturninfo; 43.} 44. 45./** 46.*设置副本因子 47.*/ 48.publicpublicpublicpublicbooleanbooleanbooleanbooleansetReplication(Stringsrc,shortshortshortshortreplication)throwsthrowsthrowsthrowsIOExcept ion{ 49.returnreturnreturnreturnnamesystem.setReplication(src,replication);//调用:设置副本因子 50.} 51. 52./** 53.*设置权限 54.*/ 55.publicpublicpublicpublicvoidvoidvoidvoidsetPermission(Stringsrc,FsPermissionpermissions)throwsthrowsthrowsthrowsIOExc eption{ 56.namesystem.setPermission(src,permissions);//调用:设置权限 57.} 58. 59./** 60.*设置文件属主 61.*/ 62.publicpublicpublicpublicvoidvoidvoidvoidsetOwner(Stringsrc,Stringusername,Stringgroupname)throwsthrowsthrowsthrowsI OException{ 63.namesystem.setOwner(src,username,groupname);//调用:设置文件属主 64.} 65. 66./** 67.*向指定的文件中写入数据块 68.*/ 69.publicpublicpublicpublicLocatedBlockaddBlock(Stringsrc,StringclientName)throwsthrowsthrowsthrowsIOExcepti on{ 70.stateChangeLog.debug("*BLOCK*NameNode.addBlock:file"+src+"for"+clien tName); 71.LocatedBlocklocatedBlock=namesystem.getAdditionalBlock(src,clientName); //调用:获取并执行写入操作 72.ifififif(locatedBlock!=nullnullnullnull) 73.myMetrics.numAddBlockOps.inc(); 74.returnreturnreturnreturnlocatedBlock; 75.} 76. 77./** 78.*客户端放弃对指定块的操作 79.*/ 80.publicpublicpublicpublicvoidvoidvoidvoidabandonBlock(Blockb,Stringsrc,Stringholder)throwsthrowsthrowsthrowsIOExcept ion{ 81.stateChangeLog.debug("*BLOCK*NameNode.abandonBlock:"+b+"offile"+src); 82.ifififif(!namesystem.abandonBlock(b,src,holder)){//调用 83.throwthrowthrowthrownewnewnewnewIOException("Cannotabandonblockduringwriteto"+src); 84.} 85.} 86. 87./** 88.*客户端完成对指定文件的写操作 89.*/ 90.publicpublicpublicpublicbooleanbooleanbooleanbooleancomplete(Stringsrc,StringclientName)throwsthrowsthrowsthrowsIOException{ 91.stateChangeLog.debug("*DIR*NameNode.complete:"+src+"for"+clientN ame); 92.CompleteFileStatusreturnCode=namesystem.completeFile(src,clientName); //调用:执行写操作,写完成之后关闭该文件流 93.ifififif(returnCode==CompleteFileStatus.STILL_WAITING){ 94.returnreturnreturnreturnfalsefalsefalsefalse; 95.}elseelseelseelseifififif(returnCode==CompleteFileStatus.COMPLETE_SUCCESS){ 96.returnreturnreturnreturntruetruetruetrue; 97.}elseelseelseelse{ 98.throwthrowthrowthrownewnewnewnewIOException("Couldnotcompletewritetofile"+src+"by" +clientName); 99.} 100.} 101. 102./** 103.*客户端向 Namenode报告 corrupted块的信息 104.*/ 105.publicpublicpublicpublicvoidvoidvoidvoidreportBadBlocks(LocatedBlock[]blocks)throwsthrowsthrowsthrowsIOException{ 106.stateChangeLog.info("*DIR*NameNode.reportBadBlocks"); 107.forforforfor(intintintinti=0;idirsToFormat=FSNamesystem.getNamespaceDirs(conf);//通 过配置实例 conf获取到需要进行格式化的文件系统命名空间目录 6.CollectioneditDirsToFormat=FSNamesystem.getNamespaceEditsDirs(con f);//获取到需要进行格式化的文件系统命名空间修改目录 7.forforforfor(Iteratorit=dirsToFormat.iterator();it.hasNext();){//验证上 述目录的合法性 8.FilecurDir=it.next(); 9.ifififif(!curDir.exists()) 10.continuecontinuecontinuecontinue; 11.ifififif(isConfirmationNeeded){ 12.System.err.print("Re-formatfilesystemin"+curDir+"?(YorN)"); 13.ifififif(!(System.in.read()=='Y')){ 14.System.err.println("Formatabortedin"+curDir); 15.returnreturnreturnreturntruetruetruetrue;//放弃对文件系统命名空间的格式化操作 16.} 17.whilewhilewhilewhile(System.in.read()!='/n');//discardtheenter-key 18.} 19.} 20. 21.FSNamesystemnsys=newnewnewnewFSNamesystem(newnewnewnewFSImage(dirsToFormat,editDirsToF ormat),conf);//基于构造的 FsImage文件实例来创建文件系统命名空间 22.nsys.dir.fsImage.format();//对FsImage文件进行格式化 23.returnreturnreturnreturnfalsefalsefalsefalse; 24.} 关于FsImage 与EditLog 在 文件 系统 中的 所在 位置 的选 择, 在 org.apache.hadoop.hdfs.server.namenode.FSNamesystem 类 中可 以看 到详 细的 情况 。 如 果是 确认 升级 完成 命令 行, 则实 现如 下所 示: view plain 1.privateprivateprivateprivatestaticstaticstaticstaticbooleanbooleanbooleanbooleanfinalize(Configurationconf,booleanbooleanbooleanbooleanisConfirmationNe eded)throwsthrowsthrowsthrowsIOException{ 2.CollectiondirsToFormat=FSNamesystem.getNamespaceDirs(conf);//文 件系统命名空间文件 FsImage所在目录 3.CollectioneditDirsToFormat=FSNamesystem.getNamespaceEditsDirs(con f);//EditLog所在目录 4.FSNamesystemnsys=newnewnewnewFSNamesystem(newnewnewnewFSImage(dirsToFormat,editDirsToF ormat),conf);//构造 FSNamesystem 5.System.err.print( 6."/"finalize/"willremovethepreviousstateofthefilessystem./n" 7.+"Recentupgradewillbecomepermanent./n" 8.+"Rollbackoptionwillnotbeavailableanymore./n"); 9.ifififif(isConfirmationNeeded){ 10.System.err.print("Finalizefilesystemstate?(YorN)"); 11.ifififif(!(System.in.read()=='Y')){ 12.System.err.println("Finalizeaborted."); 13.returnreturnreturnreturntruetruetruetrue;//放弃升级确认 14.} 15.whilewhilewhilewhile(System.in.read()!='/n');//discardtheenter-key 16.} 17.nsys.dir.fsImage.finalizeUpgrade();//调用FSImage类的finalizeUpgrade方法实 现升级完成的确认 18.returnreturnreturnreturnfalsefalsefalsefalse; 19.} 关于FsImage 文 件的 操作 ,可 以查 看 org.apache.hadoop.hdfs.server.namenode.FSImage 类 的具 体实 现。 在后 面会 专门 对 FSImage 类 进行 详细 阅读 分析 的。 下 面, 对 Namenode 类 的实 现做 一个 总结 : 从Namenode 实 现类 来看 ,它 主要 是实 现了 Namenode 服 务器 进程 与 Datanode 进程、文 件 系统 客户 端进 程 ,以及Secondary NameNode 进 程进 行交 互过 程中 一些 重要 的基 本的 操 作 ,具 体表 现为 , Namenode 实 现了 ClientProtocol 协 议来 与客 户端 交互 ,实 现 了 DatanodeProtocol 协 议来 与 Datanode 进 行交 互, 实现 了 NamenodeProtocol 协 议来 与 Secondary NameNode 进 行交 互。 而且 ,该 类还 给出 了一 个 static 方 法, 通过 命令 行的 方 式 用来 构造 一个 Namenode 实 例, 并启 动 Namenode 服 务器 进程 。 我 们已 经分 析了 org.apache.hadoop.hdfs.server.namenode.Namenode 类 的实 现, 而且 知 道 ,一 个 Namenode 提 供的 主要 服务 是基 于其 内部 定义 的 org.apache.hadoop.hdfs.server.namenode.FSNamesystem 属 性来 实现 的。 可见 , org.apache.hadoop.hdfs.server.namenode.FSNamesystem在 具备 了Namenode所 提供 基 本 服务 的基 础上 ,也 可以 料想 到它 实现 的复 杂性 。 在 阅读 分 析FSNamesystem 类 的源 代码 之前 ,还 是先 对与 该类 相关 的一 些其 它类 进行 了解 , 也 是为 了更 好地 理解 FSNamesystem 类 的实 现。 • Host2NodesMap 类 该类org.apache.hadoop.hdfs.server.namenode.Host2NodesMap 用 来保 存Datanode 结点 的 主机 -> DatanodeDescriptor 数 组的 映射 的类 ,其 实一 个 DatanodeDescriptor 中 已经 包 含了Datanode 所 在主 机的 字符 串的 信息 ,该 类设 置了 一个 HashMap, 如下 所示 : view plain 1.privateprivateprivateprivateHashMapmap=newnewnewnewHashMap(); 可见,对 于一 个 Datanode 结点,也 就是 对应 的一 台主 机上 ,可 以存 在多 个 Datanode 进程, 因 此存 在一 个 Datanode 主 机到 一组 Datanode 进 程信 息描 述的 映射 。但 是一 般来 说 ,一个 主 机上 只单 独设 置一 个 Datanode 进 程。 通 过上 面叙 述, 可以 想到 ,作 为一 个 Host2NodesMap 类 ,应 该提 供向 map 映 射表 中添 加 对 的操 作, 删除 指定 Host 对 应的 DatanodeDescriptor[],获 取 到指 定 Host 对 应的 DatanodeDescriptor[]信 息, 等等 。 • NetworkTopology 类 该类org.apache.hadoop.net.NetworkTopology 表 示一 个具 有树 状网 络拓 扑结 构的 计算 机 集 群, 例如 ,一 个集 群可 能由 多个 数据 中心 ( Data Center) 组成 ,在 这些 数据 中心 分布 着 为 计算 需求 而设 置的 很多 计算 机的 机架 ( Rack) 。在 该类 中定 义了 一 个 org.apache.hadoop.net.NetworkTopology.InnerNode 类 ,表 示数 据中 心( 或机 架) 的转 换 器 (或 路由 ), 该内 部类 继承 层次 关系 如下 所示 : view plain 1.◦org.apache.hadoop.net.NodeBase(implementsimplementsimplementsimplementsorg.apache.hadoop.net.Node) 2.◦org.apache.hadoop.net.NetworkTopology.InnerNode 1、Node 接口 org.apache.hadoop.net.Node 接 口表 示网 络拓 扑中 的结 点的 抽象 ,一 个 Node 可 能是 一 个 Datanode, 也可 能是 一个 表示 数据 中心 或机 架的 内部 结点 。每 一个 Node 在 网络 拓扑 中应 该 有一 个名 称及 其位 置 (使 用类 似文 件路 径的 方式 来定 义 ),例如,如 果一 个 Datanode 名 称为hostname:port, 并且 该 Datanode 在 数据 中心 dog 里的orange 机 架上 ,则 这 个 Datanode 在 网络 拓扑 中的 位置 (网 络地 址) 为 /dog/orange, 下面 是 Node 接 口的 定义 : view plain 1.packagepackagepackagepackageorg.apache.hadoop.net; 2. 3.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceNode{ 4./**返回表示该结点的网络地址的字符串 */ 5.publicpublicpublicpublicStringgetNetworkLocation(); 6./**设置该结点的网络地址字符串 */ 7.publicpublicpublicpublicvoidvoidvoidvoidsetNetworkLocation(Stringlocation); 8./**获取该结点的名称 */ 9.publicpublicpublicpublicStringgetName(); 10./**获取该结点的父结点 */ 11.publicpublicpublicpublicNodegetParent(); 12./**设置该结点的父结点 */ 13.publicpublicpublicpublicvoidvoidvoidvoidsetParent(Nodeparent); 14./** 15.*获取该结点在网络拓扑中的层次 ,例如 ,如果是树状网络拓扑的根则返回 0,树状网络拓扑 的根的直接孩子的层次为 1 16.*/ 17.publicpublicpublicpublicintintintintgetLevel(); 18./**设置该结点在网络拓扑中的层次 */ 19.publicpublicpublicpublicvoidvoidvoidvoidsetLevel(intintintinti); 20.} 2、NodeBase 类 该 类实 现了 Node 接口,是 一个 最基 本的 结点 的实 现 。下 面是 该类 定义 的属 性 ,都 是与 一个 结 点的 基本 属性 信息 相关 的: view plain 1.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticcharcharcharcharPATH_SEPARATOR='/';//路径分隔符 2.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticStringPATH_SEPARATOR_STR="/"; 3.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticStringROOT="";//网络拓扑的根结点 4. 5.protectedprotectedprotectedprotectedStringname;//host:port# 6.protectedprotectedprotectedprotectedStringlocation;//该结点的网络位置 7.protectedprotectedprotectedprotectedintintintintlevel;//该结点在网络拓扑中的层次 8.protectedprotectedprotectedprotectedNodeparent;//该结点的父结点 该 类定 义的 方法 就不 多说 了, 就是 实现 了 Node 接 口中 定义 的基 本方 法。 3、NetworkTopology.InnerNode 类 该 类表 示一 个数 据中 心 (或 机架 )的 转换 器 (或 路由 ),它 不同 于网 络拓 扑中 的叶 结点 (主 机 ), 因此 它具 有非 空的 孩子 结点 。该 内部 结点 定义 了如 下两 个属 性: view plain 1.privateprivateprivateprivateArrayListchildren=newnewnewnewArrayList(); 2.privateprivateprivateprivateintintintintnumOfLeaves; 作 为内 部结 点 ,它 是其 它节 点之 间连 接的 桥梁 。上 面属 性 children 列 表表 示该 内部 结点 所维 护 的孩 子结 点的 列表 , numOfLeaves 用 来记 录该 结点 所维 护的 结点 的数 量。 现 在, 我们 可以 分析 NetworkTopology 类 的实 现了 。该 类定 义了 如下 属性 : view plain 1.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticStringDEFAULT_RACK="/default-rack";//默认机架名称 2.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticintintintintDEFAULT_HOST_LEVEL=2;//主机层次 3. 4.InnerNodeclusterMap=newnewnewnewInnerNode(InnerNode.ROOT);//定义网络拓扑的根结 点 5.privateprivateprivateprivateintintintintnumOfRacks=0;//机架计数 6.privateprivateprivateprivateReadWriteLocknetlock;//读写锁 下 面, 对一 个网 络拓 扑实 例可 以执 行的 操作 ,及 其功 能描 述列 表如 下: view plain 1./** 2.*向网络中加入一个(叶)结点 3.*如果必要的话,更新结点计数和机架计数 4.*/ 5.publicpublicpublicpublicvoidvoidvoidvoidadd(Nodenode); 6. 7./** 8.*删除一个结点 9.*/ 10.publicpublicpublicpublicvoidvoidvoidvoidremove(Nodenode); 11. 12./** 13.*判断当前网络中是否包含指定节点 node 14.*/ 15.publicpublicpublicpublicbooleanbooleanbooleanbooleancontains(Nodenode); 16. 17./** 18.*获取指定位置所表示的结点,返回该结点的引用 19.*/ 20.publicpublicpublicpublicNodegetNode(Stringloc); 21. 22./**返回网络中机架总数 */ 23.publicpublicpublicpublicintintintintgetNumOfRacks(); 24. 25./**返回总结点数量 */ 26.publicpublicpublicpublicintintintintgetNumOfLeaves(); 27. 28./** 29.*返回两个节点之间的距离 30.*假定某个结点到其父结点之间的距离为 1 31.*/ 32.publicpublicpublicpublicintintintintgetDistance(Nodenode1,Nodenode2); 33. 34./** 35.*两个结点是否在同一个机架上 36.*/ 37.publicpublicpublicpublicbooleanbooleanbooleanbooleanisOnSameRack(Nodenode1,Nodenode2); 38. 39./** 40.*从指定范围 scope内,随机选择一个结点并返回 41.*/ 42.publicpublicpublicpublicNodechooseRandom(Stringscope); 43. 44./** 45.*返回指定范围 scope内,且不在 excludedNodes列表中的可用结点的数量 46.*/ 47.publicpublicpublicpublicintintintintcountNumOfAvailableNodes(Stringscope,ListexcludedNodes); 48. 49./** 50.*根据网络中到结点 reader的距离,对 nodes结点数组进行排序 51.*/ 52.publicpublicpublicpublicvoidvoidvoidvoidpseudoSortByDistance(Nodereader,Node[]nodes); • DNSToSwitchMapping 接 口类 该 接口 是一 个支 持插 件的 定义 ,通 过插 件定 义 DNS-name/IP-address -> RackID 之 间转 换 的 解析 器。 该接 口定 义了 如下 一个 解析 的方 法: view plain 1./** 2.*解析给定列表中 DNS-names/IP-addresses,返回一个经过解析以后的网络路径的列表 3.* 4.*例如 ,列表中带解析的一个域名 x.y.com,转换后应该是一个网络路径 ,形如 /foo/rack,其 中/表示根, foo是连接到 rack的转换器。 5.* 6.*注意 :hostname/ip-address并不是经过解析返回路径的一部分 ,一个集群网络拓扑实例应 该能够确定在该网络中的组件的数量。 7.*/ 8.publicpublicpublicpublicListresolve(Listnames); 实 现该 接口 的类 的继 承层 次关 系如 下: view plain 1.◦org.apache.hadoop.net.StaticMapping(implementsimplementsimplementsimplementsorg.apache.hadoop.net.DNSToS witchMapping) 2.◦org.apache.hadoop.net.ScriptBasedMapping.RawScriptBasedMapping(implementsimplementsimplementsimplementso rg.apache.hadoop.net.DNSToSwitchMapping) 3.◦org.apache.hadoop.net.CachedDNSToSwitchMapping(implementsimplementsimplementsimplementsorg.apache.hadoop. net.DNSToSwitchMapping) 4.◦org.apache.hadoop.net.ScriptBasedMapping 这 里, 我们 只看 CachedDNSToSwitchMapping 类 的实 现, 该类 将解 析 DNS-name/IP-address -> RackID, 并将 解析 后的 RackID 缓 存起 来。 该 类定 义了 一个 缓存 : view plain 1.privateprivateprivateprivateMapcache=newnewnewnewConcurrentHashMap(); 通 过该 类内 部定 义的 一个 DNSToSwitchMapping 实 例: view plain 1.protectedprotectedprotectedprotectedDNSToSwitchMappingrawMapping; 来 解析 输入 列表 中的 DNS-name/IP-address, 然后 通过 解析 获取 到指 定 的 DNS-name/IP-address 所 对应 的 RackID, 并将 DNS-name/IP-address -> RackID 键 值对 加 入到 缓存 cache 中。 对 于一 个 DNSToSwitchMapping 接 口的 实现 类, 例如 RawScriptBasedMapping, 是从 与 Hadoop 配 置文 件相 关的 配置 类实 例来 获取 到解 析 DNS-name/IP-address -> RackID 映射 的 脚本 文件 ,通 过执 行脚 本来 实现 解析 。关 于具 体实 现可 以查 看对 应的 实现 类的 源代 码。 • ReplicationTargetChooser 类 该类org.apache.hadoop.hdfs.server.namenode.ReplicationTargetChooser 是 对指 定的 块 副 本的 存放 位置 进行 定位 选择 的实 现类 。 对 指定 块副 本的 存放 位置 进行 定位 选择 的基 本策 略是 :如 果一 个写 操作 是在 一个 Datanode 上,第 一个 块副 本被 存放 在本 地机 器上 ,否 则就 随机 选择 一个 Datanode 来 存放 第一 个块 副 本;第 二个 块副 本存 放在 与第 一个 块副 本不 同的 机架 的一 个 Datanode 上;第 三个 块副 本存 放 在与 第一 个块 副本 相同 的机 架上 ,也 就是 同一 个 Datanode 上。 该 类主 要就 是针 对块 副本 的存 放位 置的 选择 ,因 此在 选择 指定 机架 的过 程中 ,需 要获 取到 指 定Datanode 的DatanodeDescriptor 信 息, 通过 它来 验证 是否 可以 存放 块副 本。 在三 个块 副 本都 已经 存放 好以 后 ,可 能还 需要 对指 定块 的块 副本 进行 检查 验证 ,是 否满 足实 际的 存储 需 要, 比如 满足 副本 因子 。 • HostsFileReader 类 该类org.apache.hadoop.util.HostsFileReader 用 来跟 踪 Datanode 的 ,哪 些 Datanode 允 许 连接 到 Namenode, 哪些 不能 够连 接到 Namenode, 都在 该类 中指 定的 列表 中记 录着 , 如 下所 示: view plain 1.privateprivateprivateprivateSetincludes;//允许连接到 Namenode的Datanode列表 2.privateprivateprivateprivateSetexcludes;//不允许连接到 Namenode的Datanode列表 3.privateprivateprivateprivateStringincludesFile;//允许连接到 Namenode的Datanode记录文件 4.privateprivateprivateprivateStringexcludesFile;//不允许连接到 Namenode的Datanode记录文件 该 类能 够将 的 Datanode 记 录列 表加 载的 内存 中, 这是 通过 refresh 方 法, 在该 方法 中调 用 readFileToSet 方 法实 现的 ,以 此刷 新内 存中 的 includes 与excludes 列 表。 • GenerationStamp 类 该类org.apache.hadoop.hdfs.server.common.GenerationStamp 是Hadoop 文 件系 统的 时 间 戳的 实现 ,通 过它 可以 设置 时间 戳 。它 实现 了 WritableComparable 接口,因 此又 是可 序 列 化、 可进 行比 较的 。 • BlocksMap 类 通 过上 面 ,已 经知 道 ,FSNamesystem 跟 踪几 个重 要的 数据 映射 表 ,其中BlocksMap 类维 护块(Block)到 其元 数据 的映 射表 ,元 数据 信息 包括 块所 属的 inode、存 储块 的 Datanode。 在 该类 内部 ,定 义了 一个 块元 数据 的实 体 类 org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo, 该实 体类 包含 了块 所属 的inode 与 存储 块的 Datanode。 该内 部类 继承 自 org.apache.hadoop.hdfs.protocol.Block 类,Block 是 块的 基本 抽象 ,其 中包 含了 块的 下面 三个 属性 信息 : view plain 1.privateprivateprivateprivatelonglonglonglongblockId;//块ID 2.privateprivateprivateprivatelonglonglonglongnumBytes;//块大小 3.privateprivateprivateprivatelonglonglonglonggenerationStamp;//时间戳 并且,由于Block 实 现了 org.apache.hadoop.io.Writable 接口,因此Block 类 是可 序列 化的 。 再 看内 部类 org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo,该 内部 类中 定 义了 两个 属性 : view plain 1.privateprivateprivateprivateINodeFileinode;//文件结点 2.privateprivateprivateprivateObject[]triplets;//文件的块列表 org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo 的 第一 个是 inode 属性, 简 单看 一下 INodeFile 类 的继 承层 次关 系: view plain 1.◦java.lang.Comparable 2.◦org.apache.hadoop.hdfs.server.namenode.INode 3.◦org.apache.hadoop.hdfs.server.namenode.INodeFile 其 中, INode 表 示了 一个 文件 或者 目录 的 inode, 包括 如下 属性 : view plain 1.protectedprotectedprotectedprotectedbytebytebytebyte[]name;//文件(或目录)名称 2.protectedprotectedprotectedprotectedINodeDirectoryparent;//父目录 3.protectedprotectedprotectedprotectedlonglonglonglongmodificationTime;//INode修改时间 4.protectedprotectedprotectedprotectedlonglonglonglongaccessTime;//访问时间 5.privateprivateprivateprivatelonglonglonglongpermission; INodeFile 继 承自 INode 类 ,又 增加 了如 下几 个描 述块 信息 的属 性: view plain 1.protectedprotectedprotectedprotectedBlockInfoblocks[]=nullnullnullnull;//块列表 2.protectedprotectedprotectedprotectedshortshortshortshortblockReplication;//块副本因子 3.protectedprotectedprotectedprotectedlonglonglonglongpreferredBlockSize;//标准块大小 org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo 的 第二 个属 性是 一 个 Object[] triplets 数 组, 该数 组包 含了 块的 三组 引用 。具 体信 息描 述如 下: 对 于块 所属 的第 i个Datanode, 数组 元素 triplets[3*i]引 用了 描述 该 Datanode 结点的 DatanodeDescriptor 实 例, 数组 元素 triplets[3*i+1]与triplets[3*i+2]引 用的 分别 是前 (previous) 一个 块与 后( next) 一个 块。 注意 , triplets[3*i+1]与triplets[3*i+2]所 引用 的块 应 该在 指定 的同 一个 Datanode 上 的块 列表 中。 BlocksMap 类 只定 义了 一个 映射 表 Map, 如下 所示 : view plain 1.privateprivateprivateprivateMapmap=newnewnewnewHashMap(); 围 绕该 映射 表, 该类 中定 义了 一系 列与 该映 射表 相关 的操 作, 包括 实现 的内 部 类 org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo.NodeIterator, 内部 类定 义 如下 所示 : view plain 1.privateprivateprivateprivatestaticstaticstaticstaticclassclassclassclassNodeIteratorimplementsimplementsimplementsimplementsIterator 该 内部 类用 于迭 代映 射 表map 中 块对 应的 元数 据实 体BlockInfo 中 封装 的Datanode 的 状态 的 属性 DatanodeDescriptor, 从而 获取 该块 所在 的 Datanode 的 状态 。 • CorruptReplicasMap 类 该 类是 有关 失效 块的 映射 表 。如 果一 个块 被认 为是 失效 的 ,是 指该 块对 应的 全部 块副 本都 失 效 而不 可用 。 org.apache.hadoop.hdfs.server.namenode.CorruptReplicasMap 类 定义 了一 个 有序 Map 映 射表 : view plain 1.privateprivateprivateprivateMap>corruptReplicasMap= 2.newnewnewnewTreeMap>(); 表示,一 个块 对应 的块 副本 可能 在其 它 Datanode 上的,corruptReplicasMap 表 示一 个块 到 对 应的 全部 块副 本所 在的 Datanode 集 合的 映射 。 • UnderReplicatedBlocks 类 org.apache.hadoop.hdfs.server.namenode.UnderReplicatedBlocks 类 是描 述某 些块 的副 本 数量 不足 块的 实体 类 ,而且,对 于块 设定 了优 先级 ,通 过一 个优 先级 队列 来管 理块 副本 不 足 的块 的集 合。 权限 为 0的 块, 具有 最高 的优 先级 ,而 且, 一个 块的 副本 只能 有一 个具 有 最 高优 先级 。看 该类 定义 的优 先级 队列 ,如 下所 示: view plain 1.privateprivateprivateprivateList>priorityQueues=newnewnewnewArrayList>() ; 如 果某 些块 副本 数量 不足 ,可 以通 过该 队列 来根 据块 的优 先级 执行 块复 制操 作 ,以 满足 副本 因 子的 要求 。如 果某 个块 的副 本数 量满 足副 本因 子, 则需 要从 队列 中删 除, 更新 统计 数据 。 该 类提 供了 想队 列中 添加 和删 除块 等等 基本 操作 。 • PendingReplicationBlocks 类 该类org.apache.hadoop.hdfs.server.namenode.PendingReplicationBlocks 是 描述 当前 尚 未 完成 块副 本复 制的 块的 列表 。因 为 HDFS 支 持块 的流 水线 复制 ,当 文件 系统 客户 端开 始 向 第一 个 Datanode 复 制数 据块 的时 候 ,会 在第 一个 Datanode 上 启动 复制 进程 ,将 该结 点 上 接收 到的 (部分)数 据块 开始 向第 二个 Datanode 上 传输 复制 ,而 第二 个 Datanode 结点 又 向第 三个 Datanode 结 点进 行流 水线 复制 ,……,直 到满 足副 本因 子要 求 。所以,在 执行 流 水线 复制 的过 程中 ,因 为这 种可 并行 的复 制方 式使 得某 些块 副本 的复 制完 成时 间呈 现阶 梯 状 ,也 就是 使用 一个 数据 结构 来保 存这 些尚 未复 制完 成的 块副 本, PendingReplicationBlocks 就 是, 可以 看到 该类 中定 义了 一个 映射 表: view plain 1.privateprivateprivateprivateMappendingReplications; 其中Block 是 块, 作为 映射 表的 键, 而值 是 org.apache.hadoop.hdfs.server.namenode.PendingReplicationBlocks.PendingBlockInfo, 该 内部 类定 义了 如下 两个 属性 来表 示执 行流 水线 复过 程中 的元 数据 : view plain 1.privateprivateprivateprivatelonglonglonglongtimeStamp;//时间戳 2.privateprivateprivateprivateintintintintnumReplicasInProgress;//执行块的流水线复制的块副本数量 而 且, 该类 中还 定义 了一 个内 部后 台线 程 类 org.apache.hadoop.hdfs.server.namenode.PendingReplicationBlocks.PendingReplicatio nMonitor, 用来 监视 流水 线复 制过 程中 块复 制的 进度 情况 。 • LeaseManager 类 该类org.apache.hadoop.hdfs.server.namenode.LeaseManager 表 示对 文件 的租 约进 行管 理 。很 自然 地, 对于 租约 的表 示与 管理 ,在 该类 的内 部定 义了 两个 内部 类: Lease 内 部类 :一 个用 来表 示租 约的 实体 类 org.apache.hadoop.hdfs.server.namenode.LeaseManager.Lease,因 为文 件系 统客 户端 需 要向Namenode 请 求写 数据 (向Namenode 结 点写 数据 块 ),因 此一 个文 件系 统客 户端 必 须 持有 一个 Lease 实 例, 该 Lease 实 例包 含文 件系 统客 户端 的持 有身 份( 客户 端名 称) 、 最 后更 新 Lease 时间(用 于判 断是 否超 时 )、所 要写 数据 的路 径 (应 该与 指定 的 Datanode 上 文件 系统 的 Path 相 关) 。对 于每 一个 文件 系统 客户 端, 都应 该持 有一 个 Lease,Lease 管 理一 个客 户端 的全 部锁 (写 锁) ,其 中 Lease 中 包含 的最 后更 新时 间需 要文 件系 统客 户 端 周期 地检 查来 实现 更新 ,这 样写 数据 才不 会因 为超 时而 放弃 一个 Lease。当然,如 果一 个 文 件系 统客 户端 发生 故障 ,或 者它 不需 要持 有该 Lease, 也就 是不 需要 执行 文件 的写 操作 , 那 么它 会释 放掉 由它 所持 有的 Lease 管 理的 全部 的锁 ,以 便满 足其 它客 户端 的请 求。 Monitor 内 部线 程类 :一 个内 部线 程 类 org.apache.hadoop.hdfs.server.namenode.LeaseManager.Monitor,它 是用 来周 期性 地 (每 2s 检 查一 次 )检查LeaseManager 管 理器 所维 护的 Lease 列 表中 是否 有 Lease 过 期的 文件 系 统客 户端 ,如 果过 期则 从 Lease 列 表中 删除 掉。 基 本可 以清 楚 LeaseManager 的 是如 何管 理 Lease, 其中 LeaseManager 还 提供 了向 它维 护 的列 表中 添加 Lease、 删除 Lease、 更新 Lease 等 等操 作。 • SafeModeInfo 类 该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.SafeModeInfo 是 FSNamesystem 的 一个 内部 类, 定义 了与 安全 模式 相关 的信 息和 操作 。看 该类 定义 的属 性 信 息: view plain 1.//配置 Field,从与配置文件相关的配置类实例获取到这些属性值 2. 3./**只有当条件(是个比率)大于 threshold的时候,才能进入安全模式 */ 4.privateprivateprivateprivatedoubledoubledoubledoublethreshold; 5./**进入安全模式 */ 6.privateprivateprivateprivateintintintintextension; 7./**安全模式要求的最小副本因子 */ 8.privateprivateprivateprivateintintintintsafeReplication; 9. 10.//内部 Field 11. 12./** 13.*当达到 threshold的值的时间 14.*-1离开安全模式 15.*0正在安全模式下,但是 threshold是不可达的 16.*/ 17.privateprivateprivateprivatelonglonglonglongreached=-1; 18./**块总数 */ 19.intintintintblockTotal; 20./**安全的块的总数 */ 21.privateprivateprivateprivateintintintintblockSafe; 22./**输出最后状态的时间 */ 23.privateprivateprivateprivatelonglonglonglonglastStatusReport=0; 下 面看 有关 安全 模式 下的 操作 : 1、 进入 安全 模式 : view plain 1.voidvoidvoidvoidenter(){ 2.thisthisthisthis.reached=0; 3.} 设 置标 志 reached=0, 表示 进入 安全 模式 。 2、 离开 安全 模式 : view plain 1.synchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidleave(booleanbooleanbooleanbooleancheckForUpgrades){ 2.ifififif(checkForUpgrades){ 3.booleanbooleanbooleanbooleanneedUpgrade=falsefalsefalsefalse;//是否是因为要启动分布式升级进程而离开安全 模式 4.trytrytrytry{ 5.needUpgrade=startDistributedUpgradeIfNeeded();//调用 Namenode的 升级管理器的操作来返回是否需要启动分布式升级进程 6.}catchcatchcatchcatch(IOExceptione){ 7.FSNamesystem.LOG.error(StringUtils.stringifyException(e)); 8.} 9.ifififif(needUpgrade){//如果需要,进入手动安全模式 10.safeMode=newnewnewnewSafeModeInfo(); 11.returnreturnreturnreturn; 12.} 13.} 14.//如果不需要启动安全升级进程 15.processMisReplicatedBlocks();//验证安全模式下块副本是否满足要求,如果不满 足根据实际情况进行处理,例如块副本过多,对其进行标识,然后等待时机删除多余的块副本 16.longlonglonglongtimeInSafemode=now()-systemStart; 17.NameNode.stateChangeLog.info("STATE*Leavingsafemodeafter"+timeIn Safemode/1000+"secs."); 18.NameNode.getNameNodeMetrics().safeModeTime.set((intintintint)timeInSafemode); 19. 20.ifififif(reached>=0){ 21.NameNode.stateChangeLog.info("STATE*SafemodeisOFF."); 22.} 23.reached=-1;//设置离开安全模式 24.safeMode=nullnullnullnull; 25.NameNode.stateChangeLog.info("STATE*Networktopologyhas"+clusterMap. getNumOfRacks()+"racksand" 26.+clusterMap.getNumOfLeaves()+"datanodes"); 27.NameNode.stateChangeLog.info("STATE*UnderReplicatedBlockshas"+neede dReplications.size()+"blocks"); 28.} 3、 计算 安全 块比 率: view plain 1.privateprivateprivateprivatefloatfloatfloatfloatgetSafeBlockRatio(){ 2.returnreturnreturnreturn(blockTotal==0?1:(floatfloatfloatfloat)blockSafe/blockTotal); 3.} 这 个比 率为 blockSafe/blockTotal, 即安 全块 数量 与总 块数 量的 比值 ,这 个比 率用 于 与 threshold, 当调 用 needEnter 方 法需 要进 入安 全模 式进 行状 态检 查的 时候 ,必 须满 足 getSafeBlockRatio() < threshold 才 能进 入安 全模 式。 其 它还 有一 些与 安全 模式 相关 的操 作, 比如 对块 的检 查等 等操 作。 • SafeModeMonitor 线 程类 该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.SafeModeMonitor 是一 个 后台 线程 类 ,主 要用 于在 SafeModeInfo 类中,用 来周 期性 地检 查是 否达 到离 开安 全模 式 的 条件 ,因 此, 该线 程必 须在 进入 安全 模式 之后 启动 (也 就是 达到 threshold)。 • HeartbeatMonitor 线 程类 该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.HeartbeatMonitor 是 一个 后 台线 程类 ,周 期性 地调 用 FSNamesystem 类 定义 的 heartbeatCheck 方 法, 来监 视 Datanode 结 点发 送的 心跳 状态 信息 ,并 做出 处理 ,后 面会 对 heartbeatCheck 方 法详 细分 析 的。 • ReplicationMonitor 线 程类 该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.ReplicationMonitor 是一 个 后台 线程 类, 周期 性地 调用 FSNamesystem 类 定义 的两 个方 法: view plain 1.computeDatanodeWork();//计算块副本数量,以制定计划并调度 Datanode处理 2.processPendingReplications();//处理未完成块的流水线复制的副本 具 体实 现在 后面 详细 分析 。 这 里对 与 org.apache.hadoop.hdfs.server.namenode.FSDirectory 类 相关 的类 进行 阅读 分 析。 • INodeDirectoryWithQuota 类 该类org.apache.hadoop.hdfs.server.namenode.INodeDirectoryWithQuota 的 继承 层次 关 系 如下 所示 : view plain 1.◦org.apache.hadoop.hdfs.server.namenode.INode(implementsimplementsimplementsimplementsjava.lang.Comparabl e) 2.◦org.apache.hadoop.hdfs.server.namenode.INodeDirectory 3.◦org.apache.hadoop.hdfs.server.namenode.INodeDirectoryWithQuota 1、INode 抽 象类 该 类是 一个 保存 在内 存中 的file/block 层 次结 构 ,一 个基 本 的INode 包 含了 文件 和目 录inode 的 通用 域( Field)。 下面 看 INode 类 定义 的属 性: view plain 1.protectedprotectedprotectedprotectedbytebytebytebyte[]name;//名称 2.protectedprotectedprotectedprotectedINodeDirectoryparent;//所在目录 3.protectedprotectedprotectedprotectedlonglonglonglongmodificationTime;//修改时间 4.protectedprotectedprotectedprotectedlonglonglonglongaccessTime;//访问时间 5.privateprivateprivateprivatelonglonglonglongpermission;//权限,只能调用 updatePermissionStatus方法设置权限 INode 类 提供 的构 造方 法如 下所 示: view plain 1.protectedprotectedprotectedprotectedINode(){//受保护构造方法,子类可以继承(初始化 INode的属性值) 2.name=nullnullnullnull; 3.parent=nullnullnullnull; 4.modificationTime=0; 5.accessTime=0; 6.} 7. 8.INode(PermissionStatuspermissions,longlonglonglongmTime,longlonglonglongatime){//根据指定权限 创建 INode 9.thisthisthisthis.name=nullnullnullnull; 10.thisthisthisthis.parent=nullnullnullnull; 11.thisthisthisthis.modificationTime=mTime; 12.setAccessTime(atime); 13.setPermissionStatus(permissions);//设置权限 :包括用户名 、组、FsPermission信 息 14.} 15. 16.protectedprotectedprotectedprotectedINode(Stringname,PermissionStatuspermissions){//受保护构造方 法 17.thisthisthisthis(permissions,0L,0L); 18.setLocalName(name); 19.} 20. 21./** 22.*通过拷贝构造 INode实例 23.*/ 24.INode(INodeother){ 25.setLocalName(other.getLocalName()); 26.thisthisthisthis.parent=other.getParent(); 27.setPermissionStatus(other.getPermissionStatus()); 28.setModificationTime(other.getModificationTime()); 29.setAccessTime(other.getAccessTime()); 30.} INode 类 主要 就是 针对 一个 INode 的 名称 、所 在目 录、 修改 时间 、访 问时 间、 权限 这些 属 性 来实 现操 作的 ,该 类中 的方 法无 非实 现对 这些 属性 的操 作。 另外 还包 括删 除该 INode,其 它 几个 抽象 方法 ,如 下所 示: view plain 1./** 2.*检查该 INode是否是一个目录 3.*/ 4.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleanisDirectory(); 5. 6./** 7.*收集该 INode的所有块(包括该 INode的孩子结点),并清除全部对该 INode的引用 8.*/ 9.abstractabstractabstractabstractintintintintcollectSubtreeBlocksAndClear(Listv); 10. 11./** 12.*计算摘要信息,返回数组包含如下内容: 13.*0:内容摘要长度 ,1:文件数量 ,2:目录数量 3:磁盘空间 14.*/ 15.abstractabstractabstractabstractlonglonglonglong[]computeContentSummary(longlonglonglong[]summary); 16. 17./** 18.*获取以该 INode为根的磁盘空间与命名空间统计信息 19.*其中, DirCounts包含 nsCount(namespaceconsumed)和 dsCount(diskspaceconsumed)两个计数器变量 20.*/ 21.abstractabstractabstractabstractDirCountsspaceConsumedInTree(DirCountscounts); 2、INodeDirectory 类 INodeDirectory 类 是一 个目 录 INode,因 此该 类内 部定 义了 一个 INode 列表。该 类定 义的 属 性 如下 : view plain 1.protectedprotectedprotectedprotectedstaticstaticstaticstaticfinalfinalfinalfinalintintintintDEFAULT_FILES_PER_DIRECTORY=5;//一个目录 INode 中默认可以存储 5个文件 2.privateprivateprivateprivateListchildren;//以该目录 INode为根的 INode实例列表 可 以想 象得 到, 作为 一个 目录 ,应 该提 供从 目录 中检 索得 到指 定的 INode 的 操作 ,还 有就 是 对引 用该 INode 的INode 进 行的 一些 基本 操作 。这 里就 不过 多阐 述了 ,可 以阅 读该 类的 源 代码 。 3、INodeDirectoryWithQuota 类 INodeDirectoryWithQuota 类 继承 自 INodeDirectory 类,INodeDirectoryWithQuota 类 表示 具 有配 额限 制的 目录 INode 实 现类 。我 们通 过该 类中 定义 的一 些与 配额 有关 的属 性就 能了 解 到这 样一 种目 录 INode 有 什么 样的 特点 : view plain 1.privateprivateprivateprivatelonglonglonglongnsQuota;//命名空间配额限制 2.privateprivateprivateprivatelonglonglonglongnsCount;//命名空间大小 3.privateprivateprivateprivatelonglonglonglongdsQuota;//磁盘空间配额限制 4.privateprivateprivateprivatelonglonglonglongdiskspace;//磁盘空间大小 • INodeFileUnderConstruction 类 该 类的 继承 层次 关系 如下 所示 : view plain 1.◦org.apache.hadoop.hdfs.server.namenode.INode(implementsimplementsimplementsimplementsjava.lang.Comparabl e) 2.◦org.apache.hadoop.hdfs.server.namenode.INodeFile 3.◦org.apache.hadoop.hdfs.server.namenode.INodeFileUnderConstruction 其 中, INode 类 前面 已经 介绍 了, 它是 一个 目录 或者 文件 的 INode 的 抽象 。 1、INodeFile 类 该 类表 示一 个文 件 INode,正 好与 目录 INode 相 对应 。因 为我 们已 经阅 读分 析过 目录 INode 的 实现 ,对 该文 件 INode 的 实现 就比 较简 单了 。看 该类 的属 性: view plain 1.staticstaticstaticstaticfinalfinalfinalfinalFsPermissionUMASK=FsPermission.createImmutable((shortshortshortshort)0111); //文件 INode默认权限 2. 3.protectedprotectedprotectedprotectedBlockInfoblocks[]=nullnullnullnull;//块的元数据信息实体的数组 4.protectedprotectedprotectedprotectedshortshortshortshortblockReplication;//块副本数 5.protectedprotectedprotectedprotectedlonglonglonglongpreferredBlockSize;//块大小 该 类定 义了 如下 几个 基本 的操 作: view plain 1./** 2.*将块加入到列表 blocks中 3.*/ 4.voidvoidvoidvoidaddBlock(BlockInfonewblock); 5. 6./** 7.*为该文件设置块副本数 8.*/ 9.voidvoidvoidvoidsetReplication(shortshortshortshortreplication); 10. 11./** 12.*根据索引位置和块(属于该文件)设置该文件中该块 13.*/ 14.voidvoidvoidvoidsetBlock(intintintintidx,BlockInfoblk); 对 应的 set 方 法, 也存 在 get 实 现。 可见,一个INodeFile 类 实例 是不 持有 任何 客户 端或 者 Datanode 信 息的 ,就 是一 个基 本的 实 在的 文件 。因 为在 HDFS 集 群中 需要 执行 计算 任务 ,这 要涉 及到 块的 复制 等操 作, 而某 些 块需 要由 Namenode 调 度分 派给 指定 的进 程去 执行 ,这 就需 要一 种实 体类 ,既 能够 包 含 INodeFile 的 基本 信息 ,又 能够 包含 与在 该 INodeFile 上 执行 操作 的进 程, 所以 , Hadoop 实 现了 一个 INodeFileUnderConstruction 类 ,并 在 INodeFile 类 中实 现了 由 INodeFile 到 INodeFileUnderConstruction 的 转换 ,如 下所 示: view plain 1.INodeFileUnderConstructiontoINodeFileUnderConstruction(StringclientName,S tringclientMachine,DatanodeDescriptorclientNode)throwsthrowsthrowsthrowsIOException{ 2.ifififif(isUnderConstruction()){//如果该 INodeFile已经被创建 3.returnreturnreturnreturn(INodeFileUnderConstruction)thisthisthisthis;//转换 为 INodeFileUnderConstruction实例 4.} 5.//如果该 INodeFile没有创建,则直接构造一个 INodeFileUnderConstruction实例 6.returnreturnreturnreturnnewnewnewnewINodeFileUnderConstruction(name, 7.blockReplication,modificationTime,preferredBlockSize, 8.blocks,getPermissionStatus(), 9.clientName,clientMachine,clientNode); 10.} 2、INodeFileUnderConstruction 类 该 类所 含有 的信 息包 括与 执行 计算 任务 相关 的一 些属 性, 如下 所示 : view plain 1.finalfinalfinalfinalStringclientName;//租约( lease)持有者 2.privateprivateprivateprivatefinalfinalfinalfinalStringclientMachine;//客户端主机 3.privateprivateprivateprivatefinalfinalfinalfinalDatanodeDescriptorclientNode;//如果客户端同时也是 HDFS集群中 的 Datanode 4. 5.privateprivateprivateprivateintintintintprimaryNodeIndex=-1;//客户端结点激活租约( lease) 6.privateprivateprivateprivateDatanodeDescriptor[]targets=nullnullnullnull;//文件最后一个块的存储位置信 息 7.privateprivateprivateprivatelonglonglonglonglastRecoveryTime=0; 通 过上 面属 性信 息可 以知 道, 一个 INodeFileUnderConstruction 文 件具 有持 有操 作该 文件 的 进程 (客 户端 )的 一些 信息 ,如 果客 户端 进程 同时 也是 HDFS 集 群中 Datanode,它 就能 够 根据 租约 的有 效性 来执 行与 该文 件相 关的 操作 ,例 如复 制等 。 下 面介 绍个 主要 方法 : 1)assignPrimaryDatanode 方法 INodeFileUnderConstruction 类 中实 现的 assignPrimaryDatanode 方 法, 能够 将 该 INodeFileUnderConstruction 文 件分 配给 指定 的客 户端 进程 ,也 就是 执行 租约 恢复 的操 作 , 并 通过 setLastRecoveryTime 更 新最 后租 约恢 复时 间 lastRecoveryTime。 下面 是 assignPrimaryDatanode 方 法的 实现 : view plain 1./** 2.*为该文件初始化租约的恢复的处理(存储选择的主 Datanode所激活的块列表) 3.*/ 4.voidvoidvoidvoidassignPrimaryDatanode(){ 5.//指派第一个活跃的为主 Datanode结点 6.ifififif(targets.length==0){ 7.NameNode.stateChangeLog.warn("BLOCK*"+"INodeFileUnderConstruction.in itLeaseRecovery:" 8."Noblocksfound,leaseremoved."); 9.} 10. 11.intintintintprevious=primaryNodeIndex; 12.//从索引 previous开始查找到一个活跃的 Datanode进程 13.forforforfor(intintintinti=1;i<=targets.length;i++){ 14.intintintintj=(previous+i)%targets.length; 15.ifififif(targets[j].isAlive){//保证第 j个Datanode处于活跃状态 16.DatanodeDescriptorprimary=targets[primaryNodeIndex=j]; 17.primary.addBlockToBeRecovered(blocks[blocks.length-1],targets);// 存储被主 Datanode激活的块,实际存储到该 Datanode的块队列中 18.NameNode.stateChangeLog.info("BLOCK*"+blocks[blocks.length-1]+" recoverystarted,primary="+primary); 19.returnreturnreturnreturn; 20.} 21.} 22.} 2)removeBlock 方法 该 类的 removeBlock 方 法从 该文 件的 块列 表中 删除 一个 块, 并且 只能 删除 列表 中的 最后 一 个 块。 实现 如下 所示 : view plain 1.voidvoidvoidvoidremoveBlock(Blockoldblock)throwsthrowsthrowsthrowsIOException{ 2.ifififif(blocks==nullnullnullnull){ 3.throwthrowthrowthrownewnewnewnewIOException("Tryingtodeletenon-existantblock"+oldblock); 4.} 5.intintintintsize_1=blocks.length-1; 6.ifififif(!blocks[size_1].equals(oldblock)){ 7.throwthrowthrowthrownewnewnewnewIOException("Tryingtodeletenon-lastblock"+oldblock); 8.} 9. 10.BlockInfo[]newlist=newnewnewnewBlockInfo[size_1];//创建一个新的块列表 (比原来的块 列表小 1) 11.System.arraycopy(blocks,0,newlist,0,size_1);//将原来的块列表中除去最后 一个块以外的全部块,拷贝到新的块列表中 12.blocks=newlist;//修改当前文件的块列表 13.targets=nullnullnullnull;//因为最后一个块删除了,该块对应的存储位置信息也不存在了 14.} 3)convertToInodeFile 方法 该 方法 将一 个 INodeFileUnderConstruction 文 件转 化为 INodeFile 文 件, 如下 所示 : view plain 1.INodeFileconvertToInodeFile(){ 2.INodeFileobj=newnewnewnewINodeFile(getPermissionStatus(), 3. getBlocks(), 4. getReplication(), 5. getModificationTime(), 6. getModificationTime(), 7. getPreferredBlockSize()); 8.returnreturnreturnreturnobj; 9.} • FSDirectory 类 该类org.apache.hadoop.hdfs.server.namenode.FSDirectory 用 来存 储文 件系 统目 录的 状 态。它 处理 向磁 盘中 写入 或加 载数 据 ,并 且对 目录 中的 数据 发生 的改 变记 录到 日志 中 。它保 存 了一 个最 新的 filename->blockset 的 映射 表, 并且 将它 写入 到磁 盘中 。 该 类定 义的 属性 如下 所示 : view plain 1.finalfinalfinalfinalFSNamesystemnamesystem;//文件系统命名空间系统实例 2.finalfinalfinalfinalINodeDirectoryWithQuotarootDir;//具有配额限制的目录 INode,这里即是根目 录 3.FSImagefsImage;//FSImage映像 4.privateprivateprivateprivatebooleanbooleanbooleanbooleanready=falsefalsefalsefalse;//该目录是否准备好处理 writing/loading到磁盘 5.privateprivateprivateprivateMetricsRecorddirectoryMetrics=nullnullnullnull;//目录元数据记录实体 该 类构 造方 法如 下所 示: view plain 1.FSDirectory(FSNamesystemns,Configurationconf){ 2.thisthisthisthis(newnewnewnewFSImage(),ns,conf); 3.fsImage.setCheckpointDirectories(FSImage.getCheckpointDirs(conf,nullnullnullnull),FS Image.getCheckpointEditsDirs(conf,nullnullnullnull)); 4.} 5. 6.FSDirectory(FSImagefsImage,FSNamesystemns,Configurationconf){ 7.rootDir=newnewnewnewINodeDirectoryWithQuota(INodeDirectory.ROOT_NAME,ns.createF sOwnerPermissions(newnewnewnewFsPermission((shortshortshortshort)0755)),Integer.MAX_VALUE,-1);// 目录的权限设为 755(drwxrw-rw-) 8.thisthisthisthis.fsImage=fsImage;//后面会详细分析 FSImage映像类的 9.namesystem=ns; 10.initialize(conf);//调用,根据配置类实例 conf初始化 directoryMetrics 11.} 通 过上 面的 FSDirectory 的 构造 可以 看出 ,通 过 FSNamesystem ns 访 问一 个已 经存 在 的 DFS 的 命名 空间 系统 目录 ,为 FSDirectory 的 根目 录 rootDir 设 置访 问权 限。 下 面介 绍 FSDirectory 类 的方 法, 选择 几个 重要 的方 法详 细分 析: 1、 加载 FSImage 映像 方法loadFSImage 实 现如 下所 示: view plain 1.voidvoidvoidvoidloadFSImage(CollectiondataDirs,CollectioneditsDirs,Star tupOptionstartOpt)throwsthrowsthrowsthrowsIOException{ 2.//根据 Hadoopservers启动选项进行操作 3.ifififif(startOpt==StartupOption.FORMAT){//如果启动选项类型为 FORMAT(格式化 ), 在启动之前需要进行格式化 4.fsImage.setStorageDirectories(dataDirs,editsDirs);//设置 FSImage映像文 件文件的存储目录 5.fsImage.format();//对FSImage执行格式化操作 6.startOpt=StartupOption.REGULAR;//动态修改启动选项 REGULAR(正常启动) 7.} 8.trytrytrytry{ 9.ifififif(fsImage.recoverTransitionRead(dataDirs,editsDirs,startOpt)){// 根据启动选项及其对应存储目录,分析存储目录,必要的话从先前的事务恢复过来 10.fsImage.saveFSImage();//保存 FSImage映像文件内容 ,并创建一个空的 edits文 件 11.} 12.FSEditLogeditLog=fsImage.getEditLog();//获取到存 FSImage映像对应 的 EditLog文件 13.assertassertassertasserteditLog!=nullnullnullnull:"editLogmustbeinitialized"; 14.ifififif(!editLog.isOpen()) 15.editLog.open();//打开 EditLog文件 16.fsImage.setCheckpointDirectories(nullnullnullnull,nullnullnullnull);//设置检查点存储目录 17.}catchcatchcatchcatch(IOExceptione){ 18.fsImage.close(); 19.throwthrowthrowthrowe; 20.} 21.synchronizedsynchronizedsynchronizedsynchronized(thisthisthisthis){ 22.thisthisthisthis.ready=truetruetruetrue;//设置当前 FSDirectory状态 23.thisthisthisthis.notifyAll();//通知阻塞在该 FSDirectory对象上的全部其它线程 24.} 25.} 通 过该 方法 ,我 们可 以看 到加 载一 个 FSImage 映 像的 过程 :首 先需 要对 内存 中的 FSImage 对 象进 行格 式化 ;然 后从 将指 定存 储目 录中 的 EditLog 日 志文 件作 用到 格式 化完 成 的 FSImage 内 存映 像上 ;最 后需 要再 创建 一个 空的 EditLog 日 志准 备记 录对 命名 空间 进行 修 改 的操 作 ,以 备检 查点 进程 根据 需要 将EditLog 内 容作 用 到FSImage 映 像上 ,保持FSImage 总 是最 新的 ,保 证 EditLog 与FSImage 同 步。 2、 更新 INode 文 件计 数 实 现的 方法 为 updateCount, 如下 所示 : view plain 1./** 2.*批量更新:更新具有配额限制的每一个 INode的计数 3.* 4.*@paraminodes某个 Path下的 INode数组 5.*@paramnumOfINodes需要更新的 INode的数量(从数组 inodes的索引 0开始计数) 6.*@paramnsDelta文件系统命名空间大小的改变量 7.*@paramdsDelta磁盘空间大小的改变量 8.*/ 9.privateprivateprivateprivatevoidvoidvoidvoidupdateCount(INode[]inodes,intintintintnumOfINodes,longlonglonglongnsDelta,longlonglonglong dsDelta)throwsthrowsthrowsthrowsQuotaExceededException{ 10.ifififif(!ready){ 11.returnreturnreturnreturn; 12.} 13.ifififif(numOfINodes>inodes.length){//检查 numOfINodes,当大于 inodes数组大小时 , 设置为 inodes数组大小 14.numOfINodes=inodes.length; 15.} 16.//checkexistingcomponentsinthepath 17.intintintinti=0; 18.trytrytrytry{ 19.forforforfor(;i0;){ 29.trytrytrytry{ 30.ifififif(inodes[i].isQuotaSet()){ 31.INodeDirectoryWithQuotanode=(INodeDirectoryWithQuota)inodes[i]; 32.node.updateNumItemsInTree(-nsDelta,-dsDelta); 33.} 34.}catchcatchcatchcatch(IOExceptioningored){ 35.} 36.} 37.throwthrowthrowthrowe; 38.} 39.} 文 件系 统中 的 INode( 目录 或文 件) 可能 因为 在执 行计 算任 务过 程中 ,某 个 INode(树)的 内 容发 生变 化, 为保 证 HDFS 中 文件 管理 的一 致性 ,在 必要 的时 候需 要更 新 INode 的 统计 数 据。 3、 向该 目录 中添 加一 个孩 子 INode 实 现方 法为 addChild, 如下 所示 : view plain 1.privateprivateprivateprivateTaddChild(INode[]pathComponents,intintintintpos,Tchil d,longlonglonglongchildDiskspace,booleanbooleanbooleanbooleaninheritPermission)throwsthrowsthrowsthrowsQuotaExceededExcep tion{ 2.INode.DirCountscounts=newnewnewnewINode.DirCounts(); 3.child.spaceConsumedInTree(counts);//更新 counts对象(该对象包含 child在目录 树中 INode名字的数量与占用磁盘空间) 4.ifififif(childDiskspace<0){ 5.childDiskspace=counts.getDsCount();//获取 child的磁盘空间大小 6.} 7.updateCount(pathComponents,pos,counts.getNsCount(),childDiskspace);// 更新 pathComponents数组从 0到pos-1位置的每一个 INode的统计计数 8.TaddedNode=((INodeDirectory)pathComponents[pos-1]).addChild(child,inhe ritPermission);//将child添加到 ((INodeDirectory)pathComponents[pos-1])目录 中,并返回 child结点 9.ifififif(addedNode==nullnullnullnull){//如果 ((INodeDirectory)pathComponents[pos-1])中已 经存在 child结点 10.updateCount(pathComponents,pos,-counts.getNsCount(),-childDiskspace); //回滚上述更新操作 11.} 12.returnreturnreturnreturnaddedNode;//返回添加到该目录中的 INode 13.} 4、向namespace 中 添加 一个 INode 实 现的 方法 为 addNode, 如下 所示 : view plain 1./** 2.*将node添加到 namespace中, node的完整路径为 src,如果该 node磁盘空 间 childDiskspace未知则应该为 -1 3.*/ 4.privateprivateprivateprivateTaddNode(Stringsrc,Tchild,longlonglonglongchildDiskspace, booleanbooleanbooleanbooleaninheritPermission)throwsthrowsthrowsthrowsQuotaExceededException{ 5.bytebytebytebyte[][]components=INode.getPathComponents(src);//将路 径src转换 为UTF-8 编码的字节数组 6.child.setLocalName(components[components.length-1]);//为child设置本地文件 名称 7.INode[]inodes=newnewnewnewINode[components.length];//分配一 个components.length 大小的 INode[] 8.synchronizedsynchronizedsynchronizedsynchronized(rootDir){ 9.rootDir.getExistingPathINodes(components,inodes); 10.returnreturnreturnreturnaddChild(inodes,inodes.length-1,child,childDiskspace,inheritP ermission); 11.} 12.} 上 面调 用了 INodeDirectory 类的getExistingPathINodes 方 法, 这里 说明 一下 该方 法。 例 如, 给定 一个 路径 /c1/c2/c3, 其中 只有 /c1/c2 是 存在 的, 而 /c3 不 存在 ,则 得到 这样 一个 字 节数 组 ["","c1","c2","c3"]。 如 果想 要执 行调 用getExistingPathINodes(["","c1","c2"], [?]),则 应该 使用 [c2]填 充占 位数 组 ; 如 果想 要执 行调 用 getExistingPathINodes(["","c1","c2","c3"], [?]), 则应 该使 用 [null]填 充占 位 数组 ; 如 果想 要执 行调 用 getExistingPathINodes(["","c1","c2"], [?,?]),则 应该 使用 [c1,c2]填 充占 位 数 组; 如 果想 要执 行调 用 getExistingPathINodes(["","c1","c2","c3"], [?,?]), 则应 该使 用 [c2,null]填 充 占位 数组 ; 如 果想 要执 行调 用 getExistingPathINodes(["","c1","c2"], [?,?,?,?]), 则应 该使 用 [rootINode,c1,c2,null]填 充占 位数 组; 如 果想 要执 行调 用 getExistingPathINodes(["","c1","c2","c3"], [?,?,?,?]), 则应 该使 用 [rootINode,c1,c2,null]填 充占 位数 组。 对 应于 上面 方法 中 ,对getExistingPathINodes 方 法的 调用 ,指 定一 个完 整路 径 components ( 例如 上面 的 src 转 化后 得到 的 components 数组),执 行调 用后 ,会 根据 上述 举例 中的 规 则 来对 inodes 数 组进 行填 充。 得到 一个 inodes 数 组以 后, 就可 以调 用 addChild 方 法向 该 目录FSDirectory 中 添加 一个 child。 5、 向文 件系 统中 添加 一个 文件 实 现的 方法 为 addFile, 如下 所示 : view plain 1.INodeFileUnderConstructionaddFile( 2.Stringpath, 3.PermissionStatuspermissions, 4.shortshortshortshortreplication, 5.longlonglonglongpreferredBlockSize, 6.StringclientName, 7.StringclientMachine, 8.DatanodeDescriptorclientNode, 9.longlonglonglonggenerationStamp)throwsthrowsthrowsthrowsIOException{ 10.waitForReady();//等待该目录已经准备好,能够被使用 11.longlonglonglongmodTime=FSNamesystem.now();//取当前时间 12.ifififif(!mkdirs(newnewnewnewPath(path).getParent().toString(),permissions,truetruetruetrue,modT ime)){//创建 path的父目录 13.returnreturnreturnreturnnullnullnullnull; 14.} 15.INodeFileUnderConstructionnewNode=newnewnewnewINodeFileUnderConstruction( 16. permissions,replication, 17. preferredBlockSize,modTime,clientName, 18. clientMachine,clientNode);//创建一个新 的 INode文件 19.synchronizedsynchronizedsynchronizedsynchronized(rootDir){ 20.newNode=addNode(path,newNode,-1,falsefalsefalsefalse);//将newNode加入到 namespace 中去 21.} 22.ifififif(newNode==nullnullnullnull){//添加失败 23.NameNode.stateChangeLog.info("DIR*FSDirectory.addFile:"+"failedtoad d"+path 24."tothefilesystem"); 25.returnreturnreturnreturnnullnullnullnull; 26.} 27.fsImage.getEditLog().logOpenFile(path,newNode);//将namespace中新添 加 INode的事务写入到 FSImage对应的 EditLog日志文件中 28.NameNode.stateChangeLog.debug("DIR*FSDirectory.addFile:"+path+"isadd edtothefilesystem"); 29.returnreturnreturnreturnnewNode; 30.} 通 过该 方法 ,我 们了 解到 ,当 向 namespace 中 添加 一个 文件 的时 候, 需要 通过 FSImage 映 像获 取到 其所 对应 的 EditLog 日 志文 件, 将对 使 namespace 发 生改 变的 事务 记录 下来 。 只 要当 对 namespace 执 行的 操作 生效 的时 候 ,才 会被 记录 到 EditLog 日 志文 件中 ,如 果失 败 的话 是不 会登 陆日 志的 。 6、 向指 定文 件中 写入 块( Block) 如 下所 示: view plain 1.BlockaddBlock(Stringpath,INode[]inodes,Blockblock)throwsthrowsthrowsthrowsIOException { 2.waitForReady(); 3.synchronizedsynchronizedsynchronizedsynchronized(rootDir){ 4.INodeFilefileNode=(INodeFile)inodes[inodes.length-1];//inodes数组 中最后一个 INodeFile 5.//检查配额限制,更新空间用量 6.updateCount(inodes,inodes.length-1,0,fileNode.getPreferredBlockSize() *fileNode.getReplication()); 7. 8.//associatethenewlistofblockswiththisfile 9.namesystem.blocksMap.addINode(block,fileNode);//将该块 block加入 到 namesystem所维护的映射表 blocksMap中的 fileNode文件中去 10.BlockInfoblockInfo=namesystem.blocksMap.getStoredBlock(block);//获取 到namesystem所维护的 blocksMap映射表中, block块的信息 blockInfo 11.fileNode.addBlock(blockInfo);//将blockInfo信息添加到 fileNode文件中 12. 13.NameNode.stateChangeLog.debug("DIR*FSDirectory.addFile:" 14. +path+"with"+block 15. +"blockisaddedtothein-memory" 16. +"filesystem"); 17.} 18.returnreturnreturnreturnblock; 19.} 可见,每 当需 要向 目录 中写 入块 (Block)的 时候 ,都 需要 向 FSNamesystem 的blocksMap 映 射表 中登 记 ,同 时通 过从 FSNamesystem 的blocksMap 映 射表 中获 取待 写入 块已 经存 在 的 信息 ,一 同写 入到 该目 录中 该块 所属 的文 件中 去。 7、 需要 写入 EditLog 日 志文 件的 操作 这 里, 对 FSDirectory 类 中实 现的 ,与 namespace 相 关的 需要 写入 到 EditLog 日 志文 件的 事 务进 行总 结, 给出 具体 的操 作说 明。 一 共涉 及到 12 个 操作 ,执 行这 些操 作的 时候 ,需 要登 录到 EditLog 日 志中 ,如 下所 示: view plain 1./** 2.*将文件添加到文件系统中 3.*/ 4.INodeFileUnderConstructionaddFile( 5.Stringpath, 6.PermissionStatuspermissions, 7.shortshortshortshortreplication, 8.longlonglonglongpreferredBlockSize, 9.StringclientName, 10.StringclientMachine, 11.DatanodeDescriptorclientNode, 12.longlonglonglonggenerationStamp)throwsthrowsthrowsthrowsIOException; 13. 14./** 15.*将一个文件对应的块列表持久化到文件系统 16.*/ 17.voidvoidvoidvoidpersistBlocks(Stringpath,INodeFileUnderConstructionfile)IOException ; 18. 19./** 20.*关闭文件 21.*/ 22.voidvoidvoidvoidcloseFile(Stringpath,INodeFilefile)throwsthrowsthrowsthrowsIOException; 23. 24./** 25.*删除指定文件的某个块 26.*/ 27.booleanbooleanbooleanbooleanremoveBlock(Stringpath,INodeFileUnderConstructionfileNode,Block block)throwsthrowsthrowsthrowsIOException; 28. 29./** 30.*文件重命名 31.*/ 32.booleanbooleanbooleanbooleanrenameTo(Stringsrc,Stringdst)throwsthrowsthrowsthrowsQuotaExceededException; 33. 34./** 35.*为指定文件设置副本因子 36.*/ 37.Block[]setReplication(Stringsrc,shortshortshortshortreplication,intintintint[]oldReplication) throwsthrowsthrowsthrowsIOException; 38. 39./** 40.*为指定文件设置权限 41.*/ 42.voidvoidvoidvoidsetPermission(Stringsrc,FsPermissionpermission)throwsthrowsthrowsthrowsIOException; 43. 44./** 45.*设置文件属主 46.*/ 47.voidvoidvoidvoidsetOwner(Stringsrc,Stringusername,Stringgroupname)throwsthrowsthrowsthrowsIOExcept ion; 48. 49./** 50.*删除文件 51.*/ 52.INodedelete(Stringsrc); 53. 54./** 55.*创建目录 56.*/ 57.booleanbooleanbooleanbooleanmkdirs(Stringsrc,PermissionStatuspermissions,booleanbooleanbooleanbooleaninheritPerm ission,longlonglonglongnow)throwsthrowsthrowsthrowsFileNotFoundException,QuotaExceededException; 58. 59./** 60.*为指定目录设置配额 61.*/ 62.voidvoidvoidvoidsetQuota(Stringsrc,longlonglonglongnsQuota,longlonglonglongdsQuota)throwsthrowsthrowsthrowsFileNotFoundExc eption,QuotaExceededException; 63. 64./** 65.*设置一个文件的访问时间 66.*/ 67.voidvoidvoidvoidsetTimes(Stringsrc,INodeFileinode,longlonglonglongmtime,longlonglonglongatime,booleanbooleanbooleanbooleanf orce)throwsthrowsthrowsthrowsIOException; 对于FSDirectory 类,我 们就 分析 这么 多 。通 过上 面分 析 ,我 们知 道了 FSDirectory 类 主要 是 管理 对于 属于 一个 FSDirectory 类 目录 实例 的文 件的 基本 操作 ,而 一个 FSDirectory 类是 位 于文 件系 统中 的 ,对 于指 定的 文件 进行 的操 作都 由 FSDirectory 类 来管 理维 护 ,并 对特 定 的 事务 写入 到 EditLog 日 志文 件中 。 继 续为 分析 org.apache.hadoop.hdfs.server.namenode.FSNamesystem 类 做准 备 ,这 里分 析与FSEditLog 相 关的 几个 类, 当然 , FSEditLog 类 才是 核心 的。 • FSEditLog.EditLogFileOutputStream 内 部静 态类 该 类是 定义 在 org.apache.hadoop.hdfs.server.namenode.FSEditLog 类 内部 的静 态类 ,表 示将EditLog 日 志通 过打 开一 个该 输出 流实 例写 入到 本地 磁盘 。该 输出 流类 的继 承层 次结 构 如 下所 示: view plain 1.◦java.io.OutputStream(implementsimplementsimplementsimplementsjava.io.Closeable,java.io.Flushable) 2.◦org.apache.hadoop.hdfs.server.namenode.EditLogOutputStream 3.◦org.apache.hadoop.hdfs.server.namenode.FSEditLog.EditLogFileOutpu tStream 首 先, 看 EditLogOutputStream 这 个抽 象类 的定 义。 EditLogOutputStream 类 是一 个用 来支 持将 EditLog 日 志文 件中 内容 持久 化 (写入)到 存储 目 录中 的通 用抽 象类 。该 抽象 类定 义了 两个 统计 变量 ,如 下所 示: view plain 1.privateprivateprivateprivatelonglonglonglongnumSync;//对EditLog日志文件执行同步操作(同步写入磁盘)的 次数 2.privateprivateprivateprivatelonglonglonglongtotalTimeSync;//同步 EditLog日志文件到磁盘,所花费的总累加时间 该 抽象 类中 实现 的方 法都 是与 更新 统计 变量 相关 的, 如下 所示 : view plain 1./** 2.*将数据持久化到存储目录,保持数据同步一致,同时刷新统计数据 3.*/ 4.publicpublicpublicpublicvoidvoidvoidvoidflush()throwsIOException{ 5.numSync++;//执行该方法一次,则同步数据统计变量加 1 6.longlonglonglongstart=FSNamesystem.now();//此次开始同步的时刻(用来计算此次同步所花费 的时间,记入对应统计变量中) 7.flushAndSync();//调用刷新与执行同步的操作 8.longlonglonglongend=FSNamesystem.now();//此次同步结束时间 9.totalTimeSync+=(end-start);//计算此次同步花费时间,累加到统计变 量 totalTimeSync上 10.} 11. 12./** 13.*获取执行 flushAndSync()同步方法花费的总时间 14.*/ 15.longlonglonglonggetTotalSyncTime(){ 16.returnreturnreturnreturntotalTimeSync; 17.} 18. 19./** 20.*获取调用 flushAndSync()方法执行同步的次数 21.*/ 22.longlonglonglonggetNumSync(){ 23.returnreturnreturnreturnnumSync; 24.} 该 抽象 类中 定义 了一 些抽 象方 法, 这些 方法 需要 在该 类的 子类 中给 出具 体实 现: view plain 1./** 2.*获取该 EditLogOutputStream输出流的名称 3.*/ 4.abstractabstractabstractabstractStringgetName(); 5. 6./** 7.*该方法继承自 OutputStream,表示向该输出流对象中写入字节 8.*/ 9.abstractabstractabstractabstractpublicpublicpublicpublicvoidvoidvoidvoidwrite(intintintintb)throwsthrowsthrowsthrowsIOException; 10. 11./** 12.*将EditLog日志记录写入到该输出流对象中 13.*(日志记录通过操作名称和一个 Writable参数的数组来表征) 14.*/ 15.abstractabstractabstractabstractvoidvoidvoidvoidwrite(bytebytebytebyteop,Writable...writables)throwsthrowsthrowsthrowsIOException; 16. 17./** 18.*创建并初始化一个新的 EditLog日志存储对象 19.*/ 20.abstractabstractabstractabstractvoidvoidvoidvoidcreate()throwsthrowsthrowsthrowsIOException; 21. 22./**{@inheritDoc}*/ 23.abstractabstractabstractabstractpublicpublicpublicpublicvoidvoidvoidvoidclose()throwsthrowsthrowsthrowsIOException; 24. 25./** 26.*所有已经被写入到该输出流对象中的数据,将准备对其执行刷新( flush)操作。 27.*(在执行刷新的过程中,新的数据仍然能够被写入到该输出流对象中) 28.*/ 29.abstractabstractabstractabstractvoidvoidvoidvoidsetReadyToFlush()throwsthrowsthrowsthrowsIOException; 30. 31./** 32.*Flush并且 sync所有准备好的数据(调用了 setReadyToFlush()方法表示准备),持久化 到相应的(持久)存储中 33.*/ 34.abstractabstractabstractabstractprotectedprotectedprotectedprotectedvoidvoidvoidvoidflushAndSync()throwsthrowsthrowsthrowsIOException; 35. 36./** 37.*获取当前 EditLog日志文件的大小 38.*得到 EditLog日志文件的长度是为了检查日志文件是否大到需要启动一个检查点进程来处 理 39.*/ 40.abstractabstractabstractabstractlonglonglonglonglength()throwsthrowsthrowsthrowsIOException; 通 过对 EditLogOutputStream 抽 象类 的分 析 ,我 们获 知 :一个EditLog 日 志文 件也 是通 过流 式 进行 写的 ,而 且定 时对 日志 文件 进行 同步 ,写 入持 久存 储中 ,其 中支 持写 入字 节和 Writable 类 型数 据 ;在 同步 的过 程中 ,需 要记 录同 步统 计数 据 ;当EditLog 日 志文 件达 到一 定大 小的 时 候, 需要 启动 检查 点进 程来 进行 处理 。 然 后, 我们 看 FSEditLog.EditLogFileOutputStream 内 部静 态类 的具 体实 现。 该 类继 承自 EditLogOutputStream 抽 象类 ,实 现了 该抽 象类 中定 义的 抽象 方法 ,能 够在 一 个 本地 文件 ( local file) 中存 储 EditLog 日 志文 件。 该 类定 义了 如下 属性 : view plain 1.privateprivateprivateprivateFilefile;//EditLog日志文件记录所存储的本地文件 2.privateprivateprivateprivateFileOutputStreamfp;//存储 EditLog日志记录的文件输出流对象 3.privateprivateprivateprivateFileChannelfc;//执行同步操作( sync)的文件输出流对象对应的通 道 4.privateprivateprivateprivateDataOutputBufferbufCurrent;//当前用于写操作的数据缓冲区 5.privateprivateprivateprivateDataOutputBufferbufReady;//为了执行写操作而准备的数据缓冲区 6.staticstaticstaticstaticByteBufferfill=ByteBuffer.allocateDirect(512);//预分配大小为 512的 字节缓冲区 下 面是 FSEditLog.EditLogFileOutputStream 类 的构 造方 法: view plain 1.EditLogFileOutputStream(Filename)throwsthrowsthrowsthrowsIOException{ 2.supersupersupersuper(); 3.file=name;//EditLog日志文件对应的本地文件名称 4.bufCurrent=newnewnewnewDataOutputBuffer(sizeFlushBuffer);//分配 sizeFlushBuffer=512*1024 5.bufReady=newnewnewnewDataOutputBuffer(sizeFlushBuffer);//分配 sizeFlushBuffer=512*1024 6.RandomAccessFilerp=newnewnewnewRandomAccessFile(name,"rw");//根据文 件 Filename创建一个随机读写文件实例 7.fp=newnewnewnewFileOutputStream(rp.getFD());//为向 fp输出流对象中追加数据而打开该 输出流 8.fc=rp.getChannel();//获取 fp对应的通道 9.fc.position(fc.size());//设置输出流通道对应文件流的当前位置 10.} 通 过上 面的 构造 方法 ,可 见通 过构 造方 法已 经准 备好 了写 EditLog 日 志文 件的 条件 ,定位到 EditLog 对 应于 本地 存储 文件 流的 当前 写入 位置 ,也 就是 说对 日志 文件 的操 作时 追加 写操 作 。 下 面看 该类 中实 现的 方法 : view plain 1.@Override 2.StringgetName(){ 3.returnreturnreturnreturnfile.getPath();//返回 EditLog日志文件对应的本地存储文件的路径 4.} 5. 6.@Override 7.publicpublicpublicpublicvoidvoidvoidvoidwrite(intintintintb)throwsthrowsthrowsthrowsIOException{ 8.bufCurrent.write(b);//将字节写入当前数据缓冲区 bufCurrent 9.} 10. 11.@Override 12.voidvoidvoidvoidwrite(bytebytebytebyteop,Writable...writables)throwsthrowsthrowsthrowsIOException{ 13.write(op);//调用重载的 write方法,写入操作名称(其实是一个操作代码) 14.forforforfor(Writablew:writables){//迭代 Writable对象数组 15.w.write(bufCurrent);//分别写入到前数据缓冲区 bufCurrent 16.} 17.} 18. 19./** 20.*创建一个空的 EditLog日志文件 21.*/ 22.@Override 23.voidvoidvoidvoidcreate()throwsthrowsthrowsthrowsIOException{ 24.fc.truncate(0);//设置当前文件的大小 (如果文件大小大于 0,删除文件中的全部字节数 据) 25.fc.position(0);//设置当前写位置(初始化) 26.bufCurrent.writeInt(FSConstants.LAYOUT_VERSION);//在EditLog日志文件最前面 写入版本号 27.setReadyToFlush();//设置准备刷新状态 28.flush();//执行刷新操作(将版本号写入到输出流中) 29.} 30. 31.@Override 32.publicpublicpublicpublicvoidvoidvoidvoidclose()throwsthrowsthrowsthrowsIOException{ 33.//在所有追加写事务执行 flush与sync操作完成以后,该方法被调用 34.intintintintbufSize=bufCurrent.size(); 35.ifififif(bufSize!=0){ 36.throwthrowthrowthrownewnewnewnewIOException("FSEditStreamhas"+bufSize+"bytesstillto beflushedandcannot"+"beclosed."); 37.} 38.bufCurrent.close();//关闭当前数据缓冲区 39.bufReady.close();//关闭预执行 flush准备数据缓冲区 40. 41.fc.truncate(fc.position());//从事务日志中删除最后面的 OP_INVALID标记(因为在 调用 setReadyToFlush方法结束时,写入了 OP_INVALID标记) 42.fp.close();//关闭 EditLog日志文件对应的本地存储文件输出流对象 43. 44.bufCurrent=bufReady=nullnullnullnull;//释放 45.} 46. 47./** 48.*所有已经被写入到该输出流对象中的数据,将准备对其执行刷新( flush)操作。 49.*(在执行刷新操作的过程中,新的数据仍然能够被写入到该输出流对象中) 50.*/ 51.@Override 52.voidvoidvoidvoidsetReadyToFlush()throwsthrowsthrowsthrowsIOException{ 53.assertassertassertassertbufReady.size()==0:"previousdataisnotflushedyet"; 54.write(OP_INVALID);//在文件末尾写入一个标记 OP_INVALID 55.DataOutputBuffertmp=bufReady;//切换缓冲区:将当前的 bufCurrent切换为准备 好要执行 flush操作的 bufReady 56.bufReady=bufCurrent; 57.bufCurrent=tmp; 58.} 59. 60./** 61.*将bufCurrent中数据到持久存储中 62.*currentBufferisnotflushedasitaccumulatesnewlogrecords 63.*whilereadyBufferwillbeflushedandsynced. 64.*/ 65.@Override 66.protectedprotectedprotectedprotectedvoidvoidvoidvoidflushAndSync()throwsthrowsthrowsthrowsIOException{ 67.preallocate();//如果必要的话,为文件预分配内存 68.bufReady.writeTo(fp);//将bufReady中数据写入到文件 69.bufReady.reset();//重启 bufReady数据缓冲区 70.fc.force(falsefalsefalsefalse);//因为调用 preallocate()方法对文件进行了预分配 ,也就 没必要更新该文件元数据信息 71.fc.position(fc.position()-1);//设置当前写入位置:回退排除文件末尾的标 记 OP_INVALID 72.} 73. 74./** 75.*获取当前 EditLog日志文件的大小(包括 bufReady与bufCurrent) 76.*/ 77.@Override 78.longlonglonglonglength()throwsthrowsthrowsthrowsIOException{ 79.returnreturnreturnreturnfc.size()+bufReady.size()+bufCurrent.size();//返回文件大 小=文件实际大小 +bufReady大小 +bufCurrent大小 80.} 81. 82.//为日志文件预分配一个大数据块 83.privateprivateprivateprivatevoidvoidvoidvoidpreallocate()throwsthrowsthrowsthrowsIOException{ 84.longlonglonglongposition=fc.position();//获取文件当前位置 85.ifififif(position+4096>=fc.size()){ 86.FSNamesystem.LOG.debug("PreallocatingEditlog,currentsize"+fc.size ()); 87.longlonglonglongnewsize=position+1024*1024;//1MB 88.fill.position(0);//预分配数据缓冲区 fill写入位置为 0 89.intintintintwritten=fc.write(fill,newsize);//为日志文件增加分配 newsize个字 节 90.FSNamesystem.LOG.debug("Editlogsizeisnow"+fc.size()+"written "+written+"bytes"+"atoffset"+newsize); 91.} 92.} 93. 94./** 95.*获取与该输出流相关联的文件 96.*/ 97.FilegetFile(){ 98.returnreturnreturnreturnfile; 99.} 通 过对 上面 方法 的阅 读分 析 ,可 以了 解到 ,EditLog 日 志文 件是 与本 地磁 盘上 的一 个文 件相 对 应的 ,实 际也 是写 入到 这个 本地 文件 中的 。在 执行 流式 追加 写的 过程 中 ,设 置了 两个 重要 的 数据 缓冲 区, bufReady 与bufCurrent, 通过 切换 这两 个数 据缓 冲区 来将 不断 追加 写入 到 EditLogFileOutputStream 流 对象 中的 数据 ,同 步到 本地 磁盘 文件 中。 而且 ,当 执 行 flushAndSync 方 法进 行同 步时 候, 如果 必要 会对 EditLog 日 志文 件对 应的 本地 文件 进行 预 分 配, 保证 可持 续做 好写 事务 操作 的记 录。 • FSEditLog.EditLogFileInputStream 内 部静 态类 该类org.apache.hadoop.hdfs.server.namenode.FSEditLog.EditLogFileInputStream 是对 EditLog 日 志文 件进 行读 取的 输入 流实 现类 。该 输入 流类 与 EditLogFileOutputStream 输出 流 类的 组织 关系 类似 ,它 的继 承层 次关 系如 下所 示: view plain 1.◦java.io.InputStream(implementsimplementsimplementsimplementsjava.io.Closeable) 2.◦org.apache.hadoop.hdfs.server.namenode.EditLogInputStream 3.◦org.apache.hadoop.hdfs.server.namenode.FSEditLog.EditLogFileInput Stream 首 先, 看抽 象流 类 EditLogInputStream。 该类 是一 个用 来支 持读 取本 地存 储目 录中 对应 的 文件,从 而得 到 EditLog 日 志文 件的 通用 抽象 类 。该 抽象 类比 较简 单 ,定 义获 取继 承了 一组 抽 象方 法, 如下 所示 : view plain 1./** 2.*获取该输入流的名称 3.*/ 4.abstractabstractabstractabstractStringgetName(); 5. 6./** 7.*估算大概存在多少字节可读的数据 8.*/ 9.publicpublicpublicpublicabstractabstractabstractabstractintintintintavailable()throwsthrowsthrowsthrowsIOException; 10. 11./** 12.*从该输入流中读取下一个字节的数据 13.*/ 14.publicpublicpublicpublicabstractabstractabstractabstractintintintintread()throwsthrowsthrowsthrowsIOException; 15. 16./** 17.*从该输入流 off位置读取 len个字节到字节数组 b中 18.*/ 19.publicpublicpublicpublicabstractabstractabstractabstractintintintintread(bytebytebytebyte[]b,intintintintoff,intintintintlen)throwsthrowsthrowsthrowsIOException; 20. 21./** 22.*关闭该输入流,释放相关资源 23.*/ 24.publicpublicpublicpublicabstractabstractabstractabstractvoidvoidvoidvoidclose()throwsthrowsthrowsthrowsIOException; 25. 26./** 27.*获取当前 EditLog日志文件的大小 28.*/ 29.abstractabstractabstractabstractlonglonglonglonglength()throwsthrowsthrowsthrowsIOException; 没 什么 可说 的, 继续 看 FSEditLog.EditLogFileInputStream 内 部静 态类 的具 体实 现吧 。 FSEditLog.EditLogFileInputStream 内 部静 态类 也比 较简 单, 就是 提供 了对 EditLog 日 志文 件 的读 取操 作, 也是 基于 流的 ,该 类源 代码 如下 所示 : view plain 1.staticstaticstaticstaticclassclassclassclassEditLogFileInputStreamextendsextendsextendsextendsEditLogInputStream{ 2.privateprivateprivateprivateFilefile;//EditLog日志文件对应的本地文件 3.privateprivateprivateprivateFileInputStreamfStream;//文件 file对应的输入流对象 4. 5.EditLogFileInputStream(Filename)throwsthrowsthrowsthrowsIOException{//构造方法 6.file=name; 7.fStream=newnewnewnewFileInputStream(name);//构造 file的输入流实例 8.} 9. 10.@Override 11.StringgetName(){//获取 file的路径 12.returnreturnreturnreturnfile.getPath(); 13.} 14. 15.@Override 16.publicpublicpublicpublicintintintintavailable()throwsthrowsthrowsthrowsIOException{ 17.returnreturnreturnreturnfStream.available();//获取从该输入流可以读取到的字节数 18.} 19. 20.@Override 21.publicpublicpublicpublicintintintintread()throwsthrowsthrowsthrowsIOException{ 22.returnreturnreturnreturnfStream.read();//从该输入流中读取一个字节的数据 23.} 24. 25.@Override 26.publicpublicpublicpublicintintintintread(bytebytebytebyte[]b,intintintintoff,intintintintlen)throwsthrowsthrowsthrowsIOException{ 27.returnreturnreturnreturnfStream.read(b,off,len);//将该输入流中从 off位置开始读取 len个字 节到字节数组 b中 28.} 29. 30.@Override 31.publicpublicpublicpublicvoidvoidvoidvoidclose()throwsthrowsthrowsthrowsIOException{ 32.fStream.close();//关闭该输入流 33.} 34. 35.@Override 36.longlonglonglonglength()throwsthrowsthrowsthrowsIOException{ 37.returnreturnreturnreturnfile.length();//返回文件大小 =文件实际大小 +bufReady大 小+bufCurrent大小 38.} 39.} • FSEditLog 类 该 类维 护一 个记 录对 文件 系统 命名 空间 进行 修改 操作 的日 志文 件。 我 们已 经对 FSEditLog.EditLogFileOutputStream 类与FSEditLog.EditLogFileInputStream 类 非常 熟悉 了, 它们 是与 EditLog 日 志文 件的 读写 密切 相关 的。 在FSEditLog 类 内部 还有 一个 实现 了 Writable 接 口的 可序 列化 内实 体 类 FSEditLog.BlockTwo, 它能 够从 以旧 格式 存储 的块 中读 写数 据, 如下 所示 : view plain 1.staticstaticstaticstaticclassclassclassclassBlockTwoimplementsimplementsimplementsimplementsWritable{ 2.longlonglonglongblkid;//块ID 3.longlonglonglonglen;//块长度 4. 5.staticstaticstaticstatic{//注册 6.WritableFactories.setFactory 7.(BlockTwo.classclassclassclass, 8.newnewnewnewWritableFactory(){ 9.publicpublicpublicpublicWritablenewInstance(){returnreturnreturnreturnnewnewnewnewBlockTwo();} 10.}); 11.} 12. 13. 14.BlockTwo(){ 15.blkid=0; 16.len=0; 17.} 18. 19.publicpublicpublicpublicvoidvoidvoidvoidwrite(DataOutputout)throwsthrowsthrowsthrowsIOException{ 20.out.writeLong(blkid); 21.out.writeLong(len); 22.} 23. 24.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin)throwsthrowsthrowsthrowsIOException{ 25.thisthisthisthis.blkid=in.readLong(); 26.thisthisthisthis.len=in.readLong(); 27.} 28.} 通 过一 个 BlockTwo 对 象, 能够 读写 块的 ID 和 块的 长度 。 另 一个 是事 务 ID 的 封装 内部 实体 类 FSEditLog.TransactionId,表示EditLog 日 志文 件记 录 的 修改 操作 这样 的事 务 ID,在FSEditLog 类 中主 要是 用来 作为 当前 执行 事务 的线 程对 应的 线 程局 部变 量的 拷贝 : ThreadLocal。 该类 如下 所示 : view plain 1.privateprivateprivateprivatestaticstaticstaticstaticclassclassclassclassTransactionId{ 2.publicpublicpublicpubliclonglonglonglongtxid; 3. 4.TransactionId(longlonglonglongvalue){ 5.thisthisthisthis.txid=value; 6.} 7.} FSEditLog.TransactionId 类 只封 装了 一个 事务 ID, 再没 有其 它内 容了 。 我 们再 看 FSEditLog 类。 首 先看 ,对 文件 系统 命名 空间 进行 不同 操作 的操 作代 码 ,通 过向 EditLog 日 志文 件中 写入 对 应 的操 作代 码来 表示 实际 的操 作, 这些 操作 代码 包含 : view plain 1.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_INVALID=-1;//非法操作 2.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_ADD=0;//添加文件操作 3.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_RENAME=1;//重命名 4.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_DELETE=2;//删除 5.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_MKDIR=3;//创建目录 6.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_SET_REPLICATION=4;//设置副本因子 7.//下面两个仅仅为了保证后向兼容 8.@DeprecatedprivateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_DATANODE_ADD=5; 9.@DeprecatedprivateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_DATANODE_REMOVE=6; 10.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_SET_PERMISSIONS=7;//设置权限 11.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_SET_OWNER=8;//设置属主 12.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_CLOSE=9;//写操作完成后的关闭操作 13.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_SET_GENSTAMP=10;//存储时间戳 14.//下面两个已经不再使用 ,如果 LAST_UPGRADABLE_LAYOUT_VERSION等于 -17或者更新 ,则可 以删除掉 15.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_SET_NS_QUOTA=11;//设置文件系统命名空间配额 16.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_CLEAR_NS_QUOTA=12;//清除文件系统命名空间配 额 17.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_TIMES=13;//为一个文件设置 mod&access时间 18.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalbytebytebytebyteOP_SET_QUOTA=14;//设置名称和磁盘配额 再 看该 类中 定义 的其 它属 性, 如下 所示 : view plain 1.privateprivateprivateprivatestaticstaticstaticstaticintintintintsizeFlushBuffer=512*1024;//数据缓冲区大小 ,主要是写日志文 件过程中为两个执行切换的数据缓冲区分配大小 2.privateprivateprivateprivateArrayListeditStreams=nullnullnullnull;//EditLogOutputS tream输出流对象列表 3.privateprivateprivateprivateFSImagefsimage=nullnullnullnull;//FsImage映像 4. 5.privateprivateprivateprivatelonglonglonglongtxid=0;//单调递增事务 ID计数器,用来为事务分配 ID的 6.privateprivateprivateprivatelonglonglonglongsynctxid=0;//最后执行 sync同步操作的事务 ID 7.privateprivateprivateprivatelonglonglonglonglastPrintTime;//最后一次将统计数据输出到日志文件的时间 8.privateprivateprivateprivatebooleanbooleanbooleanbooleanisSyncRunning;//是否当前正在执行 sync同步操作 9. 10.privateprivateprivateprivatelonglonglonglongnumTransactions;//事务数量统计变量 11.privateprivateprivateprivatelonglonglonglongnumTransactionsBatchedInSync;//批量 sync事务的数量的统计变量 12.privateprivateprivateprivatelonglonglonglongtotalTimeTransactions;//执行全部事务的时间统计变量 13.privateprivateprivateprivateNameNodeMetricsmetrics;//Namenode统计数据的统计变量 14. 15.//存储线程的当前事务 16.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalThreadLocalmyTransactionId=newnewnewnewThread Local(){ 17.protectedprotectedprotectedprotectedsynchronizedsynchronizedsynchronizedsynchronizedTransactionIdinitialValue(){ 18.returnreturnreturnreturnnewnewnewnewTransactionId(Long.MAX_VALUE); 19.} 20.}; 该 类的 构造 方法 如下 所示 : view plain 1.FSEditLog(FSImageimage){//通过 FSImage实例来构造, FSImage类会在之后分析 2.fsimage=image; 3.isSyncRunning=falsefalsefalsefalse;//当前没有进行同步 sync 4.metrics=NameNode.getNameNodeMetrics();//通过Namenode获取到其状态统计数据 对象,用来跟踪与 EditLog相关的信息(写入 Namenode的统计数据对象中) 5.lastPrintTime=FSNamesystem.now();//初始化时间 6.} 关 于一 个 FSEditLog 类 的实 例能 够做 哪些 事情 ,我 们从 该类 中挑 选几 个比 较关 键的 方法 来 详 细解 释说 明。 1、 创建 一个 EditLog 日 志文 件 如 下所 示: view plain 1.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidcreateEditLogFile(Filename)throwsthrowsthrowsthrowsIOException{ 2.EditLogOutputStreameStream=newnewnewnewEditLogFileOutputStream(name);//根据指 定的文件名,构造 EditLog日志文件的输出流 3.eStream.create();//创建 EditLog日志文件,并写入版本号 LAYOUT_VERSION,同时设 置当前待写入的位置 4.eStream.close();//关闭输出流 5.} 另 外, 还有 一个 用来 表示 当前 最新 的 edit 文件edit.new, 当该 文件 丢失 的时 候, 需要 进行 创 建, 该操 作对 应于 方法 createNewIfMissing, 如下 所示 : view plain 1.synchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidcreateNewIfMissing()throwsthrowsthrowsthrowsIOException{ 2.or(Iteratorit=fsimage.dirIterator(NameNodeDirType.EDIT S);it.hasNext();){//检查是否 edit.new文件丢失 3.FilenewFile=getEditNewFile(it.next()); 4.ifififif(!newFile.exists()) 5.createEditLogFile(newFile);//如果不存在,就调用上面的 createEditLogFile 创建一个 6.} 7.} 2、 打开 一个EditLog 日 志文 件 方法open 实 现了 打开 日志 文件 的功 能 。实 际上 ,当 创建 一个 FSEditLog 类 的实 例以 后 (通 过 构造 方法 构造 得到 ),调用open 方 法打 开一 个该 文件 的输 出流 ,准 备后 继向 流中 持续 追 加 日志 记录 。 open 方 法如 下所 示: view plain 1.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidopen()throwsthrowsthrowsthrowsIOException{ 2.numTransactions=totalTimeTransactions=numTransactionsBatchedInSync=0; //初始化这些统计变量 3.ifififif(editStreams==nullnullnullnull) 4.editStreams=newnewnewnewArrayList();//初始化该日志文件实 例的 EditLogOutputStream流对象列表 5.//通过 FSImage实例获取到 Namenode对应的 EDITS类型的存储目录的迭代器实例(因为 这是一组存储目录 ,势必对应用来 写EditLog日志的输出 流EditLogFileOutputStream列表 ) 6.//注意 :FSImage实例实际上只拥有一张登记了全部 EDITS类型的存储目录的表 ,至于该存 储目录中是否存在对应的文件,在加载的时候回检测出来,并采取相应的策略 7.forforforfor(Iteratorit=fsimage.dirIterator(NameNodeDirType.E DITS);it.hasNext();){ 8.StorageDirectorysd=it.next(); 9.FileeFile=getEditFile(sd);//获取一个 EditLog文件 10.trytrytrytry{ 11.EditLogOutputStreameStream=newnewnewnewEditLogFileOutputStream(eFile);// 打开该 EditLog文件输出流 12.editStreams.add(eStream);//加入列表 13.}catchcatchcatchcatch(IOExceptione){ 14.FSNamesystem.LOG.warn("Unabletoopeneditlogfile"+eFile); 15.it.remove();//如果处理该目录 sd发生异常,从存储目录列表中删除该目录 sd 16.} 17.} 18.} 调用open 方 法, 其实 是从 Namenode 来 加载 EditLog 日 志文 件所 对应 的全 部存 储目 录 ,并 打 开每 一个 与 EditLog 日 志文 件相 关的 本地 文件 的文 件输 出流 ,为 了便 于 FSEditLog 类实 例 管理 这些 输出 流, 使用 一个 ArrayList 来 存放 于内 存中 。 3、 处理 输出 流 IO 异常 通 过前 面分 析 ,一个FSEditLog 类 的实 例维 护一 个 EditLogOutputStream 输 出流 对象 列表 , 那 么当 某个 输出 流对 象发 生 IO 异 常的 时候 ,需 要对 其作 出处 理 ,而 不能 影响 其它 事务 的执 行。该 类中 给出 了三 个处 理 IO 异 常的 方法 ,如 下所 示: view plain 1./** 2.*如果发生与 EditLog日志相关的 IO异常,会从 editStreams列表中删除掉发生 IO异常的 输出流对象 3.*第一个:根据输出流对象在 editStreams列表中索引来处理 4.*/ 5.synchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidprocessIOError(intintintintindex); 6. 7./** 8.*如果发生与 EditLog日志相关的 IO异常,会从 editStreams列表中删除掉发生 IO异常的 输出流对象 9.*第二个:根据存储目录名称来处理 editStreams列表中对应的输出流对象 10.*/ 11.synchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidprocessIOError(StorageDirectorysd); 12. 13./** 14.*如果发生与 EditLog日志相关的 IO异常,会从 editStreams列表中删除掉发生 IO异常的 输出流对象 15.*第三个:对列表 editStreams中的输出流对象进行批量检查并对发生 IO异常的输出流对象 处理 16.*/ 17.privateprivateprivateprivatevoidvoidvoidvoidprocessIOError(ArrayListerrorStreams); 在FSEditLog 类 中处 理 IO 异 常的 时候 ,还 需要 将处 理的 情况 报告 到 FSImage 中 ,因 为 FSImage 也 维护 了一 个指 定为 要删 除的 存储 目录 列表 ,保 证数 据状 态的 同步 与一 致。 4、 加载 EditLog 日 志文 件 实 现的 方法 为 loadFSEdits 方 法, 因为 加载 一个 EditLog 日 志文 件的 时候 ,需 要将 对 应 EditLog 日 志文 件记 录内 容应 用到 一个 滞留 于内 存中 的结 构上 ,保 证内 存中 对应 结构 与日 志 同步。因 为该 方法 比较 重要 ,通 过它 能够 看到 如何 在日 志文 件与 其对 应的 内存 映像 之间 进行 切 换, 尽管 该方 法代 码比 较多 。我 还是 贴出 来分 析, 能够 更好 地看 到这 个过 程, 加深 理解 。 必 要的 时候 我会 对代 码行 修改 ,减 少显 示行 数。 loadFSEdits 方 法实 现如 下所 示: view plain 1.staticstaticstaticstaticintintintintloadFSEdits(EditLogInputStreamedits)throwsthrowsthrowsthrowsIOException{ 2.FSNamesystemfsNamesys=FSNamesystem.getFSNamesystem();//获取到一 个 FSNamesystem对象 3.FSDirectoryfsDir=fsNamesys.dir;//得到一个 fsNamesys.dir的引用 4.intintintintnumEdits=0,logVersion=0; 5.StringclientName=nullnullnullnull,clientMachine=nullnullnullnull,path=nullnullnullnull; 6.//这是一组统计变量 7.intintintintnumOpAdd=0,numOpClose=0,numOpDelete=0,numOpRename=0,numOpS etRepl=0,numOpMkDir=0, 8.numOpSetPerm=0,numOpSetOwner=0,numOpSetGenStamp=0,numOpTimes =0,numOpOther=0; 9.longlonglonglongstartTime=FSNamesystem.now();//EditLog日志文件开始加载的时间 10. 11.DataInputStreamin=newnewnewnewDataInputStream(newnewnewnewBufferedInputStream(edits)); //EditLog日志文件对应的输入流 12.trytrytrytry{ 13.in.mark(4);//设置 EditLog日志文件输入流当前位置,从而读取日志文件的版本(很 可能发生版本号信息丢失的情况) 14.//如果 EditLog日志文件大于 2G,调用 available方法将会返回一个负数,为了避免不 得不调用 available方法的情况,设置一个 Boolean变量,并指定其值为 true 15.booleanbooleanbooleanbooleanavailable=truetruetruetrue; 16.trytrytrytry{ 17.logVersion=in.readByte();//从输入流中读取版本号信息 18.}catchcatchcatchcatch(EOFExceptione){ 19.available=falsefalsefalsefalse;//发生异常则置于 available为false,表示当前 EditLog日 志文件不可用(可能日志文件存在问题) 20.} 21.ifififif(available){//日志文件可用 22.in.reset();//重置 23.logVersion=in.readInt();//读取版本号 24.ifififif(logVersion1){ 74.blockSize=blocks[0].getNumBytes(); 75.}elseelseelseelse{ 76.longlonglonglongfirst=((blocks.length==1)?blocks[0].getNumBytes():0); 77.blockSize=Math.max(fsNamesys.getDefaultBlockSize(),first); 78.} 79.} 80. 81.PermissionStatuspermissions=fsNamesys.getUpgradePermission(); 82.ifififif(logVersion<=-11){ 83.permissions=PermissionStatus.read(in);//读取权限 84.} 85. 86.//读取文件中最后一个块 的 clientname,clientMachineandblocklocations 87.ifififif(opcode==OP_ADD&&logVersion<=-12){ 88.clientName=FSImage.readString(in); 89.clientMachine=FSImage.readString(in); 90.ifififif(-13<=logVersion){ 91.readDatanodeDescriptorArray(in); 92.} 93.}elseelseelseelse{ 94.clientName=""; 95.clientMachine=""; 96.} 97. 98.//Theopenleasetransactionre-createsafileifnecessary. 99.ifififif(FSNamesystem.LOG.isDebugEnabled()){ 100.FSNamesystem.LOG.debug(opcode+":"+path+"numblocks:"+b locks.length+"clientHolder"+clientName+"clientMachine"+clientMa chine); 101.} 102.fsDir.unprotectedDelete(path,mtime);//从namespace中删除该文件,并 更新目录配额信息 103. 104.//将path添加到文件树 fsDir中 105.INodeFilenode=(INodeFile)fsDir.unprotectedAddFile(path,permissi ons,blocks,replication,mtime,atime,blockSize); 106.ifififif(opcode==OP_ADD){//如果是添加操作码 107.numOpAdd++;//统计 108.INodeFileUnderConstructioncons=newnewnewnewINodeFileUnderConstruction( 109.node.getLocalNameBytes(),node.getRepli cation(),node.getModificationTime(), 110.node.getPreferredBlockSize(),node.getB locks(),node.getPermissionStatus(), 111.clientName,clientMachine,nullnullnullnull); 112.fsDir.replaceNode(path,node,cons);//使用 cons替换 node 113.fsNamesys.leaseManager.addLease(cons.clientName,path);//将cons 加载到内存中,加入到租约管理器中 114.} 115.breakbreakbreakbreak; 116.} 117.casecasecasecaseOP_SET_REPLICATION:{//设置副本数操作 118.numOpSetRepl++;//统计 119.path=FSImage.readString(in);//读取文件 120.shortshortshortshortreplication=adjustReplication(readShort(in));//读取并调整 副本数 121.fsDir.unprotectedSetReplication(path,replication,nullnullnullnull);//更新目 录fsDir 122.breakbreakbreakbreak; 123.} 124.casecasecasecaseOP_RENAME:{//重命名操作 125.numOpRename++;//统计 126.intintintintlength=in.readInt();//读取长度 127.ifififif(length!=3){ 128.throwthrowthrowthrownewnewnewnewIOException("Incorrectdataformat."+"Mkdiroperatio n."); 129.} 130.Strings=FSImage.readString(in);//读取源文件名称 131.Stringd=FSImage.readString(in);//读取重命名后文件名称 132.timestamp=readLong(in);//读取重命名文件时间戳 133.FileStatusdinfo=fsDir.getFileInfo(d);//获取文件 d的描述信息 134.fsDir.unprotectedRenameTo(s,d,timestamp);//更新目录 fsDir 135.fsNamesys.changeLease(s,d,dinfo);//修改租约信息 136.breakbreakbreakbreak; 137.} 138.casecasecasecaseOP_DELETE:{//删除操作 139.numOpDelete++;//统计 140.intintintintlength=in.readInt();//删除文件名称长度 141.ifififif(length!=2){ 142.throwthrowthrowthrownewnewnewnewIOException("Incorrectdataformat."+"deleteoperati on."); 143.} 144.path=FSImage.readString(in);//读取长度 145.timestamp=readLong(in);//读取删除文件时间戳 146.fsDir.unprotectedDelete(path,timestamp);//执行删除 147.breakbreakbreakbreak; 148.} 149.casecasecasecaseOP_MKDIR:{//创建目录操作 150.numOpMkDir++;//统计 151.PermissionStatuspermissions=fsNamesys.getUpgradePermission();// 获取文件系统中路径的默认权限 152.intintintintlength=in.readInt(); 153.ifififif(-17-11) 190.throwthrowthrowthrownewnewnewnewIOException("Unexpectedopcode"+opcode+"forvers ion"+logVersion); 191.fsDir.unprotectedSetPermission(FSImage.readString(in),FsPermission. read(in));//在fsDir中同步设置 192.breakbreakbreakbreak; 193.} 194.casecasecasecaseOP_SET_OWNER:{//设置属主操作 195.numOpSetOwner++; 196.ifififif(logVersion>-11) 197.throwthrowthrowthrownewnewnewnewIOException("Unexpectedopcode"+opcode+"forversi on"+logVersion); 198.fsDir.unprotectedSetOwner(FSImage.readString(in),FSImage.readStrin g_EmptyAsNull(in),FSImage.readString_EmptyAsNull(in));//在fsDir中同步设置 属主 199.breakbreakbreakbreak; 200.} 201.casecasecasecaseOP_SET_NS_QUOTA:{//设置 namespace配额操作 202.ifififif(logVersion>-16){ 203.throwthrowthrowthrownewnewnewnewIOException("Unexpectedopcode"+opcode+"forversi on"+logVersion); 204.} 205.fsDir.unprotectedSetQuota(FSImage.readString(in),readLongWritable( in),FSConstants.QUOTA_DONT_SET);//在fsDir中同步设置 namespace配额 206.breakbreakbreakbreak; 207.} 208.casecasecasecaseOP_CLEAR_NS_QUOTA:{//清除 namespace配额操作 209.ifififif(logVersion>-16){ 210.throwthrowthrowthrownewnewnewnewIOException("Unexpectedopcode"+opcode+"forversi on"+logVersion); 211.} 212.fsDir.unprotectedSetQuota(FSImage.readString(in),FSConstants.QUOTA _RESET,FSConstants.QUOTA_DONT_SET);//在fsDir中同步清除 namespace配额 213.breakbreakbreakbreak; 214.} 215.casecasecasecaseOP_SET_QUOTA://设置名称和磁盘配额 216.fsDir.unprotectedSetQuota(FSImage.readString(in),readLongWritable( in),readLongWritable(in));//在fsDir中同步设置磁盘配 额 217.breakbreakbreakbreak; 218.casecasecasecaseOP_TIMES:{//设置文件的 mod&access时间操作 219.numOpTimes++; 220.intintintintlength=in.readInt(); 221.ifififif(length!=3){ 222.throwthrowthrowthrownewnewnewnewIOException("Incorrectdataformat."+"timesoperatio n."); 223.} 224.path=FSImage.readString(in); 225.mtime=readLong(in); 226.atime=readLong(in); 227.fsDir.unprotectedSetTimes(path,mtime,atime,truetruetruetrue);//在fsDir上同 步设置 228.breakbreakbreakbreak; 229.} 230.defaultdefaultdefaultdefault:{ 231.throwthrowthrowthrownewnewnewnewIOException("Neverseenopcode"+opcode); 232.} 233.} 234.} 235.}finallyfinallyfinallyfinally{ 236.in.close(); 237.} 238.FSImage.LOG.info("Editsfile"+edits.getName()+"ofsize"+edits.le ngth()+"edits#"+numEdits+"loadedin"+(FSNamesystem.now()-startT ime)/1000+"seconds."); 239. 240.ifififif(FSImage.LOG.isDebugEnabled()){ 241.FSImage.LOG.debug("numOpAdd="+numOpAdd+"numOpClose="+numOpCl ose 242.+"numOpDelete="+numOpDelete+"numOpRename="+numOpRename 243.+"numOpSetRepl="+numOpSetRepl+"numOpMkDir="+numOpMkDir 244.+"numOpSetPerm="+numOpSetPerm 245.+"numOpSetOwner="+numOpSetOwner 246.+"numOpSetGenStamp="+numOpSetGenStamp 247.+"numOpTimes="+numOpTimes 248.+"numOpOther="+numOpOther); 249.} 250. 251.ifififif(logVersion!=FSConstants.LAYOUT_VERSION)//如果版本号不等 于 LAYOUT_VERSION=-8 252.numEdits++;//也进行统计 253.returnreturnreturnreturnnumEdits; 254.} 通 过该 方法 的实 现 ,实 际上 在从 多个 EditLog 中 读取 日志 信息 的时 候 ,主 要是 将日 志文 件中 对 应的 事务 ,通 过 FSDirectory fsDir = fsNamesys.dir 及 时更 新到 文件 系统 的目 录上 ,保 持 数 据状 态同 步一 致。 5、 其它 方法 还 有几 个方 法, 分别 表示 对 EditLog 日 志文 件的 操作 ,比 如: 关 闭日 志文 件方 法 rollEditLog, 同时 创建 一个 edits.new 文 件; 删 除旧 日志 文件 purgeEditLog 方 法, 同时 将 edits.new 文 件重 命名 为 edits; 同 步日 志文 件方 法 logSync,只 对当 前线 程对 日志 文件 作出 的全 部修 改 ,同 步到 日志 文件 上 。 这里,对Hadoop 实 现的 与升 级管 理相 关的 实现 类进 行分 析 。通 过升 级管 理器 ,可 以对 文件 系 统的 状态 进行 定时 升级 更新 ,保 证最 良好 的工 作状 态 。下 面从 不同 的侧 面对 与分 布式 升级 相 关的 内容 分类 分析 。 • 升级命令 与 升级 命令 相关 的实 现类 的继 承层 次关 系如 下所 示: view plain 1.◦org.apache.hadoop.hdfs.server.protocol.DatanodeCommand(implementsorg.apach e.hadoop.io.Writable) 2.◦org.apache.hadoop.hdfs.server.protocol.UpgradeCommand 1111、DatanodeCommand DatanodeCommand DatanodeCommand DatanodeCommand 抽 象类 该 抽象 类实 现 Writable 接 口, 因此 是可 序列 化的 ,也 必须 实现 该接 口中 提供 的读 写方 法 。 DatanodeCommand 类 内部 定义 了两 个实 现该 抽象 类的 命令 ,如 下所 示: view plain 1./** 2.*Datanode执行注册的命令 3.*/ 4.staticstaticstaticstaticclassclassclassclassRegisterextendsextendsextendsextendsDatanodeCommand{ 5.privateprivateprivateprivateRegister(){supersupersupersuper(DatanodeProtocol.DNA_REGISTER);} 6.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin){} 7.publicpublicpublicpublicvoidvoidvoidvoidwrite(DataOutputout){} 8.} 9. 10./** 11.*Datanode执行清理的命令 12.*/ 13.staticstaticstaticstaticclassclassclassclassFinalizeextendsextendsextendsextendsDatanodeCommand{ 14.privateprivateprivateprivateFinalize(){supersupersupersuper(DatanodeProtocol.DNA_FINALIZE);} 15.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin){} 16.publicpublicpublicpublicvoidvoidvoidvoidwrite(DataOutputout){} 17.} 这 两个 命令 实现 类中 ,对Writeable 接 口中 定义 的序 列化 读写 操作 并未 给出 实现 ,只 是一 个 空 动作 。 而DatanodeCommand 类 内部 定义 了这 两各 命令 作为 属性 ,如 下所 示: view plain 1.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalDatanodeCommandREGISTER=newnewnewnewRegister(); 2.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalDatanodeCommandFINALIZE=newnewnewnewFinalize(); 因 为这 两个 命令 实现 类不 是 public 的,所 以在 获取 其实 例的 时候 ,是 通过 WritableFactories 工 厂工 具来 加载 非 public 类的Writeable 实 例, 如下 所示 : view plain 1.staticstaticstaticstatic{ 2.WritableFactories.setFactory(Register.classclassclassclass, 3.newnewnewnewWritableFactory(){ 4.publicpublicpublicpublicWritablenewInstance(){returnreturnreturnreturnnewnewnewnewRegister();} 5.}); 6.WritableFactories.setFactory(Finalize.classclassclassclass, 7.newnewnewnewWritableFactory(){ 8.publicpublicpublicpublicWritablenewInstance(){returnreturnreturnreturnnewnewnewnewFinalize();} 9.}); 10.} 该 抽象 类还 定义 了一 个 action 代 码, 表示 Datanode 需 要执 行的 动作 代码 ,如 下所 示: view plain 1.privateprivateprivateprivateintintintintaction; 2. 3.DatanodeCommand(intintintintaction){ 4.thisthisthisthis.action=action; 5.} 6. 7.publicpublicpublicpublicintintintintgetAction(){ 8.returnreturnreturnreturnthisthisthisthis.action; 9.} 另 外, 该抽 象类 应该 实现 Writeable 接 口, 如下 所示 : view plain 1.publicpublicpublicpublicvoidvoidvoidvoidwrite(DataOutputout)throwsthrowsthrowsthrowsIOException{ 2.out.writeInt(thisthisthisthis.action); 3.} 4. 5.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin)throwsthrowsthrowsthrowsIOException{ 6.thisthisthisthis.action=in.readInt(); 7.} 其 实, 就是 实现 动作 代码 action 支 持序 列化 操作 。 2222、UpgradeCommand UpgradeCommand UpgradeCommand UpgradeCommand 类 该 类作 为分 布式 升级 的通 用命 令实 现 。在 升级 期间 ,为 了能 够获 取升 级所 需要 的资 源 ,或者 是 与其 它组 件共 享某 些信 息 ,集 群组 件会 向其 它组 件发 送升 级命 令 。其中,升 级命 令包 含升 级 的版 本号 信息 ,使 用它 来验 证升 级组 件执 行升 级的 状态 。 下 面看 该类 定义 的升 级命 令所 要执 行的 动作 代码 : view plain 1.finalfinalfinalfinalstaticstaticstaticstaticintintintintUC_ACTION_UNKNOWN=DatanodeProtocol.DNA_UNKNOWN;//未知动 作 2.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticintintintintUC_ACTION_REPORT_STATUS=100;//报告升级状态 3.publicpublicpublicpublicfinalfinalfinalfinalstaticstaticstaticstaticintintintintUC_ACTION_START_UPGRADE=101;//启动升级 该 类定 义了 两个 属性 : view plain 1.privateprivateprivateprivateintintintintversion;//版本号 2.privateprivateprivateprivateshortshortshortshortupgradeStatus;//升级的状态 同 样, 对现 Writeable 接 口: view plain 1.staticstaticstaticstatic{ 2.WritableFactories.setFactory 3.(UpgradeCommand.classclassclassclass, 4.newnewnewnewWritableFactory(){ 5.publicpublicpublicpublicWritablenewInstance(){returnreturnreturnreturnnewnewnewnewUpgradeCommand();} 6.}); 7.} 8. 9.publicpublicpublicpublicvoidvoidvoidvoidwrite(DataOutputout)throwsthrowsthrowsthrowsIOException{ 10.supersupersupersuper.write(out); 11.out.writeInt(thisthisthisthis.version); 12.out.writeShort(thisthisthisthis.upgradeStatus); 13.} 14. 15.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin)throwsthrowsthrowsthrowsIOException{ 16.supersupersupersuper.readFields(in); 17.thisthisthisthis.version=in.readInt(); 18.thisthisthisthis.upgradeStatus=in.readShort(); 19.} 支 持对 两个 状态 属性 的序 列化 操作 。 • 升级状态报告 升 级状 态报 告对 应着 org.apache.hadoop.hdfs.server.common.UpgradeStatusReport 实体 类 ,该 类包 含了 与升 级相 关的 基本 状态 ,包 括三 个, 如下 所示 : view plain 1.protectedprotectedprotectedprotectedintintintintversion;//版本号 2.protectedprotectedprotectedprotectedshortshortshortshortupgradeStatus;//升级的状态 3.protectedprotectedprotectedprotectedbooleanbooleanbooleanbooleanfinalized;//升级后清理工作状态标志 并 且, 支持 对上 述属 性的 序列 化操 作。 • 分布式升级对象 与 分布 式升 级相 关的 实体 (对 象) 实现 类, 实现 了 org.apache.hadoop.hdfs.server.common.Upgradeable 接 口, 具体 的继 承层 次关 系如 下所 示: view plain 1.◦org.apache.hadoop.hdfs.server.common.UpgradeObject(implementsorg.apache.ha doop.hdfs.server.common.Upgradeable) 2.◦org.apache.hadoop.hdfs.server.namenode.UpgradeObjectNamenode(implement sjava.lang.Runnable) 3.◦org.apache.hadoop.hdfs.server.common.UO_Namenode 4.◦org.apache.hadoop.hdfs.server.common.UO_Namenode1 5.◦org.apache.hadoop.hdfs.server.common.UO_Namenode2 6.◦org.apache.hadoop.hdfs.server.common.UO_Namenode3 7.◦org.apache.hadoop.hdfs.server.datanode.UpgradeObjectDatanode(implement sjava.lang.Runnable) 8.◦org.apache.hadoop.hdfs.server.common.UO_Datanode 9.◦org.apache.hadoop.hdfs.server.common.UO_Datanode1 10.◦org.apache.hadoop.hdfs.server.common.UO_Datanode2 11.◦org.apache.hadoop.hdfs.server.common.UO_Datanode3 下 面, 对上 面给 出的 相关 实现 进行 阅读 分析 : 1111、Upgradeable Upgradeable Upgradeable Upgradeable 接口 该 接口 是分 布式 升级 对象 (distributed upgrade objects)的 通用 接口 ,定 义了 一个 分布 式对 象 应该 具有 的基 本行 为 。每 一个 分布 式升 级对 象都 应该 存在 一个 状态 ,该 状态 能够 体现 该升 级 对象 与某 个指 定的 HDFS 的 版本 相关 ,因 此, 使用 了一 个版 本号 的变 量 LAYOUT_VERSION 来 标识 分布 式升 级对 象的 版本 变化 ,升 级的 过程 实际 上是 使该 对象 的 对 应的 LAYOUT_VERSION 为 最近 (最 新) 的版 本, 每一 个分 布式 升级 对象 都对 应一 个 LAYOUT_VERSION, 当执 行完 成升 级以 后, 会返 回最 新的 LAYOUT_VERSION 版 本号 。 该 接口 的定 义如 下所 示: view plain 1.packagepackagepackagepackageorg.apache.hadoop.hdfs.server.common; 2. 3.importimportimportimportjava.io.IOException; 4. 5.importimportimportimportorg.apache.hadoop.hdfs.server.protocol.UpgradeCommand; 6. 7.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceUpgradeableextendsextendsextendsextendsComparable{ 8./** 9.*获取升级对象的 LAYOUT_VERSION版本号 10.*/ 11.intintintintgetVersion(); 12. 13./** 14.*获取正在执行升级过程的软件组件的类型,实际上只存在两种类型: NAME_NODE与 DATA_NODE 15.*/ 16.HdfsConstants.NodeTypegetType(); 17. 18./** 19.*升级对象的描述信息 20.*/ 21.StringgetDescription(); 22. 23./** 24.*升级状态确定了在一次升级过程中需要完成的升级任务量(占升级总任务量)的百分比 25.* 26.*100%意味着升级完成,小于 100%则表示没有完成升级。 27.*返回值应该至少包含两个整数值(范围是 [0,100]) 28.*/ 29.shortshortshortshortgetUpgradeStatus(); 30. 31./** 32.*升级准备 33.*例如,初始化升级数据,初始化升级状态为 0 34.* 35.*返回一个升级命令,该命令可以向其它的集群组件广播将要执行升级的动作 36.*例如, Namenode通知每个 Datanode,需要执行一次分布式升级 37.*/ 38.UpgradeCommandstartUpgrade()throwsthrowsthrowsthrowsIOException; 39. 40./** 41.*完成升级 42.*例如,清除升级数据结构,或将升级元数据写入磁盘 43.* 44.*返回一个升级命令,该命令可以向其它的集群组件广播已经完成升级 45.*例如 ,每个 Datanode通知 Namenode,它们已经完成升级 ,而其它的某些 Datanode可能 孩子升级过程中 46.*/ 47.UpgradeCommandcompleteUpgrade()throwsthrowsthrowsthrowsIOException; 48. 49./** 50.*获取升级状态报告 51.* 52.*@paramdetails如果指定 details=true,表示需要返回详细的升级状态报告 53.*/ 54.UpgradeStatusReportgetUpgradeStatusReport(booleanbooleanbooleanbooleandetails)throwsthrowsthrowsthrowsIOExcep tion; 55.} 上 面接 口中 定义 的行 为总 结如 下: 1)LAYOUT_VERSION 包 含升 级对 象的 所处 的版 本, 也就 是根 据此 版本 来确 定是 否需 要 升 级, 以及 需要 升级 到最 新的 版本 ; 2)执 行升 级组 件的 类型 ,因 为组 件不 同 ,它 们所 处的 位置 与数 据状 态时 不同 的 ,因 此不 同 的 组件 执行 升级 动作 是不 同的 ,根 据该 类型 来识 别; 3) 执行 升级 过程 ,应 该可 以从 升级 的对 象获 取到 其状 态, 包括 升级 完成 后的 升级 报告 ,通 过 该状 态来 掌握 集群 中升 级组 件的 动态 ,从 而可 能需 要对 不同 的升 级组 件作 出合 适的 决策 。 2222、UpgradeObject UpgradeObject UpgradeObject UpgradeObject 抽 象类 该 类是 升级 对象 的抽 象类 ,实 现了 Upgradeable 接 口。 该抽 象类 主要 实现 了 Upgradeable 接 口中 定义 的最 基本 的方 法, 并且 定义 了一 个状 态变 量, 如下 所示 : view plain 1.protectedprotectedprotectedprotectedshortshortshortshortstatus; 该 类实 现了 获取 升级 状态 报告 的方 法, 如下 所示 : view plain 1.publicpublicpublicpublicUpgradeStatusReportgetUpgradeStatusReport(booleanbooleanbooleanbooleandetails)throwsthrowsthrowsthrowsIO Exception{ 2.returnreturnreturnreturnnewnewnewnewUpgradeStatusReport(getVersion(),getUpgradeStatus(),falsefalsefalsefalse);/ /通过指定版本、状态 status、不返回详细升级报告标志 status,构造一 个 UpgradeStatusReport实例返回 3.} 可 想而 知 ,在 准备 升级 之前 ,一 定需 要对 版本 进行 比较 ,从 而决 定是 否升 级 ,已 经升 级到 哪 个 版本 ,如 下所 示: view plain 1.publicpublicpublicpublicintintintintcompareTo(Upgradeableo){//是对一个 Upgradeable对象的不同版本号数 字进行比较 2.ifififif(thisthisthisthis.getVersion()!=o.getVersion()) 3.returnreturnreturnreturn(getVersion()>o.getVersion()?-1:1); 4.intintintintres=thisthisthisthis.getType().toString().compareTo(o.getType().toString());// 升级对象的类型要一致 5.ifififif(res!=0) 6.returnreturnreturnreturnres; 7.returnreturnreturnreturngetClass().getCanonicalName().compareTo(o.getClass().getCanonicalNa me()); 8.} 9. 10.publicpublicpublicpublicbooleanbooleanbooleanbooleanequals(Objecto){ 11.ifififif(!(oinstanceofinstanceofinstanceofinstanceofUpgradeObject)){//只支持 UpgradeObject类升级对象的比 较 12.returnreturnreturnreturnfalsefalsefalsefalse; 13.} 14.returnreturnreturnreturnthisthisthisthis.compareTo((UpgradeObject)o)==0; 15.} 3333、UpgradeObjectNamenode UpgradeObjectNamenode UpgradeObjectNamenode UpgradeObjectNamenode 抽 象类 该 抽象 类定 义了 Namenode 类 型升 级对 象的 抽象 行为 。介 绍如 下: 1) 处理 升级 命令 如 下所 示: view plain 1./** 2.*处理一个升级命令 3.*RPC只具有一个通用的命令 ,该命令能够使 Namenode升级组件与所有升级相关的交互组件进 行通信 4.* 5.*实际的命令的识别与执行使用该方法来处理,处理完成后仍然返回值升级命令,该返回命令 可以在交互的另一端( Datanode)进行分析 6.*/ 7.publicpublicpublicpublicabstractabstractabstractabstractUpgradeCommandprocessUpgradeCommand(UpgradeCommandcommand) throwsthrowsthrowsthrowsIOException; 2) 广播 :启 动升 级 如 下所 示: view plain 1.publicpublicpublicpublicUpgradeCommandstartUpgrade()throwsthrowsthrowsthrowsIOException{ 2.returnreturnreturnreturnnewnewnewnewUpgradeCommand(UpgradeCommand.UC_ACTION_START_UPGRADE,getVersi on(),(shortshortshortshort)0); 3.} 执 行该 方法 ,能 够向 需要 升级 的 Datanode 广 播将 要启 动升 级的 消息 。 4444、UO_Namenode UO_Namenode UO_Namenode UO_Namenode 类 该 类实 现如 下所 示: view plain 1.classclassclassclassUO_NamenodeextendsextendsextendsextendsUpgradeObjectNamenode{ 2.intintintintversion;//Namenode升级对象版本号 3.UO_Namenode(intintintintv){ 4.status=(shortshortshortshort)0; 5.version=v; 6.} 7. 8.publicpublicpublicpublicintintintintgetVersion(){ 9.returnreturnreturnreturnversion; 10.} 11. 12.synchronizedsynchronizedsynchronizedsynchronizedpublicpublicpublicpublicUpgradeCommandprocessUpgradeCommand(UpgradeCommandco mmand)throwsthrowsthrowsthrowsIOException{ 13.switchswitchswitchswitch(command.getAction()){ 14.casecasecasecaseUpgradeCommand.UC_ACTION_REPORT_STATUS://如果命令是执行报告升级状 态 15.thisthisthisthis.status+=command.getCurrentStatus()/8;//4reportsneeded 16.breakbreakbreakbreak; 17.defaultdefaultdefaultdefault: 18.thisthisthisthis.status++; 19.} 20.returnreturnreturnreturnnullnullnullnull; 21.} 22. 23.publicpublicpublicpublicUpgradeCommandcompleteUpgrade()throwsthrowsthrowsthrowsIOException{ 24.returnreturnreturnreturnnullnullnullnull; 25.} 26.} 5555、UO_Datanode1 UO_Datanode1 UO_Datanode1 UO_Datanode1 类 这 里拿 UO_Namenode1 为 例, UO_UO_Namenode2、UO_UO_Namenode3 也 都是 基 于 LAYOUT_VERSION 的 ,如 下所 示: view plain 1.classclassclassclassUO_Namenode1extendsextendsextendsextendsUO_Namenode{ 2.UO_Namenode1(){ 3.supersupersupersuper(LAYOUT_VERSION+1); 4.} 5.} UO_UO_Namenode2、UO_UO_Namenode3 分 别将 初始 化的 LAYOUT_VERSION+1 变 成LAYOUT_VERSION+2、LAYOUT_VERSION+3。 • 分布式升级管理器 分 布式 升级 管理 器涉 及到 Namenode 与Datanode。下 面看 升级 管理 器实 现类 的继 承层 次关 系: view plain 1.◦org.apache.hadoop.hdfs.server.common.UpgradeManager 2.◦org.apache.hadoop.hdfs.server.namenode.UpgradeManagerNamenode 3.◦org.apache.hadoop.hdfs.server.datanode.UpgradeManagerDatanode 这里,只对UpgradeManager 抽 象类 与 UpgradeManagerNamenode 实 现类 进行 阅读 分析 , 因为UpgradeManagerDatanode 涉 及到 Datanode 进程 (org.apache.hadoop.hdfs.server.datanode.DataNode) 的实 现, 这个 在后 面会 详细 分析 的。 1111、UpgradeManager UpgradeManager UpgradeManager UpgradeManager 抽 象类 该 抽象 类是 通用 的升 级管 理器 类。 该抽 象类 定义 了如 下属 性: view plain 1.protectedprotectedprotectedprotectedSortedSetcurrentUpgrades=nullnullnullnull;//当前升级对象集合 2.protectedprotectedprotectedprotectedbooleanbooleanbooleanbooleanupgradeState=falsefalsefalsefalse;//如果当前正在执行升级过程,其值 为 true 3.protectedprotectedprotectedprotectedintintintintupgradeVersion=0;//升级版本 4.protectedprotectedprotectedprotectedUpgradeCommandbroadcastCommand=nullnullnullnull;//广播升级命令 该 抽象 类定 义了 如下 主要 操作 : 1) 获取 当前 升级 对象 集合 : view plain 1.publicpublicpublicpublicSortedSetgetDistributedUpgrades()throwsthrowsthrowsthrowsIOException{ 2.returnreturnreturnreturnUpgradeObjectCollection.getDistributedUpgrades(getUpgradeVersion(), getType());//根据制定的版本号和升级对象类型获取到分布式升级对象的集合 3.} 2) 获取 升级 进度 view plain 1.publicpublicpublicpublicshortshortshortshortgetUpgradeStatus(){ 2.ifififif(currentUpgrades==nullnullnullnull)//返回 100,表示升级完成(其实是不需要升级) 3.returnreturnreturnreturn100; 4.returnreturnreturnreturncurrentUpgrades.first().getUpgradeStatus();//获取升级状态 5.} 3) 初始 化升 级管 理器 view plain 1.publicpublicpublicpublicbooleanbooleanbooleanbooleaninitializeUpgrade()throwsthrowsthrowsthrowsIOException{ 2.currentUpgrades=getDistributedUpgrades();//获取需要升级的分布式升级对象的 集合 3.ifififif(currentUpgrades==nullnullnullnull){ 4.setUpgradeState(falsefalsefalsefalse,FSConstants.LAYOUT_VERSION);//不需要升级,设置状 态 5.returnreturnreturnreturnfalsefalsefalsefalse; 6.} 7.UpgradeablecurUO=currentUpgrades.first();//取出集合中第一个升级对象 8.setUpgradeState(truetruetruetrue,curUO.getVersion());//修改状态 9.returnreturnreturnreturntruetruetruetrue; 10.} 4) 抽象 行为 view plain 1./** 2.*获取结点类型 3.*/ 4.publicpublicpublicpublicabstractabstractabstractabstractHdfsConstants.NodeTypegetType(); 5. 6./** 7.*启动升级进程 8.*/ 9.publicpublicpublicpublicabstractabstractabstractabstractbooleanbooleanbooleanbooleanstartUpgrade()throwsthrowsthrowsthrowsIOException; 10. 11./** 12.*完成升级 13.*/ 14.publicpublicpublicpublicabstractabstractabstractabstractvoidvoidvoidvoidcompleteUpgrade()throwsthrowsthrowsthrowsIOException; 2222、UpgradeManagerNamenode UpgradeManagerNamenode UpgradeManagerNamenode UpgradeManagerNamenode 类 该 类是 Namenode 的 升级 管理 器实 现类 ,当Namenode 在 安全 模式 下 ,将 要退 出安 全模 式 的 时候 ,会 启动 分布 式升 级进 程。 这时 , Namenode 直 到升 级完 成才 退出 安全 模式 。在 升 级 过程 中, Namenode 会 处理 Datanode 提 交的 命令 ,并 更新 它的 状态 。 1) 初始 化升 级管 理器 启 动分 布式 升级 过程 ,需 要调 用 startUpgrade 方 法, 如下 所示 : view plain 1.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedbooleanbooleanbooleanbooleanstartUpgrade()throwsthrowsthrowsthrowsIOException{ 2.ifififif(!upgradeState){//当前不是在升级过程中 3.initializeUpgrade();//初始化升级管理器 4.ifififif(!upgradeState)returnreturnreturnreturnfalsefalsefalsefalse;//初始化失败,返回 5.FSNamesystem.getFSNamesystem().getFSImage().writeAll();//将启动升级过程 中发生的改变写入到 fsimage映像中 6.} 7.assertassertassertassertcurrentUpgrades!=nullnullnullnull:"currentUpgradesisnull"; 8.thisthisthisthis.broadcastCommand=currentUpgrades.first().startUpgrade();//分布式升 级对象执行升级前的初始化工作 9.NameNode.LOG.info("/nDistributedupgradeforNameNodeversion" 10.+getUpgradeVersion()+"tocurrentLV" 11.+FSConstants.LAYOUT_VERSION+"isstarted."); 12.returnreturnreturnreturntruetruetruetrue; 13.} 2) 处理 升级 命令 如 下所 示: view plain 1.synchronizedsynchronizedsynchronizedsynchronizedUpgradeCommandprocessUpgradeCommand(UpgradeCommandcommand)thththth rowsrowsrowsrowsIOException{ 2.NameNode.LOG.debug("/nDistributedupgradeforNameNodeversion" 3.+getUpgradeVersion()+"tocurrentLV" 4.+FSConstants.LAYOUT_VERSION+"isprocessingupgradecommand:" 5.+command.getAction()+"status="+getUpgradeStatus()+"%"); 6.ifififif(currentUpgrades==nullnullnullnull){//如果 Namenode上没有升级进程启动 7.NameNode.LOG.info("Ignoringupgradecommand:"+command.getAction()+" version"+command.getVersion()+".Nodistributedupgradesarecurrently runningontheNameNode"); 8.returnreturnreturnreturnnullnullnullnull; 9.} 10.UpgradeObjectNamenodecurUO=(UpgradeObjectNamenode)currentUpgrades.first ();//取出分布式升级对象( Namenode上的升级对象) 11.ifififif(command.getVersion()!=curUO.getVersion())//升级命令版本号与升级对象版 本号不匹配,不能进行升级 12.throwthrowthrowthrownewnewnewnewIncorrectVersionException(command.getVersion(),"UpgradeComman d",curUO.getVersion()); 13.UpgradeCommandreply=curUO.processUpgradeCommand(command);//处理一个升 级命令,同时返回一个新的升级命令 14.ifififif(curUO.getUpgradeStatus()<100){//已经处理完升级命令,执行升级了,但是升 级进度还没有达到 100%,存在问题 15.returnreturnreturnreturnreply; 16.} 17. 18.curUO.completeUpgrade();//升级进度返回 100,表示完成升级 19.NameNode.LOG.info("/nDistributedupgradeforNameNodeversion" 20.+curUO.getVersion()+"tocurrentLV" 21.+FSConstants.LAYOUT_VERSION+"iscomplete."); 22. 23.currentUpgrades.remove(curUO);//删除升级集合中处理完成的该升级对象,准备对下 一个执行升级操作 24.ifififif(currentUpgrades.isEmpty()){//所有升级对象都已经完成升级 25.completeUpgrade();//调用该类的成员方法,完成升级 26.}elseelseelseelse{//对升级对象集合中下一个待升级对象进行处理 27.curUO=(UpgradeObjectNamenode)currentUpgrades.first(); 28.thisthisthisthis.broadcastCommand=curUO.startUpgrade();//启动 29.} 30.returnreturnreturnreturnreply; 31.} 3)完 成升 级 如 下所 示: view plain 1.publicpublicpublicpublicsynchronizedsynchronizedsynchronizedsynchronizedvoidvoidvoidvoidcompleteUpgrade()throwsthrowsthrowsthrowsIOException{ 2.setUpgradeState(falsefalsefalsefalse,FSConstants.LAYOUT_VERSION); 3.FSNamesystem.getFSNamesystem().getFSImage().writeAll();//将升级状态改变写 入磁盘 4.currentUpgrades=nullnullnullnull; 5.broadcastCommand=nullnullnullnull; 6.FSNamesystem.getFSNamesystem().leaveSafeMode(falsefalsefalsefalse);//Namenode已经完成本 次升级,退出安全模式 7.} 4) 分布 式升 级 如 下所 示: view plain 1.UpgradeStatusReportdistributedUpgradeProgress(UpgradeActionaction)throwsthrowsthrowsthrows IOException{ 2.booleanbooleanbooleanbooleanisFinalized=falsefalsefalsefalse;//设置升级后的完成清理标志 3.ifififif(currentUpgrades==nullnullnullnull){//不需要升级 4.FSImagefsimage=FSNamesystem.getFSNamesystem().getFSImage(); 5.isFinalized=fsimage.isUpgradeFinalized();//从fsimage映像获取到是否执行 完成了清理过程的标志 6.ifififif(isFinalized)//升级后清理工作完成 7.returnreturnreturnreturnnullnullnullnull;//不需要报告 8.returnreturnreturnreturnnewnewnewnewUpgradeStatusReport(fsimage.getLayoutVersion(),(shortshortshortshort)101,i sFinalized);//否则,需要返回清理工作完成报告信息 9.} 10.UpgradeObjectNamenodecurUO=(UpgradeObjectNamenode)currentUpgrades.first ();//取出 Namenode上的升级对象 11.booleanbooleanbooleanbooleandetails=falsefalsefalsefalse;//不返回详细升级报告信息 12.switchswitchswitchswitch(action){//根据指定的分布式升级动作,作出相应的处理 13.casecasecasecaseGET_STATUS: 14.breakbreakbreakbreak; 15.casecasecasecaseDETAILED_STATUS: 16.details=truetruetruetrue; 17.breakbreakbreakbreak; 18.casecasecasecaseFORCE_PROCEED: 19.curUO.forceProceed(); 20.} 21.returnreturnreturnreturncurUO.getUpgradeStatusReport(details);//返回升级状态报告 22.} 对UpgradeManagerNamenode 类 的分 析做 个总 结: 该 类实 现了 ,对 在 Namenode 上 运行 的分 布式 升级 对象 执行 升级 操作 的管 理。 Namenode 上 的升 级进 程执 行的 时机 是在 安全 模式 下, 更具 体地 说, 是在 Namenode 进 程就 要退 出安 全 模式 的时 刻执 行升 级工 作的 ,只 有等 到升 级过 程中 完成 以后 , Namenode 才 退出 安全 模 式。经 过升 级 HDFS 集 群中 ,由Namenode 升 级管 理器 管理 的升 级对 象的 状态 都会 升级 到 当 前最 新的 状态 ,当 然不 排除 提交 的升 级命 令中 的状 态与 实际 分布 式升 级对 象状 态不 匹配 的 情 况, 这种 情况 不会 执行 升级 动作 的。 这 里, 对重 要的 FSImage 类 进行 阅读 分析 。 该 类的 继承 层次 关系 如下 所示 : view plain 1.◦org.apache.hadoop.hdfs.server.common.StorageInfo 2.◦org.apache.hadoop.hdfs.server.common.Storage 3.◦org.apache.hadoop.hdfs.server.namenode.FSImage 我 们一 个一 个地 分析 : • StorageInfo 类 该 类是 一个 存储 信息 的通 用实 体类 , 该 类定 义了 如下 三个 属性 : view plain 1.publicpublicpublicpublicintintintintlayoutVersion;//从存储的文件中读取版本信息 . 2.publicpublicpublicpublicintintintintnamespaceID;//文件系统命名空间的存储 ID 3.publicpublicpublicpubliclonglonglonglongcTime;//存储(文件或目录)访问时间 其 中, namespaceID 表 示的 是文 件系 统命 名空 间的 存储 ID, 它的 生成 基于 Hash 算 法的 , 根据address + port 来 进行 散列 得到 的。 • Storage 类 该 类继 承自 StorageInfo, 是一 个抽 象类 。 本 地存 储信 息被 存储 在一 个单 独的 文件 VERSION 中 ,它 包含 结点 的类 型、 存储 布局 (Layout) 版本 、 文 件系 统命 名空 间 ID、 文件 系统 状态 的创 建时 间。 本地 存储 可以 分布 在 多 个目 录中 ,每 个目 录中 应该 保存 着相 同 的VERSION文件,当Hadoop servers (name-node and data-nodes)启 动的 时候 从这 些 VERSION 文 件中 读取 本地 的存 储信 息。 当 Hadoop servers 启 动的 时候 ,会 对每 个存 储目 录都 持有 一把 锁, 这是 为了 使得 其他 的结 点不 能因 为 共 享同 一个 存储 目录 而启 动。 当 Hadoop servers 停 止的 时候 ,会 释放 掉它 们所 持有 的锁 , 以 便其 它结 点来 使用 该存 储目 录。 首 先, 看存 储状 态的 枚举 定义 : view plain 1.publicpublicpublicpublicenumenumenumenumStorageState{ 2.NON_EXISTENT,//不存在 3.NOT_FORMATTED,//未格式化 4.COMPLETE_UPGRADE,//完成升级 5.RECOVER_UPGRADE,//恢复升级 6.COMPLETE_FINALIZE,//完成确认 7.COMPLETE_ROLLBACK,//完成回滚 8.RECOVER_ROLLBACK,//恢复回滚 9.COMPLETE_CHECKPOINT,//完成检查点 10.RECOVER_CHECKPOINT,//恢复检查点 11.NORMAL;//正常 12.} 下 面是 与存 储相 关的 几个 文件 或目 录: view plain 1.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_FILE_LOCK="in_use.lock"; 2.protectedprotectedprotectedprotectedstaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_FILE_VERSION="VERSION"; 3.publicpublicpublicpublicstaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_DIR_CURRENT="current"; 4.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_DIR_PREVIOUS="previous"; 5.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_TMP_REMOVED="removed.tmp"; 6.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_TMP_PREVIOUS="previous.tmp"; 7.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_TMP_FINALIZED="finalized.tmp"; 8.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_TMP_LAST_CKPT="lastcheckpoint.tmp"; 9.privateprivateprivateprivatestaticstaticstaticstaticfinalfinalfinalfinalStringSTORAGE_PREVIOUS_CKPT="previous.checkpoint"; 我 们再 看一 下, 该类 中定 义的 3个 与存 储相 关的 内部 类: 1、StorageDirType 接 口类 该 接口 定义 了与 存储 目录 的类 型相 关的 方法 : view plain 1.publicpublicpublicpublicinterfaceinterfaceinterfaceinterfaceStorageDirType{ 2.publicpublicpublicpublicStorageDirTypegetStorageDirType();//获取存储目录类型 3.publicpublicpublicpublicbooleanbooleanbooleanbooleanisOfType(StorageDirTypetype);//指定存储目录是否是指定的存储 目录类型 type 4.} Storage 类 中, 并没 有实 现该 存储 目录 类型 的内 部类 。 2、StorageDirectory 类: 该 类表 示一 个单 独的 存储 目录 的实 体类 ,它 定义 了如 下三 个属 性: view plain 1.Fileroot;//根目录 2.FileLocklock;//存储锁 3.StorageDirTypedirType;//存储目录类型 前 面提 到, 每个 存储 对应 一个 VERSION 文 件, 也就 是在 每个 存储 目录 中保 存。 StorageDirectory 类 提供 了对 VERSION 文 件的 读写 操作 ,而 且还 给出 了对 一个 存储 目录 进 行 一致 性检 查的 操作 analyzeStorage: 根据 Hadoop servers 启 动的 选项 StartupOption, 来 返回 不正 常的 存储 状态 ,如 果正 常启 动则 检查 上面 与存 储相 关的 文件 或目 录是 否存 在 ,并 返 回对 应的 存储 状态 。 该 类中 定义 的 doRecover 方 法, 能够 从上 一个 失败 的事 务中 来完 成或 恢复 存储 状态 ,主 要 包 括对 下述 状态 进行 恢复 操作 : COMPLETE_UPGRADE: 将previous.tmp 重 命名 为 previous RECOVER_UPGRADE: 将previous.tmp 重 命名 为 current COMPLETE_ROLLBACK: 删除removed.tmp RECOVER_ROLLBACK: 将removed.tmp 重 命名 为 current COMPLETE_CHECKPOINT: 将lastcheckpoint.tmp 重 命名 为 previous.checkpoint RECOVER_CHECKPOINT: 将lastcheckpoint.tmp 重 命名 为 current 另 外, StorageDirectory 类 能够 控制 对当 前存 储进 行加 锁 lock、 解锁 unlock、 尝试 获取 锁 tryLock 的 操作 。 3、DirIterator 类 该 类是 StorageDirectory 的 迭代 器类 ,用 来方 便地 迭代 出一 个 StorageDirectory 列 表中 的 StorageDirectory 存 储目 录实 例。 下 面, 继续 看 Storage 类 的实 现。 用 来描 述一 个存 储的 信息 ,应 该包 括使 用该 存储 的结 点( 通过 结点 类型 来标 识) 、 该存 储 包 含的 目录 ,如 下所 示: view plain 1.privateprivateprivateprivateNodeTypestorageType;//使用该存储的结点的类型(包 括 NodeType.NAME_NODE与NodeTypeDATA_NODE这两种) 2.protectedprotectedprotectedprotectedListstorageDirs=newnewnewnewArrayList(); 基 本上 ,在 Storage 类 中实 现的 操作 都是 与存 储目 录相 关的 ,比 如, 获取 到一 个 StorageDirectory 列 表的 迭代 器实 例、 删除 目录 等等 ,可 以查 看该 类的 具体 实现 。 • FsImage 类 首 先, 看该 类中 定义 的三 个枚 举类 : view plain 1./** 2.*存储映像的文件名称 3.*/ 4.enumenumenumenumNameNodeFile{ 5.IMAGE("fsimage"), 6.TIME("fstime"), 7.EDITS("edits"), 8.IMAGE_NEW("fsimage.ckpt"), 9.EDITS_NEW("edits.new"); 10. 11.privateprivateprivateprivateStringfileName=nullnullnullnull; 12.privateprivateprivateprivateNameNodeFile(Stringname){thisthisthisthis.fileName=name;} 13.StringgetName(){returnreturnreturnreturnfileName;} 14.} 15. 16./** 17.*检查点状态 18.*/ 19.enumenumenumenumCheckpointStates{START,ROLLED_EDITS,UPLOAD_START,UPLOAD_DONE;} 20. 21./** 22.*实现了 StorageDirType接口,并指定为 namenode存储 23.*一个存储目录的类型应该是仅仅存储 fsimage的IMAGE类型,或者是存储 edits的EDITS 类型,或者是存储 fsimage与edits的IMAGE_AND_EDITS类型 24.*/ 25.staticstaticstaticstaticenumenumenumenumNameNodeDirTypeimplementsimplementsimplementsimplementsStorageDirType{ 26.UNDEFINED, 27.IMAGE, 28.EDITS, 29.IMAGE_AND_EDITS; 30. 31.publicpublicpublicpublicStorageDirTypegetStorageDirType(){ 32.returnreturnreturnreturnthisthisthisthis; 33.} 34. 35.publicpublicpublicpublicbooleanbooleanbooleanbooleanisOfType(StorageDirTypetype){ 36.ifififif((thisthisthisthis==IMAGE_AND_EDITS)&&(type==IMAGE||type==EDITS)) 37.returnreturnreturnreturntruetruetruetrue; 38.returnreturnreturnreturnthisthisthisthis==type; 39.} 40.} 再 看一 个内 部类 DatanodeImage,该 类用 于将 Datanode 的 持久 化信 息存 储到 FsImage 映 像 中, 实现 如下 所示 : view plain 1.staticstaticstaticstaticclassclassclassclassDatanodeImageimplementsimplementsimplementsimplementsWritable{//实现了 Writable接口 ,因此是 可序列化的 2.DatanodeDescriptornode=newnewnewnewDatanodeDescriptor(); 3./** 4.*序列化 Datanode的信息,存储到 fsImage中 5.*/ 6.publicpublicpublicpublicvoidvoidvoidvoidwrite(DataOutputout)throwsthrowsthrowsthrowsIOException{ 7.newnewnewnewDatanodeID(node).write(out); 8.out.writeLong(node.getCapacity()); 9.out.writeLong(node.getRemaining()); 10.out.writeLong(node.getLastUpdate()); 11.out.writeInt(node.getXceiverCount()); 12.} 13. 14./** 15.*从fsImage中读取 Datanode的信息,即反序列化 16.*/ 17.publicpublicpublicpublicvoidvoidvoidvoidreadFields(DataInputin)throwsthrowsthrowsthrowsIOException{ 18.DatanodeIDid=newnewnewnewDatanodeID(); 19.id.readFields(in); 20.longlonglonglongcapacity=in.readLong(); 21.longlonglonglongremaining=in.readLong(); 22.longlonglonglonglastUpdate=in.readLong(); 23.intintintintxceiverCount=in.readInt(); 24. 25.//根据读取到的 Datanode信息,更新 Datanode结点 26.node.updateRegInfo(id); 27.node.setStorageID(id.getStorageID()); 28.node.setCapacity(capacity); 29.node.setRemaining(remaining); 30.node.setLastUpdate(lastUpdate); 31.node.setXceiverCount(xceiverCount); 32.} 33.} 对FSImage 类 源代 码的 阅读 ,我 们从 构造 一个 FSImage 实 例的 方法 来看 ,如 下所 示: view plain 1.FSImage(){ 2.supersupersupersuper(NodeType.NAME_NODE);//为Namenode创建一个新的存储 3.thisthisthisthis.editLog=newnewnewnewFSEditLog(thisthisthisthis);//构造 FSEditLog实例 4.} 5. 6.FSImage(CollectionfsDirs,CollectionfsEditsDirs)throwsthrowsthrowsthrowsIOExce ption{ 7.thisthisthisthis(); 8.setStorageDirectories(fsDirs,fsEditsDirs);//将文件系统目录集合 与EditLog日 志文件目录集合,加入到创建的存储中 9.} 10. 11.publicpublicpublicpublicFSImage(StorageInfostorageInfo){ 12.supersupersupersuper(NodeType.NAME_NODE,storageInfo);//将StorageInfo指定 为Namenode的存 储 13.} 14. 15./** 16.*表示一个映像 (imageandeditfile) 17.*/ 18.publicpublicpublicpublicFSImage(FileimageDir)throwsthrowsthrowsthrowsIOException{ 19.thisthisthisthis(); 20.ArrayListdirs=newnewnewnewArrayList(1); 21.ArrayListeditsDirs=newnewnewnewArrayList(1); 22.dirs.add(imageDir); 23.editsDirs.add(imageDir); 24.setStorageDirectories(dirs,editsDirs); 25.} 可见,FSImage 实 例对 应着 一个 特定 的为 Namenode 而 创建 的存 储实 例 ,该 存储 实例 的内 容 ,实 际上 维护 着一 个目 录列 表, 在构 造 FSImage 实 例的 过程 中, 全部 加载 并初 始化 。 接 着, 介绍 FSImage 类 的重 要方 法。 1、 加载 FsImage 映 像文 件 对 应的 实现 方法 为 loadFSImage, 该类 实现 了两 个重 载的 加载 映像 文件 的方 法。 首 先看 第一 个带 参数 的 loadFSImage 方 法, 从一 个文 件中 加载 映像 到文 件系 统中 ,实 现如 下 所示 : view plain 1.booleanbooleanbooleanbooleanloadFSImage(FilecurFile)throwsthrowsthrowsthrowsIOException{ 2.assertassertassertassertthisthisthisthis.getLayoutVersion()<0:"Negativelayoutversionisexpected. "; 3.assertassertassertassertcurFile!=nullnullnullnull:"curFileisnull"; 4. 5.FSNamesystemfsNamesys=FSNamesystem.getFSNamesystem();//获取 到 FSNamesystem实例 6.FSDirectoryfsDir=fsNamesys.dir;//获取到 FSNamesystem实例的 dir引用 7. 8.booleanbooleanbooleanbooleanneedToSave=truetruetruetrue; 9.DataInputStreamin=newnewnewnewDataInputStream(newnewnewnewBufferedInputStream(newnewnewnewFileI nputStream(curFile)));//打开映像文件的输入流 10.trytrytrytry{ 11.intintintintimgVersion=in.readInt();//读取映像文件版本号(第一次出现为 -1) 12.thisthisthisthis.namespaceID=in.readInt();//读取文件系统命名空 间ID(第一次出现为 -2) 13.longlonglonglongnumFiles;//映像文件数变量 14.ifififif(imgVersion<=-16){ 15.numFiles=in.readLong();//读取文件数量 16.}elseelseelseelse{ 17.numFiles=in.readInt(); 18.} 19.thisthisthisthis.layoutVersion=imgVersion; 20.ifififif(imgVersion<=-12){ 21.longlonglonglonggenstamp=in.readLong();//读取最后设置的时间戳 22.fsNamesys.setGenerationStamp(genstamp);//同步到 FSNamesystem实例上 23.} 24.needToSave=(imgVersion!=FSConstants.LAYOUT_VERSION); 25.shortshortshortshortreplication=FSNamesystem.getFSNamesystem().getDefaultReplication ();//获取默认副本数 26.LOG.info("Numberoffiles="+numFiles); 27. 28.Stringpath; 29.StringparentPath=""; 30.INodeDirectoryparentINode=fsDir.rootDir;//获取到根目录 31.forforforfor(longlonglonglongi=0;i0)||(imgVersion<-9&&numBloc ks>=0)){ 49.blocks=newnewnewnewBlock[numBlocks]; 50.forforforfor(intintintintj=0;j1){ 62.blockSize=blocks[0].getNumBytes(); 63.}elseelseelseelse{ 64.longlonglonglongfirst=((numBlocks==1)?blocks[0].getNumBytes():0); 65.blockSize=Math.max(fsNamesys.getDefaultBlockSize(),first); 66.} 67.} 68. 69.longlonglonglongnsQuota=-1L; 70.ifififif(imgVersion<=-16&&blocks==nullnullnullnull){ 71.nsQuota=in.readLong();//如果是目录,读取 namespace配额信息 72.} 73.longlonglonglongdsQuota=-1L; 74.ifififif(imgVersion<=-18&&blocks==nullnullnullnull){ 75.dsQuota=in.readLong();//读取磁盘配额信息 76.} 77. 78.PermissionStatuspermissions=fsNamesys.getUpgradePermission(); 79.ifififif(imgVersion<=-11){ 80.permissions=PermissionStatus.read(in);//读取权限 81.} 82.ifififif(path.length()==0){//如果是根目录,设置其属性值 83.ifififif(nsQuota!=-1||dsQuota!=-1){ 84.fsDir.rootDir.setQuota(nsQuota,dsQuota); 85.} 86.fsDir.rootDir.setModificationTime(modificationTime); 87.fsDir.rootDir.setPermissionStatus(permissions); 88.continuecontinuecontinuecontinue; 89.} 90.//检查新的 inode是否属于相同的父目录 91.ifififif(!isParent(path,parentPath)){ 92.parentINode=nullnullnullnull; 93.parentPath=getParent(path); 94.} 95.//添加一个新的 inode 96.parentINode=fsDir.addToParent(path,parentINode,permissions,blocks, replication,modificationTime,atime,nsQuota,dsQuota,blockSize); 97.} 98.thisthisthisthis.loadDatanodes(imgVersion,in);//加载 Datanode信息 99.thisthisthisthis.loadFilesUnderConstruction(imgVersion,in,fsNamesys);//加载待创建 的文件 100. 101.}finallyfinallyfinallyfinally{ 102.in.close(); 103.} 104.returnreturnreturnreturnneedToSave; 105.} 再 看第 二个 不带 参数 的 loadFSImage 方 法, 它从 一个 目录 中选 择最 新的 映像 文件 并加 载到 内 存中 ,同 时与 这个 目录 中的 EditLog 日 志文 件合 并如 下所 示: view plain 1.booleanbooleanbooleanbooleanloadFSImage()throwsthrowsthrowsthrowsIOException{ 2.//首先检查 FSImage对应的存储中的目录列表,找到最新的 3.longlonglonglonglatestNameCheckpointTime=Long.MIN_VALUE,latestEditsCheckpointTime =Long.MIN_VALUE; 4.StorageDirectorylatestNameSD=nullnullnullnull,latestEditsSD=nullnullnullnull; 5.booleanbooleanbooleanbooleanneedToSave=falsefalsefalsefalse,isUpgradeFinalized=truetruetruetrue; 6.CollectionimageDirs=newnewnewnewArrayList(); 7.CollectioneditsDirs=newnewnewnewArrayList(); 8.forforforfor(Iteratorit=dirIterator();it.hasNext();){ 9.StorageDirectorysd=it.next(); 10.ifififif(!sd.getVersionFile().exists()){ 11.needToSave|=truetruetruetrue; 12.continuecontinuecontinuecontinue;//如果某个 sd目录的版本文件不存在,说明该目录还没有格式化 13.} 14.booleanbooleanbooleanbooleanimageExists=falsefalsefalsefalse,editsExists=falsefalsefalsefalse; 15.ifififif(sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE)){//判断指定 的sd的类型是否是 Namenode的IMAGE类型 16.imageExists=getImageFile(sd,NameNodeFile.IMAGE).exists(); 17.imageDirs.add(sd.getRoot().getCanonicalPath()); 18.} 19.ifififif(sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)){//判断指定 的sd的类型是否是 Namenode的EDITS类型 20.editsExists=getImageFile(sd,NameNodeFile.EDITS).exists(); 21.editsDirs.add(sd.getRoot().getCanonicalPath()); 22.} 23. 24.checkpointTime=readCheckpointTime(sd);//获取 sd检查点时间 25.ifififif((checkpointTime!=Long.MIN_VALUE)&&((checkpointTime!=latestName CheckpointTime)||(checkpointTime!=latestEditsCheckpointTime))){ 26.needToSave|=truetruetruetrue;//根据 sd的检查点时间 ,如果多个不同的时间则强制保存一个 新的映像文件 27.} 28.ifififif(sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE)&&(latestNam eCheckpointTime0);//加载最新的 EditLog日志文 件 61.returnreturnreturnreturnneedToSave; 62.} 2、 从一 个中 断的 检查 点恢 复加 载映 像文 件 该 方法 recoverInterruptedCheckpoint 的 实现 如下 所示 : view plain 1.booleanbooleanbooleanbooleanrecoverInterruptedCheckpoint(StorageDirectorynameSD,StorageDirecto ryeditsSD)throwsthrowsthrowsthrowsIOException{ 2.booleanbooleanbooleanbooleanneedToSave=falsefalsefalsefalse; 3.FilecurFile=getImageFile(nameSD,NameNodeFile.IMAGE);//获取到 fsimage 文件 4.FileckptFile=getImageFile(nameSD,NameNodeFile.IMAGE_NEW);//获取 到 fsimage.ckpt文件 5. 6.ifififif(ckptFile.exists()){//如果正在一个检查点过程当中 ,fsimage.ckpt文件存在 7.needToSave=truetruetruetrue; 8.ifififif(getImageFile(editsSD,NameNodeFile.EDITS_NEW).exists()){//获取 到 edits.new文件,如果存在 9.//检查点进程执行时会上载一个新的合并的映像文件,但是在 Namenode服务器挂机之 前不能保证该映像文件完成上载,故需要保证它是被删除掉的 10.ifififif(!ckptFile.delete()){ 11.throwthrowthrowthrownewnewnewnewIOException("Unabletodelete"+ckptFile); 12.} 13.}elseelseelseelse{//正常情况 14.//当Namenode服务器关闭时检查点进程仍在执行 ,fsimage.ckpt被创建 ,edits.new 文件被重命名为 edits 15.//为了完成该次检查点执行 ,将fsimage.new重命名为 fsimage,而fstime不需要更 新 16.ifififif(!ckptFile.renameTo(curFile)){ 17.ifififif(!curFile.delete()) 18.LOG.warn("Unabletodeletedir"+curFile+"beforerename"); 19.ifififif(!ckptFile.renameTo(curFile)){ 20.throwthrowthrowthrownewnewnewnewIOException("Unabletorename"+ckptFile+"to"+cu rFile); 21.} 22.} 23.} 24.} 25.returnreturnreturnreturnneedToSave; 26.} 通 过上 面可 以看 出 ,检 查点 进程 所执 行的 操作 是对 fsimage 映 像文 件进 行检 查 ,从 而生 成一 个fsimage.ckpt 文 件。 而很 可能 会( 只要 fsimage.ckpt 文 件存 在) 从一 个 fsimage.ckpt 文 件 来加 载 fsimage 映 像文 件 ,在Namenode 停 止的 时候 ,将fsimage.ckpt 重 命名 为 fsimage 完 成此 次检 查点 进程 的执 行, 并把 edits.new 重 命名 为 edits。 3、 切换 映像 实 现方 法rollFSImage,能够将fsimage.ckpt 重 命名 为fsImage,将edits.new 重 命名 为edits, 并 打开 一个 空的 edits 文 件。 实现 如下 所示 : view plain 1.voidvoidvoidvoidrollFSImage()throwsthrowsthrowsthrowsIOException{ 2.ifififif(ckptState!=CheckpointStates.UPLOAD_DONE){//如果检查点 fsimage.ckpt 文件没有上载完成,不能执行回滚操作 3.throwthrowthrowthrownewnewnewnewIOException("CannotrollfsImagebeforerollingeditslog."); 4.} 5.//验证 edits.new与fsimage.ckpt文件存在于所有的检查点目录中 6.ifififif(!editLog.existsNew()){ 7.throwthrowthrowthrownewnewnewnewIOException("NewEditsfiledoesnotexist"); 8.} 9.forforforfor(Iteratorit=dirIterator(NameNodeDirType.IMAGE);i t.hasNext();){//迭代 10.StorageDirectorysd=it.next(); 11.Fileckpt=getImageFile(sd,NameNodeFile.IMAGE_NEW);//获取 fsimage.ckpt文件 12.ifififif(!ckpt.exists()){ 13.throwthrowthrowthrownewnewnewnewIOException("Checkpointfile"+ckpt+"doesnotexist"); 14.} 15.} 16.editLog.purgeEditLog();//重命名 edits.new为edits 17. 18.forforforfor(Iteratorit=(NameNodeDirType.IMAGE);it.hasNext(); ){ 19.StorageDirectorysd=it.next(); 20.Fileckpt=getImageFile(sd,NameNodeFile.IMAGE_NEW);//获取 到 fsimage.ckpt文件 21.FilecurFile=getImageFile(sd,NameNodeFile.IMAGE);//获取到 fsimage文 件 22.ifififif(!ckpt.renameTo(curFile)){//将fsimage.ckpt重命名为 fsimage 23.curFile.delete();//如果 fsimage已经存在则删除它 24.ifififif(!ckpt.renameTo(curFile)){//再次尝试将 fsimage.ckpt重命名 为 fsimage 25.ifififif(sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)) 26.editLog.processIOError(sd); 27.removedStorageDirs.add(sd);//显然, sd存储目录中无法将 fsimage.ckpt重 命名为 fsimage,该 sd已经失效,需要被删除掉 28.it.remove(); 29.} 30.} 31.} 32. 33.//在fsimage与edits对应的所有的目录中,更新 fstime文件,并写 VERSION文件 34. 35.thisthisthisthis.layoutVersion=FSConstants.LAYOUT_VERSION; 36.thisthisthisthis.checkpointTime=FSNamesystem.now(); 37.forforforfor(Iteratorit=dirIterator();it.hasNext();){//迭 代 38.StorageDirectorysd=it.next(); 39.ifififif(!sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)){ 40.FileeditsFile=getImageFile(sd,NameNodeFile.EDITS); 41.editsFile.delete();//删除旧的 edits文件 42.} 43.ifififif(!sd.getStorageDirType().isOfType(NameNodeDirType.IMAGE)){ 44.FileimageFile=getImageFile(sd,NameNodeFile.IMAGE); 45.imageFile.delete();//删除旧的 fsimage文件 46.} 47.trytrytrytry{ 48.sd.write();//向sd目录中写版本文件 VERSION 49.}catchcatchcatchcatch(IOExceptione){ 50.LOG.error("Cannotwritefile"+sd.getRoot(),e); 51.ifififif(sd.getStorageDirType().isOfType(NameNodeDirType.EDITS)) 52.editLog.processIOError(sd); 53.removedStorageDirs.add(sd); 54.it.remove();//删除 sd 55.} 56.} 57.ckptState=FSImage.CheckpointStates.START;//修改检查点状态为开始,准备切 换 58.} 4、 保存 映像 文件 实 现方 法为 saveFSImage, 存在 两个 重载 的方 法。 第 一个 是带 参数 的方 法, 将 fsimage.new 或fsimage 文 件的 内容 进行 保存 。如 下所 示: view plain 1.voidvoidvoidvoidsaveFSImage(FilenewFile)throwsthrowsthrowsthrowsIOException{ 2.FSNamesystemfsNamesys=FSNamesystem.getFSNamesystem(); 3.FSDirectoryfsDir=fsNamesys.dir; 4.longlonglonglongstartTime=FSNamesystem.now(); 5. 6.DataOutputStreamout=newnewnewnewDataOutputStream(newnewnewnewBufferedOutputStream(newnewnewnewF ileOutputStream(newFile)));//创建指定文件的输出流,准备写入 7.trytrytrytry{ 8.out.writeInt(FSConstants.LAYOUT_VERSION);//写入版本号 9.out.writeInt(namespaceID);//写入 namespace的ID 10.out.writeLong(fsDir.rootDir.numItemsInTree());//写入 fsDir目录的根目录树 中INode的数量 11.out.writeLong(fsNamesys.getGenerationStamp());//写入时间戳 12.bytebytebytebyte[]byteStore=newnewnewnewbytebytebytebyte[4*FSConstants.MAX_PATH_LENGTH]; 13.ByteBufferstrbuf=ByteBuffer.wrap(byteStore); 14.saveINode2Image(strbuf,fsDir.rootDir,out);//将fsDir.rootDir的属性都写 入到指定文件中 15.saveImage(strbuf,0,fsDir.rootDir,out);//将fsDir.rootDir的目录树属性 写入到指定文件中 16.fsNamesys.saveFilesUnderConstruction(out);//将INodeUnderConstruction写 入到指定文件中 17.strbuf=nullnullnullnull; 18.}finallyfinallyfinallyfinally{ 19.out.close(); 20.} 21. 22.LOG.info("Imagefileofsize"+newFile.length()+"savedin"+(FSName system.now()-startTime)/1000+"seconds."); 23.} 上 面调 用 saveImage 方法,实 际上 方法 循环 调用 了 saveINode2Image 来 写入 fsDir.rootDir 目 录树 的属 性信 息的 ,可 以参 考这 两个 方法 的实 现。 上 面方 法是 将一 些必 要的 信息 都序 列化 到了 指定 的映 像文 件中 ,作 为 Namenode 掌 握当 前 HDFS 集 群中 重要 信息 的内 存映 像。 第 二个 是不 带参 数的 该方 法, 保存 fsimage.ckpt 文 件的 内容 ,并 创建 一个 新的 edits 文件。 实 现如 下所 示: view plain 1.publicpublicpublicpublicvoidvoidvoidvoidsaveFSImage()throwsthrowsthrowsthrowsIOException{ 2.editLog.createNewIfMissing();//如果 edits.new不存在,则创建一个新的 3.forforforfor(Iteratorit=dirIterator();it.hasNext();){//迭 代 4.StorageDirectorysd=it.next(); 5.NameNodeDirTypedirType=(NameNodeDirType)sd.getStorageDirType(); 6.ifififif(dirType.isOfType(NameNodeDirType.IMAGE)) 7.saveFSImage(getImageFile(sd,NameNodeFile.IMAGE_NEW));//调用,保 存 fsimage.ckpt文件的内容 8.ifififif(dirType.isOfType(NameNodeDirType.EDITS)){ 9.editLog.createEditLogFile(getImageFile(sd,NameNodeFile.EDITS)); 10.FileeditsNew=getImageFile(sd,NameNodeFile.EDITS_NEW); 11.ifififif(editsNew.exists()) 12.editLog.createEditLogFile(editsNew);//创建一个 edits.new文件 13.} 14.} 15.ckptState=CheckpointStates.UPLOAD_DONE;//设置检查点状态为上载完成 16.rollFSImage();//然后执行映像的切换操作:将 fsimage.ckpt改成 fsimage,将 edits.new改成 edits 17.} 5、从 检查 点目 录加 载 fsimage 映 像文 件 实 现的 方法 为 doImportCheckpoint, 如下 所示 : view plain 1.voidvoidvoidvoiddoImportCheckpoint()throwsthrowsthrowsthrowsIOException{ 2.FSImageckptImage=newnewnewnewFSImage();//创建一个 FSImage实例 3.FSNamesystemfsNamesys=FSNamesystem.getFSNamesystem(); 4.FSImagerealImage=fsNamesys.getFSImage();//当前 FSNamesystem中的当 前 fsimage映像 5.assertassertassertassertrealImage==thisthisthisthis; 6.fsNamesys.dir.fsImage=ckptImage;//切换 7. 8.trytrytrytry{ 9.ckptImage.recoverTransitionRead(checkpointDirs,checkpointEditsDirs,Star tupOption.REGULAR);//从检查点目录加载 fsimage映像 10.}finallyfinallyfinallyfinally{ 11.ckptImage.close(); 12.} 13.realImage.setStorageInfo(ckptImage);//更新 layoutVersion、namespaceID、 cTime属性值 14.fsNamesys.dir.fsImage=realImage;//更新 fsNamesys.dir.fsImage 15.saveFSImage();//保存:将 fsimage.ckpt改成 fsimage,将 edits.new改成 edits 16.} 6、 初始 化分 布式 升级 主 要通 过获 取到 UpgradeManagerNamenode 升 级管 理器 ,判 断是 否进 行了 升级 之前 的初 始 化工 作, 只有 做好 初始 化工 作, 才开 始对 fsimage 映 像文 件进 行初 始化 。 实 现方 法 initializeDistributedUpgrade 如 下所 示: view plain 1.privateprivateprivateprivatevoidvoidvoidvoidinitializeDistributedUpgrade()throwsthrowsthrowsthrowsIOException{ 2.UpgradeManagerNamenodeum=FSNamesystem.getFSNamesystem().upgradeManager; 3.ifififif(!um.initializeUpgrade()) 4.returnreturnreturnreturn; 5.FSNamesystem.getFSNamesystem().getFSImage().writeAll();//将与 fsimage相关 的数据都写入到存储中 6.NameNode.LOG.info("/nDistributedupgradeforNameNodeversion" 7.+um.getUpgradeVersion()+"tocurrentLV" 8.+FSConstants.LAYOUT_VERSION+"isinitialized."); 9.} 7、 执行 升级 实 现方 法为 doUpgrade, 如下 所示 : view plain 1.privateprivateprivateprivatevoidvoidvoidvoiddoUpgrade()throwsthrowsthrowsthrowsIOException{ 2.ifififif(getDistributedUpgradeState()){//通过升级管理器,获取当前升级状态 3.//只有分布式升级才执行,并且不对版本升级 4.thisthisthisthis.loadFSImage();//加载 fsimage映像文件 5.initializeDistributedUpgrade();//初始化升级准备工作 6.returnreturnreturnreturn; 7.} 8. 9.//如果当前不存在文件系统的任何信息,可以向下执行升级过程 10.forforforfor(Iteratorit=dirIterator();it.hasNext();){ 11.StorageDirectorysd=it.next(); 12.ifififif(sd.getPreviousDir().exists()) 13.throwthrowthrowthrownewnewnewnewInconsistentFSStateException(sd.getRoot(), 14. "previousfsstateshouldnote xistduringupgrade." 15. +"Finalizeorrollbackfirst.") ; 16.} 17. 18.thisthisthisthis.loadFSImage();//加载最新的映像文件 19. 20.longlonglonglongoldCTime=thisthisthisthis.getCTime(); 21.thisthisthisthis.cTime=FSNamesystem.now();//设置当前时间 22.intintintintoldLV=thisthisthisthis.getLayoutVersion(); 23.thisthisthisthis.layoutVersion=FSConstants.LAYOUT_VERSION; 24.thisthisthisthis.checkpointTime=FSNamesystem.now(); 25.forforforfor(Iteratorit=dirIterator();it.hasNext();){//迭 代:对每个目录分别进行升级准备操作 26.StorageDirectorysd=it.next(); 27.LOG.info("Upgradingimagedirectory"+sd.getRoot() 28.+"./noldLV="+oldLV 29.+";oldCTime="+oldCTime 30.+"./nnewLV="+thisthisthisthis.getLayoutVersion() 31.+";newCTime="+thisthisthisthis.getCTime()); 32.FilecurDir=sd.getCurrentDir();//获取到 current目录 33.FileprevDir=sd.getPreviousDir();//获取到 previous目录 34.FiletmpDir=sd.getPreviousTmp();//获取到 previous.tmp目录 35.assertassertassertassertcurDir.exists():"Currentdirectorymustexist."; 36.assertassertassertassert!prevDir.exists():"prviousdirectorymustnotexist."; 37.assertassertassertassert!tmpDir.exists():"prvious.tmpdirectorymustnotexist."; 38. 39.rename(curDir,tmpDir);//将current目录重命名为 previous.tmp 40.ifififif(!curDir.mkdir())//创建 current目录 41.throwthrowthrowthrownewnewnewnewIOException("Cannotcreatedirectory"+curDir); 42.saveFSImage(getImageFile(sd,NameNodeFile.IMAGE));//保存最新的映像 43.editLog.createEditLogFile(getImageFile(sd,NameNodeFile.EDITS));//创建 一个新的 edits文件 44.sd.write();//写版本文件 45. 46.rename(tmpDir,prevDir);//将previous.tmp重命名为 previous 47.isUpgradeFinalized=falsefalsefalsefalse;//等待升级进程确认 48.LOG.info("Upgradeof"+sd.getRoot()+"iscomplete."); 49.} 50.initializeDistributedUpgrade();//初始化分布式升级的工作 51.editLog.open();//打开 edits日志文件 52.} 8、 执行 升级 后的 后继 清理 当 执行 升级 的时 候, 可能 需要 执行 目录 的重 命名 ,或 者删 除一 些临 时文 件目 录, 对指 定 的 StorageDirectory 目 录执 行清 理工 作的 方法 为 doFinalize, 如下 所示 : view plain 1.privateprivateprivateprivatevoidvoidvoidvoiddoFinalize(StorageDirectorysd)throwsthrowsthrowsthrowsIOException{ 2.FileprevDir=sd.getPreviousDir(); 3.ifififif(!prevDir.exists()){//该临时目录 previous.tmp已经被清理了 4.LOG.info("Directory"+prevDir+"doesnotexist."); 5.LOG.info("Finalizeupgradefor"+sd.getRoot()+"isnotrequired."); 6.returnreturnreturnreturn; 7.} 8.LOG.info("Finalizingupgradeforstoragedirectory"+sd.getRoot()+"." +(getLayoutVersion()==0?"":"/ncurLV=" 9.+thisthisthisthis.getLayoutVersion()+";curCTime="+thisthisthisthis.getCTime())); 10.assertassertassertassertsd.getCurrentDir().exists():"Currentdirectorymustexist."; 11.finalfinalfinalfinalFiletmpDir=sd.getFinalizedTmp();//获取全部 finalized.tmp临时目 录 12.rename(prevDir,tmpDir);//将previous.tmp重命名为 finalized.tmp 13.deleteDir(tmpDir);//删除 finalized.tmp 14.isUpgradeFinalized=truetruetruetrue;//升级之后的清理工作已经完成 15.LOG.info("Finalizeupgradefor"+sd.getRoot()+"iscomplete."); 16.} 上 面的 方法 比较 容易 。如 果想 要执 行批 量清 理, 调用 方法 finalizeUpgrade 可 以实 现, 如下 所 示: view plain 1.voidvoidvoidvoidfinalizeUpgrade()throwsthrowsthrowsthrowsIOException{ 2.forforforfor(Iteratorit=dirIterator();it.hasNext();){ 3.doFinalize(it.next()); 4.} 5.} 该 方法 对全 部的 目录 执行 清理 工作 。 9、 回滚 操作 执 行回 滚操 作时 有条 件的 ,如 果文 件系 统不 存在 一个 先前 的状 态 ,是 不能 够执 行回 滚操 作的 , 因 为根 本无 法将 当前 的 fsimag 改 变成 上一 个状 态。 另外 ,文 件系 统先 前的 一个 状态 的信 息 保 存在 一个 或多 个存 储目 录中 ,获 取到 这些 先前 的状 态信 息才 能够 执行 回滚 操作 。而 对于 不 存 在文 件系 统状 态信 息的 存储 目录 是不 需要 回滚 的。 实 现回 滚操 作的 方法 为 doRollback, 如下 所示 : view plain 1.privateprivateprivateprivatevoidvoidvoidvoiddoRollback()throwsthrowsthrowsthrowsIOException{ 2.booleanbooleanbooleanbooleancanRollback=falsefalsefalsefalse; 3.FSImageprevState=newnewnewnewFSImage();//获取先前文件系统的映像 4.prevState.layoutVersion=FSConstants.LAYOUT_VERSION; 5.forforforfor(Iteratorit=dirIterator();it.hasNext();){ 6.StorageDirectorysd=it.next(); 7.FileprevDir=sd.getPreviousDir(); 8.ifififif(!prevDir.exists()){//usecurrentdirectorythen 9.LOG.info("Storagedirectory"+sd.getRoot()+"doesnotcontainprev iousfsstate."); 10.sd.read();//读取版本 VERSION文件,验证与其他目录的一致性 11.continuecontinuecontinuecontinue; 12.} 13.StorageDirectorysdPrev=prevState.newnewnewnewStorageDirectory(sd.getRoot()); 14.sdPrev.read(sdPrev.getPreviousVersionFile());//读取版本 VERSION文件,验 证与先前目录的一致性 15.canRollback=truetruetruetrue;//可以执行回滚操作标志 16.} 17.ifififif(!canRollback) 18.throwthrowthrowthrownewnewnewnewIOException("Cannotrollback."+"Noneofthestoragedirecto riescontainpreviousfsstate."); 19. 20.//所有目录的一致性检查通过,执行回滚,使得每个目录都包含先前的状态 21.forforforfor(Iteratorit=dirIterator();it.hasNext();){ 22.StorageDirectorysd=it.next(); 23.FileprevDir=sd.getPreviousDir();//获取到先前的 previous目录 24.ifififif(!prevDir.exists()) 25.continuecontinuecontinuecontinue; 26. 27.LOG.info("Rollingbackstoragedirectory"+sd.getRoot()+"./nnewL V="+prevState.getLayoutVersion()+";newCTime="+prevState.getCTime ()); 28.FiletmpDir=sd.getRemovedTmp();//获取到 removed.tmp目录 29.assertassertassertassert!tmpDir.exists():"removed.tmpdirectorymustnotexist."; 30. 31.FilecurDir=sd.getCurrentDir();//获取到 current目录 32.assertassertassertassertcurDir.exists():"Currentdirectorymustexist."; 33.rename(curDir,tmpDir);//将current重命名为 removed.tmp 34.rename(prevDir,curDir);//将previous重命名为 current 35.deleteDir(tmpDir);//删除临时 removed.tmp目录 36.LOG.info("Rollbackof"+sd.getRoot()+"iscomplete."); 37.} 38.isUpgradeFinalized=truetruetruetrue;//清理完成 39.verifyDistributedUpgradeProgress(StartupOption.REGULAR);//检查 Namenode是 否能够以 REGULAR模式启动 40.} 可 见, 回滚 操作 就是 ,在 保证 全部 目录 一致 性检 查通 过的 情况 下, 将当 前存 在的 previous 目 录重 命名 为 current 目 录, 表示 将 previous 目 录状 态覆 盖到 current 目 录上 ,然 后删 除被 覆 盖掉 的 current 目 录( 通过 先重 命名 current 为removed.tmp, 然后 执行 删除 )。 10、 格式 化操 作 对 文件 系统 中的 StorageDirectory 存 储目 录进 行格 式化 操作 ,存 在两 个重 载的 方法 ,分 别介 绍 如下 。 第 一个 带参 数的 方法 ,是 对指 定的 存储 目录 进行 格式 化, 如下 所示 : view plain 1.voidvoidvoidvoidformat(StorageDirectorysd)throwsthrowsthrowsthrowsIOException{ 2.sd.clearDirectory();//创建currrent目录 ,如果该目录存在 ,则会删除存在 的current 目录树 3.sd.lock();//加锁,对应的文件为 in_use.lock 4.trytrytrytry{ 5.NameNodeDirTypedirType=(NameNodeDirType)sd.getStorageDirType();//获 取到存储目录类型 6.ifififif(dirType.isOfType(NameNodeDirType.IMAGE))//如果是 fsimage目录 7.saveFSImage(getImageFile(sd,NameNodeFile.IMAGE));//保存 fsimage映 像 8.ifififif(dirType.isOfType(NameNodeDirType.EDITS))//如果是 edits日志文件目录 9.editLog.createEditLogFile(getImageFile(sd,NameNodeFile.EDITS));//创 建一个新的 edits文件 10.sd.write();//写版本文件 VERSION 11.}finallyfinallyfinallyfinally{ 12.sd.unlock();//释放锁,清除 in_use.lock文件 13.} 14.LOG.info("Storagedirectory"+sd.getRoot()+"hasbeensuccessfullyfor matted."); 15.} 比 较容 易理 解。 第 二个 不带 参数 的方 法, 是批 量进 行格 式化 ,如 下所 示: view plain 1.publicpublicpublicpublicvoidvoidvoidvoidformat()throwsthrowsthrowsthrowsIOException{ 2.thisthisthisthis.layoutVersion=FSConstants.LAYOUT_VERSION; 3.thisthisthisthis.namespaceID=newNamespaceID(); 4.thisthisthisthis.cTime=0L; 5.thisthisthisthis.checkpointTime=FSNamesystem.now(); 6.forforforfor(Iteratorit=dirIterator();it.hasNext();){//循 环 7.StorageDirectorysd=it.next(); 8.format(sd);//对每一个 sd执行格式化操作 9.} 10.} 11、 加载 Datanode 信息 事 实上 ,对 于新 版本 的 Hadoop 已 经不 再使 用 Datanode 的 映像 了 ,该 方法 是为 了兼 容旧 版 本 的, 如下 所示 : view plain 1.voidvoidvoidvoidloadDatanodes(intintintintversion,DataInputStreamin)throwsthrowsthrowsthrowsIOException{ 2.ifififif(version>-3)//之前版本不存在 Datanode信息的映像 3.returnreturnreturnreturn; 4.ifififif(version<=-12){ 5.returnreturnreturnreturn;//新版本不需要存储 Datanode映像 6.} 7.intintintintsize=in.readInt(); 8.forforforfor(intintintinti=0;i

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 2 人已下载

下载文档

相关文档