首页 > 分布式 > Hadoop > HDFS体系结构及操作(命令行、Java程序)
2015
04-27

HDFS体系结构及操作(命令行、Java程序)

当数据集的大小超过一台计算机的存储能力时,就有必要将它存储到多台计算机上,但是不方便管理和维护,此时就迫切需要一种系统来统一管理分布在多台机器上的文件,这就是分布式文件系统。一个分布式文件系统应该具备如下特点:

  • 透明性:虽然是通过网络来访问分布在各节点上的文件,但在用户看来,就像是访问本地的磁盘一般。
  • 容错性:某些节点宕机时,系统仍然可以持续运作且不会有数据损失。
  • 负载均衡:增加节点时,重新均衡数据的分布

分布式文件系统有很多(如GFS、TFS、GridFS等),HDFS只是其中的一种。HDFS也是一种文件系统,因此在初次接触的时候,你可以对比Windows或Linux下的文件系统进行学习,一个文件系统不可避免的应该支持创建文件夹、 创建文件、 移动文件、 复制文件、 删除文件、 编辑文件、 查找文件等等。

一、HDFS的操作(上):命令行

本节通过命令行的交互来初步认识HDFS。HDFS的操作命令比较类似于Linux中的文件操作命令,在进行操作时,一定要确保HDFS是正常运行的,即通过jps命令可以看到NameNode、DataNode和SecondaryNameNode等进程。

Hadoop文件系统的命令为hadoop fs,其提供了一系列子命令(or选项)。通过hadoop fs命令可以获取所有选项列表,如下图所示(部分):

03. HDFS体系结构及操作(命令行、Java程序)625

hadoop fs支持的选项有:

  • -help:获取子命令的帮助信息,如hadoop fs -help put,获取-put的帮助信息。
  • -appendToFile:将给定的文件追加到目标文件后面
  • -copyFromLocal:从本地拷贝到HDFS上,等同于-put
  • -moveFromLocal:从本地移动到HDFS上
  • -copyToLocal:从HDFS上拷贝文件到本地,等同于-get
  • -moveToLocal:从HDFS上移动文件到本地
  • -touchz:创建空白文件

除此之外,还有-ls、-mkdir、-mv、-rm、-du、-df、-tail、-cat等选项,这些选项和对应的Linux shell命令用法及其类似。

最后以-mkdir、-put和-ls选项为例说明hadoop fs命令的用法,需要注意的是hadoop fs命令中HDFS文件路径的写法,如下图:

03. HDFS体系结构及操作(命令行、Java程序)1016

不难发现:

  • 在表示HDFS上的文件路径时:hdfs://hadoop01:9000/code和/code两种方式是等同的,其实最后都会转换成第一种形式。这是因为在core-site.xml中定义了fs.defaultFS的值是hdfs://hadoop01:9000。
  • hadoop fs -ls 返回的结果信息同Linux中的ls -l输出结果非常相似,区别在于:第二列是这个文件的副本数,由于我们设置的dfs.replication为1,所以第一行显示的为1,第二行、第三行显示的目录副本数为空,是因为目录是作为元数据保存在namenode中,而非datanode中。

当然,这些只是hadoop fs子命令的一部分,对于它们的更多用法,需要慢慢体会,初学者应重点掌握 ls、rm、mkdir、put、get的使用。

二、HDFS的基本概念与体系结构

1、HDFS的基本概念

HDFS分布式文件系统的命名空间提供了一个统一的“界面”:使用一个路径就可以访问分布在多台机器上的文件。该路径表示一个抽象的文件,实际并不存在,文件最终会被分成多个数据块并写到集群里的某些机器上。文件及其分块之间的映射关系应由一个元数据管理系统来维护。

在上面对HDFS基本思想的描述中,大致涉及了如下的基本概念:

  • path:路径,HDFS对外提供的文件访问路径
  • block:数据块,将文件分块存储的好处是可以实现负载均衡和保证系统的吞吐量
  • metadata:元数据,不仅包括文件及其分块之间的映射信息,还包括文件系统的名称空间、文件访问权限等
  • datanode:存储数据块的节点
  • namenode:元数据管理系统所在的节点
  • replication:副本,为了保证数据的安全性,一般将客户端存进来的文件保存为多个副本

2、HDFS的体系结构

HDFS采用master/slave架构:一个HDFS集群由一个NameNode节点和若干的DataNode节点组成,NameNode保存并维护HDFS的所有元数据,DataNode存储实际的文件数据块。HDFS的架构如下图所示,如果看不习惯英文,本小节后面有该图的中文版:

