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包中类的继承关系:
◦java.lang.Object ◦org.apache.hadoop.security.Group (implements java.security.Principal) ◦org.apache.hadoop.security.SecurityUtil ◦org.apache.hadoop.security.SecurityUtil.AccessControlList ◦java.lang.Throwable (implements java.io.Serializable) ◦java.lang.Exception ◦java.io.IOException ◦org.apache.hadoop.fs.permission.AccessControlException ◦org.apache.hadoop.security.AccessControlException ◦org.apache.hadoop.security.User (implements java.security.Principal) ◦org.apache.hadoop.security.UserGroupInformation (implements java.security.Principal, org.apache.hadoop.io.Writable) ◦org.apache.hadoop.security.UnixUserGroupInformation
下面分别对其中关键类的源代码进行阅读分析:
Group类与User类
首先,org.apache.hadoop.security.Group类与org.apache.hadoop.security.User类都是一个实体类,表征一个属于HDFS文件系统中存在的一类实体,它们的定义非常相似,下面只拿出Group类来说明。
Group类表示一个组的概念实现,它实现了java.security.Principal接口,也就是说Group类表示一个用来容纳一些对象的实体,比如一个组中可以包含多个不同的用户,一个组中可以包含多种不同的权限,一个组中还可以包含多种授权的证书,等等。这个类比较容易,包含一个final修饰的组名称的字段,也就是说一个组一旦创建就不能修改组名称,Group类没有提供修改组名称的方法。新创建一个组的时候,需要指定组名称。该类中比较重要的是equals方法,用来比较某个Object对象(组对象)是够与该组(this)相互匹配。
UserGroupInformation抽象类
在Hadoop框架中,分布式文件系统框架HDFS具有一个用来存储用户和组信息的实现,它是通过一个位于org.apache.hadoop.security包中的UserGroupInformation抽象类来抽象这些信息的,如果对于特定的基于用户和组的操作系统,都可以继承自该抽象类,用来实现表示用户与组的一些信息的实体,及其一些简单的操作。
下面是抽象类的源代码:
package org.apache.hadoop.security; import java.io.IOException; import java.security.AccessController; import java.security.Principal; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.login.LoginException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io.Writable; /** 该类是一个用来储存用户和组信息的抽象类,并且它实现了Hadoop定义的用来实现序列化的接口Writable类,也就是说,用户和组的信息是可序列化的。 */ public abstract class UserGroupInformation implements Writable, Principal { public static final Log LOG = LogFactory.getLog(UserGroupInformation.class); private static UserGroupInformation LOGIN_UGI = null; // 用户组信息属性 private static final ThreadLocal<Subject> currentUser = new ThreadLocal<Subject>(); // 线程局部Subject变量 /** 获取当前用户线程的UserGroupInformation信息 */ public static UserGroupInformation getCurrentUGI() { Subject user = getCurrentUser(); // 调用,得到当前用户线程的Subject if (user == null) { // 如果为null user = currentUser.get(); // 获取当前用户线程当前线程局部变量拷贝的值 if (user == null) { // 没能获取到当前用户的Subject,无法认证,直接返回 return null; } } Set<UserGroupInformation> ugiPrincipals = user.getPrincipals(UserGroupInformation.class); // 获取用户身份信息 UserGroupInformation ugi = null; if (ugiPrincipals != null && ugiPrincipals.size() == 1) { ugi = ugiPrincipals.iterator().next(); if (ugi == null) { throw new RuntimeException("Cannot find _current user_ UGI in the Subject!"); } } else { throw new RuntimeException("Cannot resolve current user from subject, " + "which had " + ugiPrincipals.size() + " UGI principals!"); } return ugi; } /** * 根据构造的UserGroupInformation实例,为当前线程设置用户和组信息 */ @Deprecated public static void setCurrentUGI(UserGroupInformation ugi) { setCurrentUser(ugi); } /** * 获取当前用户所拥有的Subject实例 */ static Subject getCurrentUser() { return Subject.getSubject(AccessController.getContext()); // 根据线程当前调用上下文(例如堆栈信息),构造(或者获取到)当前用户的Subject实例 } /** * 设置当前用户线程所具有的UserGroupInformation的信息 * WARNING - This method should be used only in test cases and other exceptional * cases! */ public static void setCurrentUser(UserGroupInformation ugi) { Subject user = SecurityUtil.getSubject(ugi); // 根据ugi信息获取当前用户的Subject实例 currentUser.set(user); // 将当前用户线程局部变量,当前线程副本中的值设置为根据ugi获取到的Subject实例 } /** 获取用户名称 */ public abstract String getUserName(); /** 获取到一个用户所属的组(可能该用户属于多个组)的名称 */ public abstract String[] getGroupNames(); /** 登录系统的方法实现,如果登录成功则返回登录用户的一些信息,包括用户、组相关信息的一个实例 */ public static UserGroupInformation login(Configuration conf) throws LoginException { if (LOGIN_UGI == null) { LOGIN_UGI = UnixUserGroupInformation.login(conf); // 默认使用Hadoop实现的UnixUserGroupInformation来进行登录 } return LOGIN_UGI; } /** 从Hadoop的配置Configuration 类实例中读取UserGroupInformation的信息 */ public static UserGroupInformation readFrom(Configuration conf) throws IOException { try { return UnixUserGroupInformation.readFromConf(conf, UnixUserGroupInformation.UGI_PROPERTY_NAME); // 默认使用UnixUserGroupInformation实现类来读取Hadoop配置类实例,获取用户和组信息 } catch (LoginException e) { throw (IOException)new IOException().initCause(e); } } }
该类中使用到javax.security.auth.Subject类,该类的实例包含了一个实体的两种信息:一个是用来认证的身份信息,另一个与该用户安全相关的信息,例如许可证书。
UserGroupInformation类实现了org.apache.hadoop.io.Writable接口,该接口是Hadoop框架基于DataInput和DataOutput定义的一个序列化协议,实现该接口的类支持序列化操作。org.apache.hadoop.io.Writable接口定义如下所示:
package org.apache.hadoop.io; import java.io.DataOutput; import java.io.DataInput; import java.io.IOException; public interface Writable { /** * 将对象(this)的属性字段序列化到输出流DataOuput out中。 */ void write(DataOutput out) throws IOException; /** * 从输入流DataInput in中读取属性字段信息,重组为(this)对象,这是一个反序列化操作。 */ void readFields(DataInput in) throws IOException; }
总结一下,UserGroupInformation抽象类主要定义的操作如下:
1、获取当前用户线程的用和组信息(UGI),通过getCurrentUGI()方法实现的;
2、获取用户名和组名,分别通过抽象方法getUserName()和getGroupNames()方法实现的;
3、根据Hadoop的配置类Configuration实例,登录系统后返回一个UserGroupInformation 类的实例,通过方法login(Configuration conf)实现的;
4、读取Hadoop的配置类Configuration实例,返回一个UserGroupInformation 类的实例,通过方法readFrom(Configuration conf)实现的。