Hadoop核心-HDFS
现在我们来详细学习一下HDFS和MR的原理
HDFS的设计思想
HDFS的全称是 The Hadoop Distributed File System, 即Hadoop分布式文件系统。那么作为分布式数据管理系统,HDFS最主要要解决这几个问题:
- 如何存储上百GB/TB级别大文件?
- 如何保证文件系统的容错?
- 如何进行大文件的并发读写控制?
首先,它最大的特点就是,可以处理大数据文件。
那么具体是怎么处理的呢?HDFS会将大文件分割成一个一个block,每个block的大小为128MB(或者是64MB),然后将其存放到不同的机器(节点)上面,如下图所示:
其次,我们可以通过分块冗余存储来保证文件系统的容错性。也就是说,将大文件分割成的小块也可以做冗余备份。这些备份会被放在不同的节点上,因此处分存放某一文件块的所有节点都发生故障,否则HDFS在部分节点故障的情况下依然可以访问到该文件块。如下图:绿色块存放在DataNode1、2、5,黑色块存放在DataNode1、3、5
最后,为了让HDFS可以进行大文件的并发读写,我们对文件的读写做了简化——采用一次写入,多次读取的方式,即可避免读写冲突。在HDFS中,只支持顺序写入,不支持随机写入。而且不允许修改上传后的文件,如果需要修改,需要先删除,再上传修改完后的文件。
一个很自然的问题是,HDFS把大文件切成块之后,这些块和操作系统中的块有什么区别?
- 显然,在HDFS将大文件切成128MB的块之后,会让一些机器去存储,对于这些机器来说,128MB的块就相当于一个文件。机器会将块继续拆分成32KB的块存放在磁盘里面。因此我们可以说HDFS切成的块在上层,操作系统分块在底层。
HDFS的架构
HDFS采用master/slave架构。一个HDFS集群是由一个NameNode、一个Secondary NameNode和一定数目的DataNode组成。NameNode是一个中心服务器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问;Sencondary NameNode 所在节点是主节点的备份节点;集群中的DataNode一般是一个节点一个,负责管理它所在节点上的存储。
接下来,我们来详细介绍每个节点的作用。
NameNode内部结构
NameNode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体DataNode节点的映射。
如上图,NameNode在内存中维护了HDFS的树形结构的目录。这和Linux文件系统的目录结构时分类似。但这个目录只指示了文件块存储的位置,而不实际存储文件。HDFS将目录结构存放在磁盘上的FsImage文件中,可以看做是HDFS目录结构的一个本地快照。
此外,Editlog文件主要记录了对目录以及HDFS文件块的创建、删除、重命名等操作。
Sencondary NameNode与NameNode的交互
Sencondary NameNode :充当NameNode的备份,一般在另外一台物理机上运行。一旦NameNode出现故障的时候,就利用Secondary NameNode进行恢复
其中,Secondary NameNode和NameNode之间同步的过程如上图所示:
- 当Sec NameNode 开始请求备份数据的时候,NameNode暂时将新到达的修改操作追加到新的EditLog.new 当中
- Sec NameNode拉取NameNode中的FsImage和EditLog两个文件
- Sec NameNode将这两个文件进行合并形成最新的文件目录结构,形成检查点文件FsImage.ckpt
- Sec NameNode将检查点文件返回给NameNode
- NameNode使用FsImage.ckpt 替换旧的FsImage文件,并使用EditLog.new替换旧的EditLog文件。
- DataNode处理文件系统客户端的读写请求。在NameNode的统一调度下进行数据块的创建、删除和复制。
NameNode和DataNode 被设计成可以在普通的商用机器上运行。这些机器一般运行着GNU/Linux操作系统(OS)。HDFS采用Java语言开发,因此任何支持Java的机器都可以部署NameNode或DataNode。由于采用了可移植性极强的Java语言,使得HDFS可以部署到多种类型的机器上。一个典型的部署场景是一台机器上只运行一个NameNode实例,而集群中的其它机器分别运行一个DataNode实例。这种架构并不排斥在一台机器上运行多个DataNode,只不过这样的情况比较少见。
节点之间的读与写
文件的分块与备份
一般来说,HDFS中每个文件块都有三个副本,在写入文件块时,NameNode使用以下启发式策略来决定副本放置,并不是严格最优的
第一个副本: 如果客户端和某一DataNode位于同一个物理节点,那么HDFS将第一个副本放置在该DataNode。如果客户端不与任何DataNode放在同一物理节点,那么HDFS随机挑选一台磁盘不太满、CPU不太忙的节点。这种副本放置策略的好处就是支持快速写入
第二个副本:NameNode将第二个副本放置在与第一个副本不同的机架的某一节点上。例如下图的结构:某一文件块的第一个副本在机架1,那么第二个副本就需要放在机架2上。这样一来,如果机架2上某一个节点要读取这个文件块,就可以直接读取第二个副本了。
因此可以有效地减少跨机架的网络流量。
第三个副本:NameNode将第三个副本放在第一个副本所在机架的不同节点上。这样,如果第一个副本所在的节点宕机了,那么该节点可以读取第三个副本。而且即便机架与交换机之间存在故障也不影响。
因此这种副本放置策略有利于应对故障发生时的文件块读取。
如果还有更多的副本,NameNode将随机选择节点来放置
文件写入
首先我要将一个200M文件存到HDFS集群中。
- 客户端通过RPC(远程服务)访问NameNode,请求写入一个文件。
- NameNode检查客户端是否有权限写入,如果有权限返回一个响应。如果没有客户端就会抛出一个异常。
- 客户端会将文件按BlckSize大小(默认128M)将文件切分成一个一个Block块,然后请求写入第一个Block块。
- NameNode会根据它的负载均衡机制,给客户端返回满足其副本数量(默认是3)的列表(BlockId:主机,端口号,存放的目录)。
- 客户端根据返回的列表,开始建立管道(pipeline)。客户端->第一个节点->第二个节点->第三个节点。
- 开始传输数据,Block按照Packet一次传输,当一个Packet成功传输到第一个DataNode上以后,第一个DodaNode就把这个Packet开始进行复制,并将这个Packet通过管道传输到下一个DataNode上,下一个DataNode接收到Packet后,继续进行复制,再传输到下一个DataNode上。这就是单个文件块传输的流水线方式
- 当一个Block块成功传输完以后,从最后一个DataNode开始,依次从管道返回ACK队列,到客户端。最后,DataNode1会向客户端发送确认信息,表示该文件已成功写入。
- 客户端会在自己内部维护着一个ACK队列,跟返回来的ACK队列进行匹配,只要有一台DataNode写成功,就认为这次写操作是完成的。
- 开始进行下一个Block块的写入。重复3-8。
这样一来,集群中单一NameNode的结构大大简化了系统的架构。NameNode是所有HDFS元数据的仲裁者和管理者。用户数据永远不会流过NameNode。
如果在传输的时候,有的DataNode宕机了,这个DataNode就会从这个管道中退出。剩下的DataNode继续传输。然后,等传输完成以后,NameNode会再分发出一个节点,去写成功的DataNode上复制出一份Block块,写到新的DataNode上。
流水线复制
当客户端向HDFS文件写入数据的时候,一开始是写到本地临时文件中。假设该文件的副本系数设置为3,当本地临时文件累积到一个数据块的大小时,客户端会从NameNode获取一个DataNode列表用于存放副本。然后客户端开始向第一个DataNode传输数据,第一个DataNode一小部分一小部分(4 KB)地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个DataNode节点。第二个DataNode也是这样,一小部分一小部分地接收数据,写入本地仓库,并同时传给第三个DataNode。最后,第三个DataNode接收数据并存储在本地。因此,DataNode能流水线式地从前一个节点接收数据,并在同时转发给下一个节点,数据以流水线的方式从前一个DataNode复制到下一个。
文件读取
现在我们来学习,在这种架构下,当客户端需要读取数据的时候,系统是如何操作的。
- 客户端回合NameNode通信,请求读取一个文件
- NameNode会根据文件的路径等信息,判断读取请求是否合法。如果合法则向客户端返回文件所有数据块的存放地址
- 对于第一个数据块,客户端从最近的存放该数据块的DataNode读取数据
- 当第一个数据块读取完毕后,客户端从最近的存放第二个数据块的DataNode读取数据
- 以此类推,客户端读取下一个数据块,直到读取完所有数据块。
读取和写入的不同在于,当我写入文件的时候,客户端在每次写入一个文件块的时候,都需要询问NameNode该文件块存放的位置,并不是NameNode一次性告诉客户端所有的文件块应该存放的位置;而读取的时候,客户端则可以一下子知道所有文件块应该存放的位置。这很好理解,在写入的时候节点状态时刻在发生变化。
文件读写与一致性
如果在HDFS上对同一个文件进行并发读写访问,那么就需要加锁来保证互斥访问。这会增加编程的复杂度。因此我们可以约定:只会有一个写文件的请求发生,文件写入之后就不会更改了,文件写入后的读取操作可以并发。
基于这种思想,HDFS采用了一次写入、多次读取和简化一致性模型:
- 一个文件经过创建、写入和关闭后,就不能改变文件中已有的内容
- 已经写入到HDFS的文件,仅允许在文件末尾追加数据,即append
- 当对一个文件进行写入操作或者追加操作的时候,NameNode将拒绝其他针对该文件的读、写请求
- 当对一个文件进行读取操作时,NameNode允许其他针对该文件的读请求。
提问: 读取的时候可以并行读取吗?
如果以还原文件为目的的话,是不可以并行的,因为文件的块与块之间需要做一个拼接,如果并行读取的话,会导致文件数据混乱无法还原。
Namespace
Namespace的中文名是:文件系统的名字空间
HDFS支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。当前,HDFS不支持用户磁盘配额和访问权限控制,也不支持硬链接和软链接。但是HDFS架构并不妨碍实现这些特性。
NameNode负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被NameNode记录下来。应用程序可以设置HDFS保存的文件的副本数目。文件副本的数目称为文件的副本系数,这个信息也是由NameNode保存的。
Back up Metadata
HDFS被设计成能够在一个大集群中跨机器可靠地存储超大文件。它将每个文件存储成一系列的数据块,除了最后一个,所有的数据块都是同样大小的。为了容错,文件的所有数据块都会有副本。每个文件的数据块大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。HDFS中的文件都是一次性写入的,并且严格要求在任何时候只能有一个写入者。
NameNode全权管理数据块的复制,它周期性地从集群中的每个DataNode接收心跳信号和块状态报告(Blockreport)。接收到心跳信号意味着该DataNode节点工作正常。块状态报告包含了一个该DataNode上所有数据块的列表。
就比如说
副本存放: 最最开始的一步
副本的存放是HDFS可靠性和性能的关键。优化的副本存放策略是HDFS区分于其他大部分分布式文件系统的重要特性。这种特性需要做大量的调优,并需要经验的积累。HDFS采用一种称为机架感知(rack-aware)的策略来改进数据的可靠性、可用性和网络带宽的利用率。目前实现的副本存放策略只是在这个方向上的第一步。实现这个策略的短期目标是验证它在生产环境下的有效性,观察它的行为,为实现更先进的策略打下测试和研究的基础。
大型HDFS实例一般运行在跨越多个机架的计算机组成的集群上,不同机架上的两台机器之间的通讯需要经过交换机。在大多数情况下,同一个机架内的两台机器间的带宽会比不同机架的两台机器间的带宽大。
通过一个机架感知的过程,NameNode可以确定每个DataNode所属的机架id。一个简单但没有优化的策略就是将副本存放在不同的机架上。这样可以有效防止当整个机架失效时数据的丢失,并且允许读数据的时候充分利用多个机架的带宽。这种策略设置可以将副本均匀分布在集群中,有利于当组件失效情况下的负载均衡。但是,因为这种策略的一个写操作需要传输数据块到多个机架,这增加了写的代价。
在大多数情况下,副本系数是3,HDFS的存放策略是将一个副本存放在本地机架的节点上,一个副本放在同一机架的另一个节点上,最后一个副本放在不同机架的节点上。这种策略减少了机架间的数据传输,这就提高了写操作的效率。机架的错误远远比节点的错误少,所以这个策略不会影响到数据的可靠性和可用性。于此同时,因为数据块只放在两个(不是三个)不同的机架上,所以此策略减少了读取数据时需要的网络传输总带宽。在这种策略下,副本并不是均匀分布在不同的机架上。三分之一的副本在一个节点上,三分之二的副本在一个机架上,其他副本均匀分布在剩下的机架中,这一策略在不损害数据可靠性和读取性能的情况下改进了写的性能。
副本选择
为了降低整体的带宽消耗和读取延时,HDFS会尽量让读取程序读取离它最近的副本。如果在读取程序的同一个机架上有一个副本,那么就读取该副本。如果一个HDFS集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。
安全模式
NameNode启动后会进入一个称为安全模式的特殊状态。处于安全模式的NameNode是不会进行数据块的复制的。NameNode从所有的 DataNode接收心跳信号和块状态报告。块状态报告包括了某个DataNode所有的数据块列表。每个数据块都有一个指定的最小副本数。当NameNode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全(safely replicated)的;在一定百分比(这个参数可配置)的数据块被NameNode检测确认是安全之后(加上一个额外的30秒等待时间),NameNode将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他DataNode上。
文件系统元数据的持久化
NameNode上保存着HDFS的名字空间。对于任何对文件系统元数据产生修改的操作,NameNode都会使用一种称为EditLog的事务日志记录下来。例如,在HDFS中创建一个文件,NameNode就会在Editlog中插入一条记录来表示;同样地,修改文件的副本系数也将往Editlog插入一条记录。NameNode在本地操作系统的文件系统中存储这个Editlog。整个文件系统的名字空间,包括数据块到文件的映射、文件的属性等,都存储在一个称为FsImage的文件中,这个文件也是放在NameNode所在的本地文件系统上。
NameNode在内存中保存着整个文件系统的名字空间和文件数据块映射(Blockmap)的映像。这个关键的元数据结构设计得很紧凑,因而一个有4G内存的NameNode足够支撑大量的文件和目录。当NameNode启动时,它从硬盘中读取Editlog和FsImage,将所有Editlog中的事务作用在内存中的FsImage上,并将这个新版本的FsImage从内存中保存到本地磁盘上,然后删除旧的Editlog,因为这个旧的Editlog的事务都已经作用在FsImage上了。这个过程称为一个检查点(checkpoint)。在当前实现中,检查点只发生在NameNode启动时,在不久的将来将实现支持周期性的检查点。
DataNode将HDFS数据以文件的形式存储在本地的文件系统中,它并不知道有关HDFS文件的信息。它把每个HDFS数据块存储在本地文件系统的一个单独的文件中。DataNode并不在同一个目录创建所有的文件,实际上,它用试探的方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。在同一个目录中创建所有的本地文件并不是最优的选择,这是因为本地文件系统可能无法高效地在单个目录中支持大量的文件。当一个DataNode启动时,它会扫描本地文件系统,产生一个这些本地文件对应的所有HDFS数据块的列表,然后作为报告发送到NameNode,这个报告就是块状态报告。
通讯协议
所有的HDFS通讯协议都是建立在TCP/IP协议之上。客户端通过一个可配置的TCP端口连接到NameNode,通过ClientProtocol协议与NameNode交互。而DataNode使用DataNodeProtocol协议与NameNode交互。一个远程过程调用(RPC)模型被抽象出来封装ClientProtocol和DataNodeprotocol协议。在设计上,NameNode不会主动发起RPC,而是响应来自客户端或 DataNode 的RPC请求。
容错机制
HDFS的主要目标就是即使在出错的情况下也要保证数据存储的可靠性。常见的三种出错情况是:NameNode出错, DataNode出错和网络割裂(network partitions)。
磁盘数据错误,心跳检测和重新复制
每个DataNode节点周期性地向NameNode发送心跳信号。网络割裂可能导致一部分DataNode跟NameNode失去联系。NameNode通过心跳信号的缺失来检测这一情况,并将这些近期不再发送心跳信号DataNode标记为宕机,不会再将新的IO请求发给它们。任何存储在宕机DataNode上的数据将不再有效。DataNode的宕机可能会引起一些数据块的副本系数低于指定值,NameNode不断地检测这些需要复制的数据块,一旦发现就启动复制操作。在下列情况下,可能需要重新复制:某个DataNode节点失效,某个副本遭到损坏,DataNode上的硬盘错误,或者文件的副本系数增大。
集群均衡
HDFS的架构支持数据均衡策略。如果某个DataNode节点上的空闲空间低于特定的临界点,按照均衡策略系统就会自动地将数据从这个DataNode移动到其他空闲的DataNode。当对某个文件的请求突然增加,那么也可能启动一个计划创建该文件新的副本,并且同时重新平衡集群中的其他数据。这些均衡策略目前还没有实现。
数据完整性
从某个DataNode获取的数据块有可能是损坏的,损坏可能是由DataNode的存储设备错误、网络错误或者软件bug造成的。HDFS客户端软件实现了对HDFS文件内容的校验和(checksum)检查。当客户端创建一个新的HDFS文件,会计算这个文件每个数据块的校验和,并将校验和作为一个单独的隐藏文件保存在同一个HDFS名字空间下。当客户端获取文件内容后,它会检验从DataNode获取的数据跟相应的校验和文件中的校验和是否匹配,如果不匹配,客户端可以选择从其他DataNode获取该数据块的副本。
元数据磁盘错误
FsImage和Editlog是HDFS的核心数据结构。如果这些文件损坏了,整个HDFS实例都将失效。因而,NameNode可以配置成支持维护多个FsImage和Editlog的副本。任何对FsImage或者Editlog的修改,都将同步到它们的副本上。这种多副本的同步操作可能会降低NameNode每秒处理的名字空间事务数量。然而这个代价是可以接受的,因为即使HDFS的应用是数据密集的,它们也非元数据密集的。当NameNode重启的时候,它会选取最近的完整的FsImage和Editlog来使用。
NameNode是HDFS集群中的单点故障(single point of failure)所在。如果NameNode机器故障,是需要手工干预的。目前,自动重启或在另一台机器上做NameNode故障转移的功能还没实现。
快照
快照支持某一特定时刻的数据的复制备份。利用快照,可以让HDFS在数据损坏时恢复到过去一个已知正确的时间点。HDFS目前还不支持快照功能,但计划在将来的版本进行支持。
数据组织
数据块
HDFS被设计成支持大文件,适用HDFS的是那些需要处理大规模的数据集的应用。这些应用都是只写入数据一次,但却读取一次或多次,并且读取速度应能满足流式读取的需要。HDFS支持文件的“一次写入多次读取”语义。一个典型的数据块大小是64MB(或128MB)。因而,HDFS中的文件总是按照64M被切分成不同的块,每个块尽可能地存储于不同的DataNode中。
Staging
客户端创建文件的请求其实并没有立即发送给NameNode,事实上,在刚开始阶段HDFS客户端会先将文件数据缓存到本地的一个临时文件。应用程序的写操作被透明地重定向到这个临时文件。当这个临时文件累积的数据量超过一个数据块的大小,客户端才会联系NameNode。NameNode将文件名插入文件系统的层次结构中,并且分配一个数据块给它。然后返回DataNode的标识符和目标数据块给客户端。接着客户端将这块数据从本地临时文件上传到指定的DataNode上。当文件关闭时,在临时文件中剩余的没有上传的数据也会传输到指定的DataNode上。然后客户端告诉NameNode文件已经关闭。此时NameNode才将文件创建操作提交到日志里进行存储。如果NameNode在文件关闭前宕机了,则该文件将丢失。
上述方法是对在HDFS上运行的目标应用进行认真考虑后得到的结果。这些应用需要进行文件的流式写入。如果不采用客户端缓存,由于网络速度和网络堵塞会对吞估量造成比较大的影响。这种方法并不是没有先例的,早期的文件系统,比如AFS,就用客户端缓存来提高性能。为了达到更高的数据上传效率,已经放松了POSIX标准的要求。
可访问性
HDFS给应用提供了多种访问方式。用户可以通过Java API接口访问,通过命令行访问,通过Ambari可视化界面访问,也可以通过C语言的封装API访问,还可以通过浏览器的方式访问HDFS中的文件。
DFS Shell
HDFS以文件和目录的形式组织用户数据。它提供了一个命令行的接口(DFSShell)让用户与HDFS中的数据进行交互。命令的语法和用户熟悉的其他shell(例如 bash, csh)工具类似。下面是一些动作/命令的示例:
动作 | 命令 |
---|---|
创建一个名为 /foodir 的目录 |
bin/hadoop dfs -mkdir /foodir |
创建一个名为 /foodir 的目录 |
bin/hadoop dfs -mkdir /foodir |
查看名为/foodir/myfile.txt 的文件内容 |
bin/hadoop dfs -cat /foodir/myfile.txt |
DFSShell 可以用在那些通过脚本语言和文件系统进行交互的应用程序上。
DFS Admin
DFSAdmin 命令用来管理HDFS集群。这些命令只有HDSF的管理员才能使用。下面是一些动作/命令的示例:
动作 | 命令 |
---|---|
将集群置于安全模式 | bin/hadoop dfsadmin -safemode enter |
显示DataNode列表 | bin/hadoop dfsadmin -report |
使DataNode节点 DataNodename退役 | bin/hadoop dfsadmin -decommission DataNodename |
浏览器接口
一个典型的HDFS安装会在一个可配置的TCP端口开启一个Web服务器用于暴露HDFS的名字空间。用户可以用浏览器来浏览HDFS的名字空间和查看文件的内容。
存储空间回收
文件的删除和恢复
当用户或应用程序删除某个文件时,这个文件并没有立刻从HDFS中删除。实际上,HDFS会将这个文件重命名转移到/trash
目录。只要文件还在/trash
目录中,该文件就可以被迅速地恢复。文件在/trash
中保存的时间是可配置的,当超过这个时间时,NameNode就会将该文件从名字空间中删除。删除文件会使得该文件相关的数据块被释放。注意,从用户删除文件到HDFS空闲空间的增加之间会有一定时间的延迟。
只要被删除的文件还在/trash
目录中,用户就可以恢复这个文件。如果用户想恢复被删除的文件,可以浏览/trash
目录找回该文件。/trash
目录仅仅保存被删除文件的最后副本。/trash
目录与其他的目录没有什么区别,除了一点:在该目录上HDFS会应用一个特殊策略来自动删除文件。目前的默认策略是删除/trash
中保留时间超过6小时的文件。将来,这个策略可以通过一个被良好定义的接口配置。
减少副本系数
当一个文件的副本系数被减小后,NameNode会选择过剩的副本删除。下次心跳检测时会将该信息传递给DataNode。DataNode遂即移除相应的数据块,集群中的空闲空间加大。同样,在调用setReplication API结束和集群中空闲空间增加间会有一定的延迟。