1**、初识****HDFS**
HDFS作为一个分布式文件系统,具有高容错的特点,它可以部署在廉价的通用硬件上,提供高吞吐率的数据访问,适合那些需要处理海量数据集的应用程序。HDFS没有遵循可移植操作系统接口(Portable Operation SystemInterface,POSIX)的要求,不支持“ls”或“cp”这样的标准UNIX命令,也不支持如fopen()和fread()这样的文件读写方法,而是提供了一套特有的、基于Hadoop抽象文件系统的API,支持以流的形式访问文件系统中的数据。
A**、HDFS的主要特性**
HDFS的主要特性包括:
支持超大文件。超大文件在这里指的是几百MB、几百GB甚至几TB大小的文件,一般来说,一个Hadoop文件系统会存储T(1TB=1024GB)、P(1P=1024T)级别的数据。Hadoop需要能够支持这种级别的大文件;
*
检测和快速应对硬件故障。在大量通用硬件平台上构建集群时,故障,特别是硬件故障是常见的问题。一般的HDFS系统是由数百台甚至上千台存储着数据文件的服务器组成,这么多的服务器意味着高故障率。因此,故障检测和自动恢复是HDFS的一个设计目标;
*
流式数据访问。HDFS处理的数据规模都比较大,应用一次需要访问大量的数据。同时,这些应用一般是批量处理,而不是用户交互式处理。HDFS使应用程序能够以流的形式访问数据集,注重的是数据的吞吐量,而不是数据访问的速度;
*
简化的一致性模型。大部分的HDFS程序操作文件时需要一次写入,多次读取,在HDFS中,一个文件一旦经过创建、写入、关闭后,一般就不需要修改了。这样简单的一致性模型,有利于提供高吞吐量的数据访问模型。
正是由于以上的设计目标,HDFS并不适合如下应用:
低延迟数据访问。低延迟数据,如和用户进行交互的应用,需要数据在毫秒或秒的范围内得到响应。由于Hadoop针对高数据吞吐量做了优化,而牺牲了获取数据的延迟,对于低延迟访问,可以考虑使用HBase或Cassandra;
*
大量的小文件。HDFS支持超大文件,是通过将数据分布在数据节点(DataNode),并将文件的元数据保存在名称节点(NameNode)上。名称节点的内存大小,决定了HDFS文件系统可保存的文件数量,虽然现在的系统内存都比较大,但大量的小文件还是会影响名称节点的性能;
*
多用户写入文件、修改文件。HDFS中的文件只能有一个写入者,而且写操作总是在文件末尾。它不支持多个写入者,也不支持在数据写入后,在文件的任意位置进行修改。
总之,HDFS是为以流式数据访问模式存储超大文件而设计的文件系统,并在普通商用硬件集群上运行。
B**、HDFS的体系结构**
为了支持流式数据访问和存储超大文件,HDFS引入了一些比较特殊的设计,在一个全分布模式环境的集群上,“运行HDFS”意味着在网络分布的不同服务器上运行一些守护进程(daemon),这些进程有各自的特殊角色,并相互配合,一起形成一个分布式文件系统。
HDFS采用了主从(Master/Slave)式体系结构,名称节点NameNode、数据节点DataNode和客户端Client是HDFS中3个重要的角色。
在一个HDFS中,有一个名称节点和一个第二名称节点,典型的集群有几十到几百个数据节点,规模大的系统可达上千、甚至几千个数据节点;而客户端,一般情况下,比数据节点的个数还多。名称节点和第二名称节点、数据节点和客户端的关系如下图所示:
名称节点可以看作是分布式文件系统中的管理者,它负责管理文件系统命名空间、集群配置和数据块复制等。
数据节点是文件存储的基本单元,它以数据块的形式保存了HDFS中文件的内容和数据块的数据校验信息。
客户端和名称节点、数据节点通信,访问HDFS文件系统,操作文件。
2**、数据块**
在介绍上述各实体之前,首先了解一下HDFS中的重要概念:数据块(Block)。
在讨论文件系统的时候,特别是在分析文件系统的实现时,我们知道,为了便于管理,设备往往将存储空间组织成为具有一定结构的存储单位。如磁盘,文件是以块的形式存储在磁盘中,块的大小代表系统读/写操作的最小单位;在Linux的Ext3文件系统中,块大小默认为4096字节。文件系统通过一个块大小的整数倍的数据块,来使用磁盘。磁盘上的数据块管理属于文件系统实现的内部细节,对于通过系统调用读写文件的用户来说,是透明的。
HDFS也有块的概念,不过是更大的单元,默认HDFS数据块大小是64MB(1.x版本)/128MB(2.x版本)。和普通文件系统类似,HDFS上的文件也进行分块,块作为单独的存储单元,以Linux上普通文件的形式保存在数据节点的文件系统中。数据块是HDFS的文件存储处理的单元。
HDFS是针对大文件设计的分布式系统,使用数据块带来了很多的好处,具体如下:
HDFS可以保存比存储节点单一磁盘大的文件。
文件块可以保存在不同的磁盘上。其实,在HDFS中,文件数据可以存放在集群上的任何一个磁盘上,不需要保存在同一个磁盘上,或同一个机器的不同磁盘上。
简化了存储子系统。
简单化是所有系统的追求,特别是在故障种类繁多的分布式系统中,将管理“块”和管理“文件”的功能区分开,简化了存储管理,也消除了分布式管理文件元数据的复杂性。
方便容错,有利于数据复制。
在HDFS中,为了应对损坏的块以及磁盘、机器故障,数据块会在不同的机器上进行复制(一般副本数为3,即一份数据保存在3个不同的地方),如果一个数据块副本丢失或者损坏了,系统会在其他地方读取副本,这个过程对用户来说是透明的,它实现了分布式系统中的位置透明性和故障透明性。同时,一个因损坏或机器故障而丢失的块会从其他地方复制到某一个正常运行的机器,以保证副本数目恢复到正常水平。该正常水平的副本数,也称副本系数。
HDFS数据块比前面讨论过的磁盘块大得多,一个典型的HDFS系统中,磁盘块的大小为64MB,也有使用128MB和256MB数据块大小的集群。为什么在HDFS中要使用这么大的数据块呢?原因和在磁盘上使用大磁盘块的原理是一样的。在普通文件系统中使用较大的磁盘块,可以减少管理数据块需要的开销,如在Linux中可以减少保存在i-node中磁盘地址表中的信息链的长度;同时,在对文件进行读写时,可以减少寻址开销,即磁盘定位数据块的次数。HDFS中使用大数据块,可以减少名称节点上管理文件和数据块关系的开销,同时,对数据块进行读写时,可以有效地减少建立网络连接需要的成本。
3**、名称节点和第二名称节点**
名称节点(NameNode)是HDFS主从结构中主节点上运行的主要进程,它指导主从结构中的从节点——数据节点(DataNode)执行底层的I/O任务。
名称节点是HDFS的书记员,维护着整个文件系统的文件目录树,文件/目录的元信息和文件的数据块索引,即每个文件对应的数据块列表。这些信息以两种形式存储在本地文件系统中:一种是命名空间镜像(FileSystem Image,FSImage,也称文件系统镜像),另一种是命名空间镜像的编辑日志(EditLog)(体现了HDFS最新的状态)。
命名空间镜像保存着某一特定时刻HDFS的目录树、元信息和数据块索引等信息,后续对这些信息的改动,则保存在编辑日志中,它们一起提供了一个完整的名称节点第一关系。
同时,通过名称节点,客户端还可以了解到数据块所在的数据节点信息。需要注意的是,名称节点中与数据节点相关的信息不保留在名称节点的本地文件系统中,也就是上面提到的命名空间镜像和编辑日志中,名称节点每次启动时,都会动态地重建这些信息,这些信息构成了名称节点第二关系。运行时,客户端通过名称节点获取上述信息,然后和数据节点进行交互,读写文件数据。
另外,名称节点还能获取HDFS整体运行状态的一些信息,如系统的可用空间、已经使用的空间、各数据节点的当前状态等。
第二名称节点(Secondary NameNode,SNN)是用于定期合并命名空间镜像和镜像编辑日志的辅助守护进程。和名称节点一样,每个集群都有一个第二名称节点,在大规模部署的条件下,一般第二名称节点也独自占用一台服务器。
第二名称节点和名称节点的区别在于它不接收或记录HDFS的任何实时变化,而只是根据集群配置的时间间隔,不停地获取HDFS某一个时间点的命名空间镜像和镜像的编辑日志,合并得到一个新的命名空间镜像。该镜像会上传到名称节点,替换原有的命名空间镜像,并清空上述日志。应该说,第二名称节点配合名称节点,为名称节点上的名称节点第一关系提供了一个简单的检查点(Checkpoint)机制,并避免出现编辑日志过大,导致名称节点启动时间过长的问题(注意:合并发生的时机为HDFS发出检查点的时候,有两种情形:1.默认每隔60分钟触发一次检查点,可通过fs.check.period参数修改该时间;2.默认当edits文件达到64MB的大小时,也会触发一次检查点,可通过fs.check.size参数修改该大小)。
如前面所述,名称节点是HDFS集群中的单一故障点,通过第二名称节点的检查点,可以减少停机的时间并降低名称节点元数据丢失的风险。但是,第二名称节点不支持名称节点的故障自动恢复,名称节点失效处理需要人工干预。
实践
HDFS最新的操作日志都保存在文件edits_inprogress文件中,并且这些edits文件都是二进制形式,HDFS提供了一个命令工具——edits viewer日志查看器,专门用于查看edits文件的内容。使用如下命令在HDFS文件系统中创建一个示例目录:
hdfs dfs -mkdir /myexample
运行命令find . -name edits/*,找到保存edits文件的目录,并进入到该目录下。运行如下命令将最新操作日志文件,导出到xml文件中,以查看其内容:
hdfs oev -i edits_inprogress_0000000000000000110 -o ~/example1.xml
cat ~/example1.xml
结果如下图所示:
HDFS中数据文件的元信息(数据块的存储位置及冗余信息等)都保存在fsimage文件中,它们同样也是二进制形式,并且跟edits文件保存在同一个目录下。HDFS也提供了一个命令工具——image viewer专门用于查看fsimage文件的内容,需要注意的是,在使用该工具时,要使用-p参数指定输出文件的类型,如下:
hdfs oiv -i fsimage_0000000000000000002 -o ~/example2.xml -p XML
cat ~/example2.xml
输出结果部分内容截图如下所示:
4**、数据节点**
HDFS集群上的从节点都会驻留一个数据节点的守护进程,来执行分布式文件系统中最忙碌的部分:将HDFS数据块写到Linux本地文件系统的实际文件中,或者从这些实际文件中读取数据块。
虽然HDFS是为大文件设计,但放在HDFS上的文件和传统文件系统类似,也是将文件分块,然后进行存储。但和传统文件系统不同,在数据节点上,HDFS文件块(也就是数据块)以Linux文件系统上的普通文件进行保存。客户端进行文件内容操作时,先由名称节点告知客户端每个数据块驻留在哪个数据节点,然后客户端直接与数据节点守护进程进行通信,处理与数据块对应的本地文件。同时,数据节点会和其他数据节点进行通信,复制数据块,保证数据的冗余性(注意:数据块的冗余度一般跟数据节点的个数保持一致,最大不要超过3个,在生产环境中,至少保持两个数据节点)。
数据节点作为从节点,会不断地向名称节点报告。初始化时,每个数据节点将当前存储的数据块告知名称节点。后续数据节点工作过程中,数据节点仍会不断地更新名称节点,为之提供本地修改的相关信息,并接受来自名称节点的指令,创建、移动或者删除本地磁盘上的数据块。下图说明了名称节点和数据节点的角色:
上图中显示两个数据文件,它们都位于”/data”目录下。其中,“data1.txt”文件有3个数据块,表示为b1、b2和b3,“data2.txt”文件由数据块b4和b5组成。这两个文件的内容分散在几个数据节点上。示例中,每个数据块都有3个副本。如数据块1(属于”data1.txt”文件)的3个副本分布于DataNode1、DataNode2和DataNode3上,当这些数据节点中任意一个崩溃或者无法通过网络访问时,可以通过其他节点访问“data1.txt”文件。
实践
上传一个大小超过128MB的文件到HDFS中,查看数据块文件的情况。运行如下命令上传hadoop-2.7.3.tar.gz文件到HDFS的/tmp目录下:
hdfs dfs -put /root/tools/hadoop-2.7.3.tar.gz /tmp
运行命令find . -name blk/*,找到保存数据块文件的目录,并进入该目录,运行命令:ls –l,结果如下图所示:
从图中可以看到,blk_1073741838文件和blk_1073741839文件便是hadoop-2.7.3.tar.gz对应的数据块文件,这两个文件的大小之和便是hadoop-2.7.3.tar.gz文件的实际大小。
5**、客户端**
客户端是用户和HDFS进行交互的手段,HDFS提供了各种各样的客户端,包括命令行接口、Java API、Thrift接口、C语言库、用户空间文件系统(Filesystem in Userspace,FUSE)等。
虽然Hadoop不是一个POSIX文件系统,不支持“ls”和“cp”这样的命令,但Hadoop提供了一套和Linux文件命令类似的命令行工具,使熟悉Linux文件系统的用户,可以很快使用该工具,对HDFS进行操作。例如,下面的命令可以在HDFS文件系统上创建一个目录:
hadoop fs –mkdir testDIR
通过命令行工具,可以进行一些典型的文件操作,如读文件、创建文件路径、移动文件(包括文件改名)、删除文件、列出文件列表等,同时,命令行工具也提供了本地文件和HDFS交互的能力,可以通过下面的命令,将本地文件上传到HDFS:
hdfs dfs –copyFromLocal testInput/hello.txt/user/alick/in/hello.txt
命令行工具提供了访问HDFS的基本能力,HDFS的Java API提供了更进一步的功能,目前,所有访问HDFS的接口都是基于Java API,包括上面介绍的命令行工具。HDFS的Java API实现了上一章介绍的Hadoop抽象文件系统,包括Distributed File System和对应的输入/输出流。
下面是一个非常简单的HDFS JavaAPI的例子
Configurationconf=newConfiguration();
conf.set("fs.defaultFS","hdfs://192.168.12.111:9000");
FileSystemclient= FileSystem.get(conf);
OutputStreamos = client.create(new Path("/tools/a.zip"));
FileStatus[]status=client.listStatus(newPath("/tools"));
for(FileStatuss:status) {
System.out.println(s.isDirectory()?"目录":"文件");
}
client.delete(newPath("/tools/b.txt"));
client.close();
在上面的代码中,通过FileSystem.get()获取的文件系统是DistributedFileSystem实例,而通过FileSystem.create()创建的输出流,则是一个FSDataOutputStream实例。方法listStatus()和delete()用于获得HDFS上文件状态信息和删除文件,它们都是标准的Hadoop文件系统方法。
HDFS正是通过Java的客户端,屏蔽了访问HDFS的各种各样细节,用户通过标准的Hadoop文件接口,就可以访问复杂的HDFS,而不需要考虑与名称节点、数据节点等的交互细节,降低了Hadoop应用开发的难度,也证明了Hadoop抽象文件系统的适用性。
此外,HDFS的Thrift接口、C语言库和FUSE等模块,它们和HDFS的命令行一样,都是在JavaAPI的基础上开发的,用于访问HDFS的接口,这里不再详细介绍。
6**、HDFS主要操作流程**
下面介绍5个典型的HDFS操作流程:客户端到名称节点的元数据操作、客户端读文件、客户端写文件、数据节点到名称节点的注册和心跳,以及第二名称节点合并元数据。这些操作流程充分体现了HDFS实体间IPC接口和流式接口的配合。
A**、客户端到名称节点的文件与目录操作**
客户端有到名称节点的大量元数据操作,如更改文件名(rename)、在给定目录下创建一个子目录(mkdir)等,这些操作一般只涉及客户端和名称节点的交互,通过远程接口ClientProtocol进行。
以创建子目录为例,如下图所示:
当客户端调用HDFS的FileSystem实例,也就是DistributedFileSystem的mkdir()方法时(如图中步骤1),DistributedFileSystem对象通过IPC调用名称节点上的远程方法mkdir(),让名称节点执行具体的创建子目录操作:在目录树数据结构上的对应位置创建新的目录节点,同时记录这个操作并持久化到日志中,方法执行成功后,mkdir()返回true,结束创建过程。期间,客户端和名称节点都不需要和数据节点交互。
一些更为复杂的操作,如使用DistributedFileSystem.setReplication()增加文件的副本数,再如通过DistributedFileSystem.delete()删除HDFS上的文件,都需要数据节点配合执行一些动作。
以客户端删除HDFS文件为例(如下图所示),操作在名称节点上执行完毕后,数据节点上存放文件内容的数据块也必须删除。但是,名称节点执行delete()方法时,它只标记操作涉及的需要被删除的数据块(当然,也会记录delete操作并持久化到日志),而不会主动联系保存这些数据块的数据节点,立即删除数据。当保存着这些数据块的数据节点向名称节点发送心跳时(如图中的步骤3),在心跳的应答里,名称节点会通过DatanodeCommand命令数据节点删除数据。在这个过程中,需要注意两个要点:被删除文件的数据,也就是该文件对应的数据块,在删除操作完成后的一段时间以后,才会被真正删除;名称节点和数据节点间永远维持着简单的主从关系,名称节点不会向数据节点发起任何IPC调用,数据节点需要配合名称节点执行的操作,都是通过数据节点心跳应答中携带的DatanodeCommand返回。
B**、客户端读文件**
下图显示了在读取HDFS上的文件时,客户端、名称节点和数据节点间发生的一些事件以及事件的顺序。
客户端通过FileSystem.open()打开文件,对应的HDFS具体文件系统DistributedFileSystem创建输入流FSDataInputStream,返回给客户端,客户端使用这个输入流读取数据。FSDataInputStream需要和具体的输入流结合,一起形成过滤器流(filtered stream)向外提供服务。
对HDFS来说,具体的输入流是DFSInputStream。在DFSInputStream的构造函数中,输入流实例通过ClientProtocal.getBlockLocation()远程接口调用名称节点,以确定文件开始部分数据块的保存位置,即图中的步骤2。对于文件中的每个块,名称节点返回保存着该块副本的数据节点地址。注意,这些数据节点根据它们与客户端的距离(利用了网络的拓扑信息),进行了简单的排序。
客户端调用FSDataInputStream.read()方法读取文件数据时,DFSInputStream对象会通过和数据节点间的“读数据”流接口,和最近的数据节点建立联系。客户端反复调用read()方法,数据会通过数据节点和客户端连接上的数据包返回客户端。当到达块的末端时,DFSInputStream会关闭和数据节点间的连接,并通过getBlockLocation()远程方法获得保存着下一个数据块的数据节点信息(严格说,在对象没有缓存该数据块的位置时,才会使用这个远程方法),即图中的步骤5,然后继续寻找最佳数据节点,再次通过数据节点的读数据接口,获得数据(图中的步骤6).
另外,由于ClientProtocol.getBlockLocations()不会一次返回文件的所有数据块信息,DFSInputStream可能需要多次使用该远程方法,检索下一组数据块的位置信息。对于客户端来说,它读取的是一个连续的数据流,上面分析的联系不同数据节点、定位一组数据块位置的过程,对它来说都是透明的。当客户端完成数据读取任务后,通过FSDataInputStream.close()关闭输入流(图中的步骤7).
在客户端读取文件时,如果数据节点发生了错误,如节点停机或者网络出现故障,那么客户端会尝试下一个数据块位置。同时,它也会记住出现故障的那个数据节点,不会再进行徒劳无益的尝试。读数据的应答包中,不但包含了数据,还包含了数据的校验和,客户端会检查数据的一致性,如果发现有校验错误,也就是说数据块已经损坏,它会将这个信息报告给名称节点,同时,尝试从别的数据节点中读取另外一个副本的文件内容。由客户端在读数据时进行数据完整性检查,可以降低数据节点的负载,均衡各节点的计算能力。
由客户端直接联系名称节点,检索数据存放位置,并由名称节点安排数据节点读取顺序,这样的设计还有一个好处是,能够将读取文件引起的数据传输,分散到集群的各个数据节点,HDFS可以支持大量的并发客户端。同时,名称节点只处理数据块定位请求,不提供数据,否则,随着客户端数量的增长,名称节点会迅速成为系统的“瓶颈”。
C**、客户端写文件**
即使不考虑数据节点出错后的故障处理,文件写入也是HDFS中最复杂的流程。以创建一个新文件并向文件中写入数据,然后关闭文件为例,分析客户端写文件时系统各节点的配合,如下图所示:
客户端调用DistributedFileSystem的create()方法创建文件(如图中的步骤1),这时,DistributedFileSystem创建DFSOutputStream,并由远程过程调用,让名称节点执行同名方法,在文件系统的命名空间中创建一个新文件。名称节点创建新文件时,需要执行各种各样的检查,如名称节点处于正常工作状态,被创建的文件不存在,客户端有在父目录中创建文件的权限等。这些检查都通过以后,名称节点会构造一个新文件,并记录创建操作到编辑日志edits中。远程方法调用结束后,DistributedFileSystem将该DFSOutputStream对象包裹在FSDataOutputStream实例中,返回给客户端。
在上图中的步骤3客户端写入数据时,由于create()调用创建了一个空文件,所以,DFSOutputStream实例首先需要向名称节点申请数据块,addBlock()方法成功执行后,返回一个LocatedBlock对象。该对象包含了新数据块标识和版本号,同时,它的成员变量LocatedBlock.locs提供了数据流管道的信息,通过上述信息,DFSOutputStream就可以和数据节点联系,通过写数据接口建立数据流管道。客户端写入FSDataOutputStream流中的数据,被分成一个一个的文件包,放入DFSOutputStream对象的内部队列。该队列中的文件包最后打包成数据包,发往数据流管道,流经管道上的各个数据节点,并持久化。确认包(如图中的步骤6)逆流而上,从数据流管道一次发往客户端,当客户端收到应答时,它将对应的包从内部队列移除。
DFSOutputStream在写完一个数据块后,数据流管道上的节点,会通过和名称节点的DatanodeProtocol远程接口的blockReceived()方法,向名称节点提交数据块。如果数据队列中还有等待输出的数据,DFSOutputStream对象需要再次调用addBlock()方法,为文件添加新的数据块。
客户端完成数据的写入后,调用close()方法关闭流,如图中的步骤8。关闭意味着客户端不会再往流中写入数据,所以,当DFSOutputStream数据队列中的文件包都收到应答后,就可以使用ClientProtocol.complete()方法通知名称节点关闭文件,完成一次正常的写文件流程。
如果在文件数据写入期间,数据节点发生故障,则会执行下面的操作(注意,这些操作对于写入数据的客户端而言是透明的):首先,数据流管道会被关闭,已经发送到管道但还没有收到确认的文件包,会重新添加到DFSOutputStream的输出队列,这样保证了无论数据流管道中哪个数据节点故障,都不会丢失数据。当前正常工作的数据节点上的数据块会被赋予一个新的版本号,并通知名称节点,这样,失败的数据节点从故障恢复过来以后,上面只有部分数据的数据块会因为数据块版本号和名称节点保存的版本号不匹配而删除。然后,在数据流管道中删除错误数据节点并重新建立管道,并继续写数据到正常工作的数据节点。文件关闭后,名称节点会发现该数据块的副本数没有得到要求,会选择一个新的数据节点并复制数据块,创建新的副本。数据节点故障只会影响一个数据块的写操作,后续数据块写入不会受到影响。
在数据块写入过程中,可能出现多于一个的数据节点出现故障的情况,这时,只要数据流管道中的数据节点满足配置项${dfs.replication.min}的值(默认值是1),就认为写操作是成功的。后续这个数据块会被复制,直到满足文件的副本系数的要求。
D**、数据节点的启动和心跳**
这里讨论数据节点和名称节点间的交互,如下图所示。其中包括数据节点从启动到进入正常工作状态的注册,数据块上报,以及正常工作过程中的心跳等与名称节点相关的远程调用。这部分虽然只涉及DatanodeProtocol接口,但有助于读者更进一步理解数据节点和名称节点的关系。
正常启动数据节点或者为升级而启动数据节点,都会向名称节点发送远程调用versionRequest(),进行必要的版本检查。这里的版本检查,只涉及构建版本号,保证它们间的HDFS版本是一致的。正常启动的数据节点,在版本检查结束后,在图中的步骤2会接着发送远程调用register(),向名称节点注册。DatanodeProtocol.register()的主要工作也是检查,通过检查确认该数据节点是名称节点管理集群的成员。也就是说,用户不能将某一个集群的数据节点,直接注册到另一个集群的名称节点,这保证了整个系统的数据一致性。注册成功后,数据节点会将它管理的所有数据块信息,通过blockReport()方法上报到名称节点,帮助名称节点建立HDFS文件数据块到数据节点的映射关系。这一步操作完成后,数据节点才正式提供服务。(数据节点和名称节点间的交互)
由于名称节点和数据节点间存在着主从关系,数据节点需要每隔一段时间发送心跳到名称节点(如图中的步骤4、步骤5),如果名称节点长时间接收不到数据节点的心跳,它会认为该数据节点已经失效。名称节点如果有一些需要数据节点配合的动作,则会通过方法sendHeartBeat()返回。该返回值是一个DatanodeCommand数组,它带回来一系列名称节点指令。继续上面提到的客户端删除HDFS文件例子,操作在名称节点上执行完毕后,被删除文件的数据块会被标记,如果保存这些数据块的数据节点向名称节点发送心跳,则在返回的DatanodeCommand数组里,有对应的命令编号为DNA_INVALIDATE的名称节点指令。数据节点执行指令,删除数据块,释放存储空间。注意,数据节点删除数据块时,并不知道、也不需要知道该数据块是因为文件被删除,还是均衡器移动数据块后导致副本数升高,或者是用户降低文件的副本系数触发的数据块删除,它只需要简单地执行名称节点指令即可。
应该说,数据节点和名称节点间的交互非常简单,大部分都是数据节点到名称节点的心跳,但考虑到在一个大规模的HDFS集群中,一个名称节点会管理上千个数据节点,这样的设计也就非常自然。
E**、第二名称节点合并元数据**
客户端对HDFS的文件系统目录树进行修改时,名称节点都会在编辑日志里写下记录,以保证系统出现故障后,能够根据这些日志进行恢复。日志会随时间不断地增长,意味着如果系统重启后,需要进行日志恢复的时间会很长。为了避免这种情况发生,HDFS引入了检查点机制,命名空间镜像(fsimage)文件就是文件系统元数据的持久性检查点,和编辑日志不同,它不能在每次对文件系统元数据进行修改后都进行更新。在一个比较大的运营集群中,fsimage文件可以有GB的大小,所以命名空间镜像加上编辑日志,为名称节点的元数据提供了安全保障。如果名称节点重新启动,元数据可以通过从磁盘中读入命名空间镜像,恢复到某一个检查点,然后再执行检查点后记录的编辑日志,进行重建。一般来说,fsimage中包括了序列化后的文件系统目录和文件的i-node。和Linux的i-node相似,HDFS索引节点表征一个文件或者目录的元数据信息,以及文件的副本数、修改和访问时间等信息。
上述机制有一个问题,Hadoop文件系统的编辑日志会随时间不断增长,如果HDFS重新启动,名称节点将花很长时间执行编辑日志中的每一个操作,在这个时间内,文件系统是不可用的,减少恢复时间,有利于提供系统的可用性。为了解决这个问题,Hadoop引入了第二名称节点,它的唯一工作就是辅助名称节点,合并fsimage和编辑日志。第二名称节点合并元数据的具体流程如下图所示。过程涉及远程接口NamenodeProtocol和名称节点、第二名称节点上的非IPC接口。
该过程由第二名称节点发起,首先通过远程方法NamenodeProtocol.getEditLogSize()获得名称节点上编辑日志的大小。如果日志很小,第二名称节点就不需要合并元数据镜像和编辑日志,否则,继续通过该远程接口上的rollEditLog(),启动一次检查点过程。这时,名称节点需要创建一个新的编辑日志edit.inprogress,后续对文件系统元数据的改动,都会记录到这个新的日志里。而原有的命名空间镜像和编辑日志,则由第二名称节点,通过HTTP接口(如图中的步骤3和步骤4),读取到本地,并在内存中进行合并。合并的结果输出为fsimage.check,然后,第二名称节点再次发起HTTP请求,通知名称节点数据已经准备好。名称节点通过图中步骤6的HTTP GET请求,下载fsimage.check。名称节点下载新命名空间镜像结束后,第二名称节点通过NamenodeProtocol.rollFsImage(),完成这次检查点。名称节点在处理该远程方法时,会用fsimage.check覆盖原来的fsimage,形成新的命名空间镜像,同时将新的编辑日志edit.inprogress改名为edit,注意,该编辑日志中的内容包含了在新元数据镜像的基础上,对文件系统的修改。
7**、小结**
作为支撑海量数据处理的文件系统,HDFS需要提供与普通文件系统不一样的特性,这是支持超大文件、检测和快速恢复硬件故障、流式数据访问和简化的一致性模型这些设计目标,决定了HDFS采用了主从式体系结构,系统的功能分布在名称节点和第二名称节点、数据节点和客户端等实体中,它们相互配合,为海量数据处理提供底层支持。
接口是观察复杂系统工作的一个很好的出发点,HDFS是采用多项分布式技术实现的文件系统,HDFS各个实体间存在着多种信息交互的过程,这些交互过程,有些使用Hadoop远程过程调用实现,充分利用了IPC机制特有的,和本地过程调用相同的语法外观,在需要交换大量数据的场景中,则使用基于TCP或者基于HTTP的流式接口。从另一个角度看,这些接口有些和客户端相关,有些是HDFS各个服务器间,也就是HDFS内部实体间的交互。HDFS各个实体间的接口如下图所示:
基于IPC的,和客户端相关的接口包括ClientProtocol、ClientDatanodeProtocol,分别用于客户端和名称节点、客户端和数据节点的通信。HDFS内部实体使用Hadoop远程过程调用实现的信息交换包括数据节点和名称节点、数据节点和数据节点、第二名称节点和名称节点间的交互。流式接口主要用于大数据传输,包括了客户端和数据节点、数据节点和数据节点间的基于TCP的,与数据块操作相关的接口,以及用于第二名称节点合并命名空间镜像和镜像编辑日志,基于HTTP协议的接口。
参考文献:
——《潭州大数据课程课件》
——《Hadoop技术内幕 深入解析HADOOP COMMON和HDFS架构设计与实现原理》
Process finished with exit code 0
转自https://mp.weixin.qq.com/s/0SHvWRiW4glVCzRa1Vo_oQ
内容来源于网络,如有侵权,请联系作者删除!