通过前面,对Hadoop的org.apache.hadoop.fs包中内容进行分析,已经基本了解到,一个文件系统应该具备哪些基本要素和基本操作。最显著的一个特点就是,FileSystem文件系统是基于流式数据访问的,并且,可以基于命令行的方式来对文件系统的文件进行管理与操作。而且,基于FileSystem文件系统的抽象定义,我们可以了解到,继承自该抽象的一切具体实现的文件系统,都具有统一的文件访问接口。
对于HDFS(Hadoop Distributed FileSystem),同样也是基于FileSystem的抽象实现的,下面看org.apache.hadoop.hdfs包中的代码,该包中的类都是与一个基本的HDFS相关的。
先看继承自FileSystem的实现类DistributedFileSystem。该分布式文件系统是可配置的,在继承自FileSystem的配置的基础上,还有它专有的DFS配置文件,如下所示:
static{ Configuration.addDefaultResource("hdfs-default.xml"); Configuration.addDefaultResource("hdfs-site.xml"); }
第一个是DFS默认的配置文件,第二个是用户可以根据需要进行定制的配置选项,如果hdfs-site.xml中定义的属性在hdfs-default.xml中已经存在,会覆盖掉hdfs-default.xml中默认的属性值。
该类中有一个内部类DiskStatus,用来表示一个DistributedFileSystem文件系统中磁盘使用的统计信息,如下所示:
public static class DiskStatus { private long capacity; // 总容量 private long dfsUsed; // DFS使用量 private long remaining; // 可用量 public DiskStatus(long capacity, long dfsUsed, long remaining) { this.capacity = capacity; this.dfsUsed = dfsUsed; this.remaining = remaining; } public long getCapacity() { return capacity; } public long getDfsUsed() { return dfsUsed; } public long getRemaining() { return remaining; } }
在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方法:
public void initialize(URI uri, Configuration conf) throws IOException { super.initialize(uri, conf); // 继承自FileSystem setConf(conf); String host = uri.getHost(); // 根据URI获取到Namenode主机 if (host == null) { throw new IOException("Incomplete HDFS URI, no host: "+ uri); } InetSocketAddress namenode = NameNode.getAddress(uri.getAuthority()); //获取到Namenode的Socket地址 this.dfs = new DFSClient(namenode, conf, statistics); // 构造一个DFSClient实例 this.uri = NameNode.getUri(namenode); this.workingDir = getHomeDirectory(); }
通过这个初始化的方法可以看到,一个DFSClient实例要想执行DFS上的任务,必须与Namenode建立连接,在与Namenode通信获取许可的情况下才能执行任务。
DistributedFileSystem类的定义基本操作,主要是从它与FilesSstem抽象类定义的不同操作来了解。如下所示:
/** * 获取文件系统的磁盘使用情况统计数据 */ public DiskStatus getDiskStatus() throws IOException { return dfs.getDiskStatus(); } /** * 设置安全模式状态 */ public boolean setSafeMode(FSConstants.SafeModeAction action) throws IOException { return dfs.setSafeMode(action); } /** * 对文件系统名字空间映像进行保存 */ public void saveNamespace() throws AccessControlException, IOException { dfs.saveNamespace(); } /** * 刷新主机列表 */ public void refreshNodes() throws IOException { dfs.refreshNodes(); } /** *返回标识为失效状态的块副本的数目 */ public long getCorruptBlocksCount() throws IOException { return dfs.getCorruptBlocksCount(); } /** * 返回每个Datanode结点的状态信息 */ public DatanodeInfo[] getDataNodeStats() throws IOException { return dfs.datanodeReport(DatanodeReportType.ALL); } /* * 请求Namenode结点,将文件系统元数据写入到指定文件pathname中,如果该文件已经存在,则追加写入。 */ public void metaSave(String pathname) throws IOException { dfs.metaSave(pathname); }
可见,实际上还是通过org.apache.hadoop.hdfs.DFSClient来实现保存的操作的。还有一个用来报告块副本的校验和出错的方法:
public boolean reportChecksumFailure(Path f, FSDataInputStream in, long inPos, FSDataInputStream sums, long sumsPos)
目前实现是,如果某个文件块及其对应的块副本的校验和不匹配,只能够知道该文件块状态有问题,没有实现报告具体是哪个块副本出了问题。
一个分布式文件系统实例 ,是通过DFS Client来进行文件的创建、删除、复制等等操作的。因此,如果希望了解DFSClient如何执行这些操作的,就需要对DFSClient 的实现进行分析了。