- 大数据:从基础理论到最佳实践
- 祁伟
- 7817字
- 2021-01-07 18:47:57
2.2 HDFS的运行机制
本节将详细介绍HDFS的结构与运行原理。
2.2.1 HDFS的结构与组成
HDFS采用主/从(Master/Slave)结构,整个集群由一个名字节点和多个数据节点组成。
NameNode主要负责管理文件命名空间和客户端访问的主服务器,而DataNode则负责对存储进行管理。
HDFS的体系结构如图2-4所示。
图2-4 HDFS的体系结构
由图2-4可知,名字节点NameNode上保存着控制数据节点DataNode信息的元数据(Metadata)。客户端Client可以通过NameNode对元数据进行操作,也可以直接对DataNode进行读和写操作。
1. NameNode的主要功能
(1)管理元数据信息。元数据信息包括名字空间、文件到文件块的映射、文件块到数据节点的映射三部分。管理文件块包括创建新文件块、文件复制、移除无效文件块以及回收孤立文件块等内容。
(2)管理文件系统的命名空间。任何对文件系统元数据产生修改的操作,NameNode都会使用事务日志记录(下称EditLog)来表示;同样地,修改文件的副本系数也将往EditLog中插入一条记录,NameNode将EditLog存储在本地操作系统的文件系统中。同时,文件系统的命名空间被存储在一个称为映像文件(FsImage)的文件中,包括文件的属性、文件块到文件的映射以及文件块到数据节点的映射等内容,FsImage文件也是存放在NameNode所在的本地文件系统中。
(3)监听请求。指监听客户端事件和DataNode事件。客户端事件包含名字空间的创建和删除,文件的创建、读写、重命名和删除,文件列表信息获取等信息。DataNode事件主要包括文件块信息、心跳响应、出错信息等。处理请求指处理上面的监听请求事件并返回结果。
(4)心跳检测。DataNode会定期将自己的负载情况通过心跳信息向NameNode汇报。NameNode全权管理数据块的复制,它周期性地从集群中的每个DataNode接收心跳信号和块状态报告(Block Report)。接收到心跳信号意味着该DataNode节点工作正常。块状态报告包含了一个该DataNode上所有数据块的列表。
NameNode决定是否将文件映射到DataNode的复制块上。
对于最常见的三个复制块,第一个复制块存储在同一机架的不同节点上,最后一个复制块存储在不同机架的某个节点上。
实际的I/O事务并没有经过NameNode,只有表示DataNode和块的文件映射的元数据经过NameNode。当外部客户机发送请求,要求创建文件时,NameNode会以块标识和该块的第一个副本的DataNode IP地址作为响应。这个NameNode还会通知其他将要接收该块的副本的DataNode。
NameNode在FsImage文件中存储所有关于文件系统名称空间的信息,包含所有事务的记录文件EditLog存储在NameNode的本地文件系统上。FsImage和EditLog文件也需要复制副本,以防文件损坏或NameNode系统丢失。
2. DataNode的主要功能
(1)数据块的读写。一般是文件系统客户端需要请求对指定的DataNode进行读写操作,DataNode通过DataNode的服务进程与文件系统客户端打交道。同时,DataNode进程与NameNode统一结合,对是否需要对文件块的创建、删除、复制等操作进行指挥与调度,当与NameNode交互过程中收到了可以执行文件块的创建、删除或复制操作的命令后,才开始让文件系统客户端执行指定的操作。具体文件的操作并不是DataNode来实际完成的,而是经过DataNode许可后,由文件系统客户端进程来执行实际操作。
(2)向NameNode报告状态。每个DataNode节点会周期性地向NameNode发送心跳信号和文件块状态报告,以便NameNode获取到工作集群中DataNode节点状态的全局视图,从而掌握它们的状态。如果存在DataNode节点失效的情况,NameNode会调度其他DataNode执行失效节点上文件块的复制处理,保证文件块的副本数达到规定数量。
(3)执行数据的流水线复制。当文件系统客户端从NameNode服务器进程中获取到要进行复制的数据块列表(列表中包含指定副本的存放位置,亦即某个DataNode节点)后,会首先将客户端缓存的文件块复制到第一个DataNode节点上,此时,并非整个块都复制到第一个DataNode完成以后才复制到第二个DataNode节点上,而是由第一个DataNode向第二个DataNode节点复制,如此反复进行下去,直到完成文件块及其块副本的流水线复制。
2.2.2 HDFS的数据操作
HDFS被设计成在一个大集群中可以跨机器地可靠地存储海量的文件。它将每个文件存储成block(即数据块)序列,除了最后一个block,其他所有的block都是同样的大小。
1.数据写入
在HDFS文件系统上创建并写一个文件的流程如图2-5所示。
图2-5 HDFS写入流程
具体流程描述如下。
(1)Client调用DistributedFileSystem对象的create方法,创建一个文件输出流(FSDataOutputStream)对象。
(2)通过DistributedFileSystem对象与Hadoop集群的NameNode进行一次远程调用(RPC),在HDFS的Namespace中创建一个文件条目(Entry),该条目没有任何的数据块。
(3)通过FSDataOutputStream对象,向DataNode写入数据,数据首先被写入FSDataOutputStream对象内部的Buffer中,然后数据被分割成一个个Packet数据包。
(4)以Packet为最小单位,基于Socket连接发送到按特定算法选择的HDFS集群中的一组DataNode(正常是3个,可能大于等于1)中的一个节点上,在这组DataNode组成的Pipeline上依次传输Packet。
(5)这组DataNode组成的Pipeline反方向上发送ack确认,最终由Pipeline中第一个DataNode节点将Pipeline ack发送给Client。
(6)完成向文件写入数据,Client在文件输出流(FSDataOutputStream)对象上调用close方法,关闭流。
(7)调用DistributedFileSystem对象的complete方法,通知NameNode文件写入成功。
小提示
写文件过程中,Client/DataNode与NameNode进行的RPC调用。
①写文件开始时创建文件:Client调用create,在NameNode节点的命名空间中创建一个标识该文件的条目。
②在Client连接Pipeline中第一个DataNode节点之前,Client调用addBlock分配一个数据块。
③如果与Pipeline中第一个DataNode节点连接失败,Client调用abandonBlock放弃一个已经分配的数据块。
④一个Block已经写入到DataNode节点磁盘,Client调用fsync让NameNode持久化数据块的位置信息数据。
⑤文件写完以后,Client调用complete方法通知NameNode写入文件成功。
⑥ DataNode节点接收到并成功持久化一个数据块的数据后,调用blockReceived方法通知NameNode已经接收到数据块。
2.数据读取
相比于写入流程,HDFS文件的读取过程比较简单,如图2-6所示。
图2-6 HDFS读取数据
文件的读取操作流程如下。
(1)客户端调用FileSystem的open()函数打开文件,DistributedFileSystem用RPC调用元数据节点,得到文件的数据块信息。
(2)对于每一个数据块,元数据节点返回保存数据块的数据节点的地址。DistributedFileSystem返回FSDataInputStream给客户端,用来读取数据。
(3)客户端调用stream的read()函数开始读取数据。
(4)DFSInputStream连接保存此文件第一个数据块的最近的数据节点。
(5)Data从数据节点读到客户端,当此数据块读取完毕时,DFSInputStream关闭与此数据节点的连接,然后连接此文件下一个数据块的最近的数据节点。
(6)当客户端读取数据完毕的时候,调用FSDataInputStream的close函数。
在读取数据的过程中,如果客户端在与数据节点通信时出现错误,则尝试连接包含此数据块的下一个数据节点。失败的数据节点将被记录下来,以后不再连接。
2.2.3 访问权限
HDFS实现了一个与POSIX类似的文件和目录的权限模型。有三类权限模式:只读权限(r)、写入权限(w)和可执行权限(x)。每个文件和目录都有所属用户(owner)、所属组(group)和模式(mode)。文件或目录对其所有者、同组的其他用户以及所有其他用户分别有着不同的权限。对文件而言,当读取这个文件时,需要有r权限,当写入或者追加到文件时,需要有w权限。对目录而言,当列出目录内容时,需要具有r权限,当新建或删除子文件或子目录时,需要有w权限,当访问目录的子节点时,需要有x权限。不同于POSIX模型,HDFS权限模型中的文件没有sticky、setuid或setgid位,因为这里没有可执行文件的概念。
每个访问HDFS的用户进程的标识分为两个部分,分别是用户名和组名列表。每次用户进程访问一个文件或home目录,HDFS都要对其进行权限检查:
●如果用户是home的所有者,则检查所有者的访问权限。
●如果home关联的组在组名列表中出现,则检查组用户的访问权限;否则检查home其他用户的访问权限。
●如果权限检查失败,则客户的操作会失败。
在HDFS中,客户端用户身份是通过宿主操作系统给出的。
对类Unix系统来说:
●用户名等于‘whoami'。
●组列表等于‘bash -c groups'。
每次文件或目录操作都传递完整的路径名给NameNode,每一个操作都会对此路径做权限检查。客户框架会隐式地将用户身份和与NameNode的连接关联起来,从而减少改变现有客户端API的需求。经常会有这种情况:当对一个文件的某一操作成功后,之后同样的操作却会失败,这是因为文件或路径上的某些目录可能已经不复存在了。比如,客户端首先开始读一个文件,它向NameNode发出一个请求以获取文件第一个数据块的位置。但接下去获取其他数据块的第二个请求可能会失败。另一方面,删除一个文件并不会撤销客户端已经获得的对文件数据块的访问权限。而权限管理能使得客户端对一个文件的访问许可在两次请求之间被收回。重复一下,权限的改变并不会撤销当前客户端对文件数据块的访问许可。
如果权限检查失败,所有使用一个路径参数的方法都可能抛出AccessControlException异常。
2.2.4 通信协议簇
HDFS所有的通信协议都是构建在TCP/IP协议上的。客户端通过一个可配置的端口连接到NameNode,通过ClientProtocol与NameNode交互。而DataNode是使用DataNodeProtocol与NameNode交互的。从ClientProtocol和DataNodeProtocol抽象出一个远程调用,在设计上,NameNode不会主动发起RPC,而是响应来自客户端和DataNode的RPC请求。
HDFS中的主要通信协议见表2-1。
表2-1 HDFS的主要通信协议
(1)ClientProtocol。
ClientProtocol协议是用户进程(包括客户端进程与DataNode进程)与NameNode进程之间进行通信所使用的协议。当客户端进程想要与NameNode进程进行通信的时候,需要通过org.apache.hadoop.hdfs.DistributedFileSystem类,基于ClientProtocol协议来实现交互过程。用户代码通过ClientProtocol协议,可以操纵HDFS的目录命名空间、打开与关闭文件流等。
该接口协议中定义的与文件内容相关的操作主要有:①文件管理,文件的增、删、改,权限控制、文件块管理等;②文件系统管理,查看文件系统状态和设置元数据信息,例如容量、块大小、副本因子数等;③持久会话类,如放弃对指定块的操作、客户端同步等。
协议位置如图2-7所示。
图2-7 HDFS协议示意
(2)DataNodeProtocol。
该协议是用于DataNode与NameNode进行通信的协议,例如发送心跳报告和块状态报告。一般来说,NameNode不直接对DataNode进行RPC调用,如果一个NameNode需要与DataNode通信,唯一的方式,就是通过调用该协议接口定义的方法。
(3)ClientDatanodeProtocol。
当客户端进程需要与DataNode进程进行通信的时候,需要基于该协议。该协议接口定义数据块恢复的方法。
(4)NameNodeProtocol。
该协议接口定义了备用NameNode(Secondary NameNode)与NameNode进行通信所需进行的操作。其中,Secondary NameNode是一个用来辅助NameNode的服务器端进程,主要是对映像文件执行特定的操作,另外,还包括获取指定DataNode上块的操作。
(5)DataTransferProtocol。
该协议用于客户端与DataNode之间通信,主要实现文件块的读写及验证等操作。
(6)InterDatanodeProtocol。
该协议是DataNode进程之间进行通信的协议,例如客户端进程启动复制数据块,此时可能需要在DataNode节点之间进行块副本的流水线复制操作。
2.2.5 HDFS的高可用性
在Hadoop 2.0之前的版本中,NameNode在HDFS集群中存在单点故障,每一个集群中存在一个NameNode,如果NameNode所在的机器出现了故障,那么,将导致整个集群无法利用,直到NameNode重启或者在另一台主机上启动NameNode守护线程。在可预知的情况下(比如NameNode所在的机器硬件或者软件需要升级)以及在不可预测的情况下,如果NameNode所在的服务器崩溃了,都将导致整个集群无法使用。
在Hadoop 2.0及以后的版本中,HDFS的高可用性(High Availability)通过在同一个集群中运行两个NameNode实现:活动节点(Active NameNode)和备用节点(Standby NameNode),允许在服务器崩溃或者机器维护期间快速地启用一个新的NameNode来恢复故障。在典型的HA集群中,通常有两台不同的机器充当NameNode。在任何时间都只有一台机器处于活动(Active)状态;另一台处于待命(Standby)状态。Active NameNode负责集群中所有客户端的操作;而Standby NameNode主要用于备用,它维持足够的状态,在必要时提供快速的故障恢复。
图2-8展示了HDFS的高可用性实现原理,其中,NameNode简写为NN, DataNode简写为DN。由图中可以看出,两个NameNode都与一组称为JNs(JournalNodes)的互相独立的守护进程保持通信,实现Standby NN的状态和Active NN的状态同步,使元数据保持一致。当Active NN执行任何有关命名空间的修改时,需要发送到一半以上的JNs上(通过Edits log进行持久化存储)。当Standby NN观察到Edits log的变化时,它会从JNs中读取edits信息,并更新其内部的命名空间。一旦Active NN出现故障,Standby NN首先确保自己在发生故障之前从JNs中读出了全部的修改内容,然后切换到Active状态。
图2-8 HDFS高可用性的实现
为了提供快速的故障恢复,Standby NN也需要保存集群中各个文件块的存储位置。为了达到这一目的,DataNodes上需要同时配置这两个NameNode的地址,同时,与它们都建立心跳连接,并把block位置等信息发送给它们。对于JNs而言,任何时候,只允许一个NameNode作为数据写入者。对于DataNodes,只执行Active NN发送过来的命令。
2.2.6 集中缓存管理
HDFS采用集中式的缓存管理(HDFS centralized cache management)技术。
HDFS集中式缓存管理是一个明确的缓存机制,它允许用户指定缓存的HDFS路径。NameNode会与保存着所需块数据的所有DataNode通信,并指导它们把块数据放在堆外缓存(off-heap)中。HDFS集中式缓存管理的架构如图2-9所示。
图2-9 HDFS集中式缓存的架构
由图2-9可以看到,NameNode负责协调集群中所有DataNode的off-heap缓存。NameNode周期性地接收来自每个DataNode的缓存报告,缓存报告中描述了缓存在给定DataNode中的所有块的信息。
NameNode通过借助DataNode心跳上的缓存和非缓存命令,来管理DataNode缓存。缓存指令存储在fsimage和editlog中,可以通过Java和命令行API添加、移除或修改,NameNode查询自身的缓存指令集,来确定应该缓存哪个路径。NameNode还存储了一组缓存池(缓存池是一个管理实体,用于管理缓存指令组)。
NameNode周期性地重复扫描命名空间和活跃的缓存,以确定需要缓存或不缓存哪个块,并向DataNode分配缓存任务。重复扫描也可以由用户动作来触发,比如添加或删除一条缓存指令,或者删除一个缓存池。
HDFS集中化缓存管理具有许多优势。
(1)用户可以根据自己的逻辑指定一些经常被使用的数据或者高优先级任务对应的数据,让它们常驻内存而不被淘汰到磁盘。当工作集的大小超过了主内存大小(这种情况对于许多HDFS负载都是常见的)时,这一点尤为重要。
(2)由于DataNode缓存是由NameNode管理的,所以在分配任务时,应用程序可以通过查询一组缓存块的位置,把任务和缓存块副本放在同一位置上,提高读操作的性能。
(3)当数据块已经被DataNode缓存时,客户端就可以使用一个新的更高效的零拷贝读操作API。因为缓存数据的校验和只需由DataNode执行一次,所以,使用零拷贝API时,客户端基本上不会有开销。
(4)集中式的缓存可以提高整个集群的内存使用率。当依赖于单独的DataNode上操作系统的内存进行缓存时,重复读取一个块数据会导致该块的一个或多个副本全部被送入内存中缓存。使用集中化缓存管理,用户就能明确地只锁定这N个副本中的M个了,从而节省了(N-M)个内存的使用量。
(5)即使出现缓存数据的DataNode节点宕机、数据块移动或集群重启等问题,缓存都不会受到影响。因为缓存被NameNode统一管理并被持久化到fsimage和editlog中,出现问题后,NameNode会调度其他存储了这个数据副本的DataNode,把它读取到内存。
零拷贝
零拷贝(zero-copy)是实现主机或路由器等设备高速网络接口的主要技术。零拷贝技术通过减少或消除关键通信路径影响速率的操作,降低数据传输的操作系统开销和协议处理开销,从而有效提高通信性能,实现高速数据传输。
零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道、实现高速服务器和路由器的关键技术之一。数据拷贝受制于传统的操作系统或通信协议,限制了通信性能。采用零拷贝技术,通过减少数据拷贝次数,简化协议处理的层次,在应用和网络间提供更快的数据通路,可以有效地降低通信延迟,增加网络的吞吐率。
2.2.7 日志和检查点
Hadoop中有两个非常重要的文件fsimage和edits,前面已经粗略地介绍了一些,这里做一个详细的讲解。它们位于NameNode的$dfs.namenode.name.dir/current/文件夹中。在current目录中,我们可以看到存在大量的以edits开头的文件和少量的以fsimage开头的文件,如图2-10所示。
图2-10 current目录
对edits和fsimage文件的概念说明如下。
(1)edits文件存放的是Hadoop文件系统的所有更新操作的日志,HDFS文件系统客户端执行的所有写操作首先会被记录到edits文件中。
(2)fsimage文件是Hadoop文件系统元数据的一个永久性检查点,其中包含Hadoop文件系统中的所有目录和文件的索引节点序列化信息。对于文件来说,包含的信息有修改时间、访问时间、块大小信息等;对于目录来说,包含的信息主要有修改时间、访问控制权限等信息。fsimage并不包含DataNode的信息,而是包含DataNode上块的映射信息,并存放到内存中,当一个新的DataNode加入到集群中时,DataNode都会向NameNode提供块的信息,而NameNode会定期地索取块的信息,以使得NameNode拥有最新的块映射。
其中,edits负责保存自最新检查点后命名空间的变化,起到日志的作用;而fsimage则保存了最新的元数据检查点信息。
fsimage和edits文件都是经过序列化的。在NameNode启动的时候,会将fsimage文件中的内容加载到内存中,然后再执行edits文件中的各项操作,使得内存中的元数据与实际的同步。存在于内存中的元数据支持客户端的读操作。
NameNode启动后,HDFS中的更新操作会重新写到edits文件中。对于一个文件来说,当所有的写操作完成以后,在向客户端发送成功代码之前,将同步更新edits文件。在NameNode运行期间,由于HDFS的所有更新操作都是直接写到edits中的,时间长了会导致edits文件变得很大。
在Hadoop 1.x中,通过SecondaryName合并fsimage和edits,以此来减小edits文件的大小,从而减少了NameNode重启的时间。在Hadoop 2.x中,已经不用SecondaryName,通过配置HA机制实现,即在standby NameNode节点上运行一个叫作CheckpointerThread的线程,这个线程调用StandbyCheckpointer类的doWork()函数,每隔一定的时间(可配置)做一次合并操作。
edits和fsimage文件中的内容使用普通文本编辑器是无法直接查看的,为此,Hadoop准备了专门的工具,用于查看文件的内容,分别为oev和oiv。
oev是offline edits viewer(离线edits查看器)的缩写,该工具只操作文件,并不需要Hadoop集群处于运行状态。oev提供了几个输出处理器,用于将输入文件转换为相关格式的输出文件,可以使用参数-p指定。目前支持的输出格式有binary(Hadoop使用的二进制格式)、xml(在不使用参数p时的默认输出格式)和stats(输出edits文件的统计信息)。由于没有与stats格式对应的输入文件,所以,一旦输出为stats格式,将不能再转换为原有格式。比如输入格式为binary,输出格式为xml,可以通过将输入文件指定为原来的输出文件,将输出文件指定为原来的输入文件,实现binary和xml的转换,而stats则不可以。
oev的具体语法可以通过在命令行输入“hdfs oev”来查看,如图2-11所示。
图2-11 oev的具体语法
oiv是offline image viewer的缩写,用于将fsimage文件的内容转储到指定文件中,以便于阅读,该工具还提供了只读的WebHDFS API,以允许离线分析和检查Hadoop集群的命名空间。oiv在处理非常大的fsimage文件时是相当快的,如果不能够处理fsimage,它会直接退出。oiv不具备向后兼容性,比如使用Hadoop 2.4版本的oiv不能处理hadoop 2.3版本的fsimage,只能使用Hadoop 2.3版本的oiv。与oev一样,oiv也不需要Hadoop集群处于运行状态。oiv的具体语法可以通过在命令行输入“hdfs oiv”来查看。
如果fsimage丢失或者损坏了,我们将失去文件到块的映射关系,也就无法使用DataNode上的所有数据了。因此,定期及时地备份fsimage和edits文件非常重要。
fsimage和edit log是HDFS的核心数据结构。这些文件的损坏会导致整个集群的失效。因此,NameNode可以配置成支持多个fsimage和edit log的副本。任何fsimage和edit log的更新都会同步到每一份副本中。同步更新多个edit log副本会降低NameNode的命名空间事务处理速率。但是这种降低是可以接受的,因为HDFS程序中大量产生的是数据请求,而不是元数据请求。NameNode重新启动时,会选择最新一致的fsimage和edit log。
2.2.8 HDFS快照
在Hadoop 2.x版本中,HDFS提供了支持元数据快照的解决方案。
快照(Snapshot)支持存储在某个时间的数据复制,当HDFS数据损坏时,可以回滚到过去一个已知正确的时间点。
快照分为两种:一种是建立文件系统的索引,每次更新文件不会真正改变文件,而是新开辟一个空间用来保存更改的文件;一种是复制所有的文件系统。HDFS把元数据和数据分离,元数据被存储在单独的NameNode上,实际的数据被复制并扩散到整个集群。使用单个节点来管理元数据,使我们能够使用一个单一的逻辑时钟,建立元数据快照。
HDFS的快照是在某一时间点对指定文件系统复制,可以是整个文件系统的,也可以是文件系统的一部分。快照采用只读模式,对重要数据进行恢复、防止用户错误性的操作。HDFS的快照有以下特征。
(1)快照的创建是瞬间的,代价为O(1),取决于子节点扫描文件目录的时间。
(2)当且仅当快照的文件目录下有文件更新时,才会占用小部分内存,占用内存的大小为O(M),其中,M为更改文件或者目录的数量。
(3)新建快照时,DataNode中的block不会被复制,快照中只是记录了文件块的列表和大小等信息。
(4)快照不会影响正常文件系统的读写等操作。对做快照之后的数据进行的更改将会按照时间顺序逆序记录下来,用户访问的还是当前最新的数据,快照里的内容为快照创建的时间点时文件的内容减去当前文件的内容。