03. HDFS体系结构及操作(命令行、Java程序)1918

(该架构来源于:http://hadoop.apache.org/docs/r2.4.1/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html)

上图展示了HDFS的NameNode、DataNode以及客户端的存储访问关系,下面是对上图的简单解释:

  • MetaData ops:NameNode接收用户的文件访问请求,完成元数据的更新操作,并返回文件的元数据信息。
  • Block ops:NameNode决定block到具体DataNode节点的映射,Datanode在Namenode的指挥下进行block的创建、删除和复制操作
  • Read、Write:对HDFS存储的用户文件的读、写操作直接在DataNode上完成,不需经过NameNode。即NameNode只管理HDFS系统的元数据信息,不存储具体的用户数据。
  • Replication:出于可靠性的考虑,每个块都会复制到多个DataNode服务器上。

最后附中文版的HDFS架构图:

03. HDFS体系结构及操作(命令行、Java程序)2364

3、元数据存储细节

元数据结构:(FileName, replications , block-ids, id2host…) 其中replications表示文件在HDFS中存储的副本数。

举个例子,一个文件a.log的所有分块在DataNodes中的分布如下图所示,则其元数据可表示为:/test/a.log, 3 ,{blk_1, blk_2}, [{blk_1:[h0,h1,h3]}, {blk_2:[h0,h2,h4]}]

03. HDFS体系结构及操作(命令行、Java程序)2586

4、NameNode和SecondaryNameNode的通信模型

客户端的读写请求都要访问元数据,因此需要将HDFS元数据放在NameNode节点的内存中,以快速响应客户端的请求。但同时也存在一些问题:如果NameNode节点宕机就会丢失所有的HDFS元数据信息。

很容易想到的就是将这些元数据在磁盘中也存储一份,当元数据改变时,同步更新内存和磁盘上的元数据。但是这样又有问题了:更新磁盘中的元数据很耗资源,特别是直接在NameNode节点上进行这一操作会延迟用户请求的响应时间。HDFS在解决这一问题时,将磁盘上的元数据分成了两部分:fsimage和edits(位于${hadoop.tmp.dir}/dfs/name/current目录下),并引入了一个新的节点:SecondaryNameNode。

  • fsimage是元数据的镜像文件,存储某一时段NameNode内存中的元数据信息
  • edits是一个操作日志文件,记录文件系统的改动信息

☆ 以上只是一些前奏,now,重点来了 ☆

当一个NameNode启动时,它首先从一个fsimage中读取HDFS的状态,接着执行edits中的编辑操作,然后将新的HDFS状态写入fsimage中,并使用一个空的edits文件记录操作的日志。

如果NameNode只在在启动阶段才合并fsimage和edits,那么久而久之日志文件可能会变得非常庞大,特别是对于大型的集群。日志文件太大的另一个副作用是下一次NameNode启动会花很长时间来合并fsimage和edits,为此引入了SecondaryNameNode,每隔一段时间它就会从NameNode上下载元数据信息(fsimage和edits),然后把二者合并,生成新的fsimage,并将其推送到NameNode,替换旧的fsimage。NameNode和SecondaryNameNode的通信示意图如下:

03. HDFS体系结构及操作(命令行、Java程序)3397

结合上图,SecondaryNameNode的工作流程(checkpoint)如下:

① secondary通知namenode切换edits文件

② secondary从namenode获得fsimage和edits(通过http协议)

③ secondary将fsimage载入内存,然后开始执行edits中的操作

④ secondary将新的fsimage发回给namenode

⑤ namenode用新的fsimage替换旧的fsimage

Secondary NameNode的checkpoint进程启动,是由以下两个配置参数控制的:

  • fs.checkpoint.period指定连续两次检查点的最大时间间隔,默认值是1小时。
  • fs.checkpoint.size定义了edits的最大值,一旦超过这个值会导致强制执行checkpoint,即使没到最大时间间隔,默认值是64MB

根据上面的概念,我们可以说fsimage文件中保存的是最新检查点的元数据。

5、DataNode

1)文件分块(block)

DataNode节点上存储了真实的文件数据块(block)。在HDFS中,block是最基本的存储单位。

Question:一个300M的文件在HDFS上是如何存储的呢?

Answer:假设文件大小为300M,从字节位置0开始,每128M划分为一个block,共划分成3个block,每个block的大小分别为128M、128M、44M。128M是HDFS中的默认block大小,由属性dfs.blocksize规定,可在hdfs-site.xml中覆盖该值。

Question:DataNode节点上的文件块存储在哪?

Answer:block在Linux系统的存放位置由属性dfs.datanode.data.dir的值规定,在hdfs-default.xml中查到该属性的值为:${hadoop.tmp.dir}/dfs/data。下图演示将一个133M的文件上传到HDFS时,被划分成两个block存储在DataNode节点上。

03. HDFS体系结构及操作(命令行、Java程序)4280
2)副本replication

为了保证HDFS中文件的安全可靠,每个文件在HDFS中都存储多份,这就是HDFS中的副本机制(replication),副本实际上就是备份。

HDFS中默认的副本数为3,这意味着意味着HDFS中的每个数据块都有3份。副本数由dfs.replication属性指定,可以在hdfs-site.xml中覆盖该值。

三、HDFS的操作(下):Java程序

HDFS提供了一个Java操作接口:FileSystem,该类中含有操作HDFS的各种方法。下面的HDFS程序演示了FileSystem的用法,包括:上传文件到HDFS、下载HDFS上的文件、在HDFS上创建目录、删除文件或目录以及获取HDFS上的文件列表。

public class FileSystemTest {

	private FileSystem fileSystem;

	@Before
	public void init() throws IOException {
		Configuration conf = new Configuration(); //加载配置文件
		conf.set("dfs.replication", "1");
		conf.set("fs.defaultFS", "hdfs://hadoop01:9000");
		fileSystem = FileSystem.get(conf);
	}

	@Test
	public void uploadFile() throws Exception {
		//本地文件
		InputStream is = new FileInputStream(new File("/home/hadoop/baby.jpg"));
		//HDFS上的文件
		FSDataOutputStream os = fileSystem.create(new Path("/baby.jpg"));

		IOUtils.copy(is, os);
	}

	@Test
	public void uploadFileSimple() throws Exception {
		fileSystem.copyFromLocalFile(new Path("/home/hadoop/baby.jpg"), new Path("/baby01.jpg"));
	}

	@Test
	public void downloadFile() throws Exception {
		//HDFS file
		FSDataInputStream is = fileSystem.open(new Path("/baby.jpg"));
		//local file
		OutputStream os = new FileOutputStream(new File("/home/hadoop/baby.new.jpg"));

		IOUtils.copy(is, os);
	}

	@Test
	public void downloadFileSimple() throws Exception{
		fileSystem.copyToLocalFile(new Path("/baby.jpg"), new Path("/home/hadoop/baby.new2.jpg"));
	}

	@Test
	public void mkdir() throws Exception{
		//可以一次创建多层目录
		boolean result = fileSystem.mkdirs(new Path("/aa/bb/cc"));
		System.out.println(result ? "success" : "fail"); //echo success
	}

	@Test
	public void rmFileOrDir() throws Exception {
		//第二个参数表示是否递归删除
		boolean result = fileSystem.delete(new Path("/aa"), true);
		System.out.println(result ? "success" : "fail"); //echo success
	}

	@Test
	public void listFiles() throws Exception {
		RemoteIterator<LocatedFileStatus> files = fileSystem.listFiles(new Path("/"), true);
		while (files.hasNext()) {
			LocatedFileStatus file = files.next();
			//file path
			String path = file.getPath().getName();
			//file blocks
			BlockLocation[] blocks = file.getBlockLocations();
			//print info
			System.out.println(path + " has " + blocks.length + " blocks");
		}

		/*
		 *  echo:
		 *  baby.jpg has 1 blocks
		 *  baby01.jpg has 1 blocks
		 */
	}
}

编写HDFS程序应注意:

  • new Configuration()会加载位于类路径下的配置文件,读取属性配置文件中的属性值,当然也可以通过程序直接设置属性值。
  • 如果类路径下没有配置文件,且在程序中没有进行相关属性的设置(如fs.defaultFS属性),那么new Path()默认表示本地文件系统中的路径。本例中new Path()代表HDFS文件系统中的路径。

本博客将在下篇文章《Hadoop RPC机制及HDFS源码分析》中详细讲解FileSystem的内部实现(包括get、open、create)以及HDFS中的文件读写流程。


HDFS体系结构及操作(命令行、Java程序)》有 1 条评论

  1. 感谢的无私分享!

留下一个回复

你的email不会被公开。