Hadoop分布式⽂件系统(HDFS)详解
HDFS简介:
当数据集的⼤⼩超过⼀台独⽴物理计算机的存储能⼒时,就有必要对它进⾏分区 (partition)并存储到若⼲台单独的计算机上。管理⽹络中跨多台计算机存储的⽂件系统成为分布式⽂件系统 (Distributed filesystem)。该系统架构于⽹络之上,势必会引⼊⽹络编程的复杂性,因此分布式⽂件系统⽐普通磁盘⽂件系统更为复杂。
HDFS是基于流数据模式访问和处理超⼤⽂件的需求⽽开发的,它可以运⾏于廉价的商⽤服务器上。总的来说,可以将 HDFS的主要特点概括为以下⼏点:
(1 )处理超⼤⽂件
这⾥的超⼤⽂件通常是指数百 MB、甚⾄数百TB ⼤⼩的⽂件。⽬前在实际应⽤中, HDFS已经能⽤来存储管理PB(PeteBytes)级的数据了。在 Yahoo!,Hadoop 集也已经扩展到了 4000个节点。
(2 )流式地访问数据
HDFS的设计建⽴在更多地响应“⼀次写⼊,多次读取”任务的基础之上。这意味着⼀个数据集⼀旦由数据源
⽣成,就会被复制分发到不同的存储节点中,然后响应各种各样的数据分析任务请求。在多数情况下,分析任务都会涉及数据集中的⼤部分数据,也就是说,对HDFS 来说,请求读取整个数据集要⽐读取⼀条记录更加⾼效。
(3 )运⾏于廉价的商⽤机器集上
Hadoop设计对硬件需求⽐较低,只须运⾏在廉价的商⽤硬件集上,⽽⽆须昂贵的⾼可⽤性机器上。廉价的商⽤机也就意味着⼤型集中出现节点故障情况的概率⾮常⾼。这就要求在设计 HDFS时要充分考虑数据的可靠性、安全性及⾼可⽤性。
正是由于以上的种种考虑,我们会发现现在的 HDFS在处理⼀些特定问题时不但没有优势,⽽且有⼀定的局限性,主要表现在以下⼏个⽅⾯。
(1 )不适合低延迟数据访问
如果要处理⼀些⽤户要求时间⽐较短的低延迟应⽤请求,则 HDFS不适合。HDFS 是为了处理⼤型数据集分析任务的,主要是为达到⾼的数据吞吐量⽽设计的,这就可能要求以⾼延迟作为代价。⽬前有⼀些补充的⽅案,⽐如使⽤HBase,通过上层数据管理项⽬来尽可能地弥补这个不⾜。
(2 )⽆法⾼效存储⼤量⼩⽂件
在Hadoop 中需要⽤ NameNode来管理⽂件系统的元数据,以响应客户端请求返回⽂件位置等,因此⽂件数量⼤⼩的限制要由 NameNode 来决定。例如,每个⽂件、索引⽬录及块⼤约占 100字节,如果有100 万个⽂件,每个⽂件占⼀个块,那么⾄少要消耗 200MB内存,这似乎还可以接受。但如果有更多⽂件,那么 NameNode的⼯作压⼒更⼤,检索处理元数据的时间就不可接受了。
(3 )不⽀持多⽤户写⼊及任意修改⽂件
在HDFS 的⼀个⽂件中只有⼀个写⼊者,⽽且写操作只能在⽂件末尾完成,即只能执⾏追加操作。⽬前 HDFS还不⽀持多个⽤户对同⼀⽂件的写操作,以及在⽂件任意位置进⾏修改。
HDFS的相关概念:
1、数据块( block)
我们知道,在操作系统中都有⼀个⽂件块的概念,⽂件以块的形式存储在磁盘中,此处块的⼤⼩代表系统读 /写可操作的最⼩⽂件⼤⼩。也就是说,⽂件系统每次只能操作磁盘块⼤⼩的整数倍数据。通常来说,⼀个⽂件系统块为⼏千字节,⽽磁盘块⼤⼩为 512字节。⽂件的操作都由系统完成,这些对⽤户来说都是透明的。
这⾥,我们所要介绍的HDFS中的块是⼀个抽象的概念,它⽐上⾯操作系统中所说的块要⼤得多。在配
置Hadoop系统时会看到,它的默认块为 64MB。和单机上的⽂件系统相同, HDFS分布式⽂件系统中的⽂件也被分成块进⾏存储,它是⽂件存储处理的逻辑单元。
为何HDFS 中的块如此之⼤? HDFS的块⽐磁盘块⼤,其⽬的是为了最⼩化寻址开销。如果块设置得⾜够⼤,从磁盘传输数据的时间可以明显⼤于定位这个块开始位置所需的时间。这样,传输⼀个由多个块组成的⽂件的时间取决于磁盘传输速率。我们来做⼀个速算,如果寻址时间为 10ms左右,⽽传输速率为100MB/s,为了使寻址时间仅占传输时间的 1%,我们需要设置块⼤⼩为 100MB左右。⽽默认的块⼤⼩实际为 64MB,但是很多情况下HDFS使⽤128MB的块设置。以后随着新⼀代磁盘驱动器传输速率的提升,块的⼤⼩将被设置得更⼤。但是该参数也不会设置得过⼤。 MapReduce中的map 任务通常⼀次处理⼀个块中的数据,因此如果任务数太少 (少于集中的节点数量),作业的运⾏速度就会⽐较慢。
HDFS作为⼀个分布式⽂件系统,是设计⽤来处理⼤⽂件的,使⽤抽象的块会带来很多好处。⼀个好处是可以存储任意⼤的⽂件,⽽⼜不会受到⽹络中任⼀单个节点磁盘⼤⼩的限制。可以想象⼀下,单个节点磁盘 100TB的数据是不可能的,但是由于逻辑块的设计, HDFS可以将这个超⼤的⽂件分成众多块,分别存储在集的各台机器上。另外⼀个好处是使⽤抽象块作为操作的单元可简化存储⼦系统。这⾥之所以提到简化,是因为这是所有系统的追求。⽽对故障出现频繁和种类繁多的分布式系统来说,简化就显得尤为重要。在 HDFS中块的⼤⼩固定,这样它就简化了存储系统的管理,特别是元数据信息可以和⽂件块内容分开存储。不仅如此,块更有利于分布式⽂件系统中复制容错的实现。在 HDFS中为了处理
节点故障,默认将⽂件块副本数设定为 3份,分别存储在集的不同节点上。当⼀个块损坏时,系统会通
过 NameNode获取元数据信息,在另外的机器上读取⼀个副本并进⾏存储,这个过程对⽤户来说都是透明的。当然,这⾥的⽂件块副本冗
余量可以通过⽂件进⾏配置,⽐如在有些应⽤中,可能会为操作频率较⾼的⽂件块设置较⾼的副本数量以提⾼集的吞吐量。hadoop分布式集搭建
2、 NameNode和DataNode
HDFS采⽤ master/slave架构对⽂件系统进⾏管理。⼀个 HDFS集是由⼀个NameNode 和⼀定数⽬的DataNode组成的。这两类节点分别承担 Master和Worker 的任务。 NameNode就是Master 管理集中的执⾏调度,是⼀个中⼼服务器, DataNode就是Worker 具体任务的执⾏节点。 NameNode管理⽂件系统的命名空间,维护整个⽂件系统的⽂件⽬录树及这些⽂件的所以⽬录。这些信息以两种形式存储在本地⽂件系统中,⼀种是命名空间镜像(Namespace image);⼀种是编辑⽇志(Edit log)。从 NameNode中你可以获得每个⽂件的每个块所
在 DataNode。有⼀点需要注意的是,这些信息不是永久保存的, NameNode会在每次启动系统时动态
地重建这些信息。当运⾏任务时,客户端通过 NameNode获取元数据信息,和DataNode进⾏交互以访问整个⽂件系统。系统会提供⼀个类似于POSIX的⽂件接⼝,这样⽤户在编程时⽆需考虑 NameNode和DataNode 的具体功能。
DataNode是⽂件系统 Worker中的节点,⽤来执⾏具体的任务:存储⽂件块,被客户端和 NameNode调⽤。同时,它会通过⼼跳
( heartbeat)定时向NameNode 发送所存储的⽂件块信息。
客户端读取HDFS中的数据:
客户端通过调⽤FileSystem对象中的 open()⽅法来打开希望读取的⽂件。 FileSystem是HDFS 中的DistributedFileSystem的⼀个实例(步骤1:open)。 DistributedFileSystem会通过RPC 协议调⽤ NameNode来确定请求⽂件起始块所在的位置(步骤2:get block locations)。这⾥需要注意的是, NameNode只会返回所调⽤⽂件中开始的⼏个块⽽不是全部返回。对于每个返回的块,都包含块所在的 DataNode地址。随后,这些返回的DatanNode会按照Hadoop 定义的集拓扑结构得出客户端的距离,然后再进⾏排序。如果客户端本⾝就是⼀个DataNode,那么它将从本地读取⽂件。
DistributedFileSystem会向客户端返回⼀个⽀持⽂件定位的输⼊流对象 FSDataInputStream,⽤于给客户端读取数据。 FSDataInputStream 包含⼀个DFSInputStream 对象,这个对象⽤来管理 DataNode和NameNode 之间的I/O。
当以上步骤完成时,客户端便会在这个输⼊流之上调⽤ read()函数(步骤3:read)。DFSInputStream 对象中包含⽂件开始部分的数据块所在的 DataNode地址,⾸先它会连接包含⽂件第⼀个块最近的 DataNode。随后,在数据流中重复调⽤ read()函数,直到这个块全部读完为⽌(步骤4:read)。当最后⼀个块读取完毕时, DFSInputStream会关闭连接,并查存储下⼀个数据块距离客户端最近的 DataNode(步骤5:read)。以上这些步骤对客户端来说都是透明的。(这⾥传输的是整个数据块,应该不涉及输⼊格式的问题。)
客户端按照DFSInputStream 打开和DataNode连接返回的数据流的顺序读取该块,它也会调⽤ NameNode来检索下⼀组块所在的DataNode 的位置信息。当客户端完成所有⽂件的读取时,则会在 FSDataInputStream中调⽤close()函数(步骤6:close)。
如果客户端和所连接的DataNode在读取时出现故障,那么它就会去尝试连接存储这个块的下⼀个最近的DataNode,同时它会记录这个节点的故障,这样它就不会再去尝试连接和读取块。客户端还会验证从 DataNode传送过来的数据校验和。如果发现⼀个损坏的块,那么客户端将会再尝试从别的 DataNode读取数据块,向NameNode报告这个信息, NameNode也会更新保存的⽂件信息。
这⾥要关注的⼀个设计要点是,客户端通过 NameNode引导获取最合适的DataNode地址,然后直接连接 DataNode读取数据。这种设计的好处是,可以使 HDFS扩展到更⼤规模的客户端并⾏处理,这是因为数据的流动是在所有DataNode之间分散进⾏的。同时NameNode的压⼒也变⼩了,使得 NameNode只⽤提供请求块所在的位置信息就可以了,⽽不⽤通过它提供数据,这样就避免了 NameNode随着客户端数据量的增长⽽成为系统瓶颈。
客户端将数据写⼊HDFS:
客户端创建⽂件夹是通过命令⾏执⾏的,例如: bin/hadoop dfs -mkdir input
客户端写⼊⽂件也是通过命令⾏执⾏的,例如:
bin/hadoop dfs -copyFromLocal conf/* input。上传的同时通过FileInputFormat类来对⼤⽂件进⾏分割。
客户端通过调⽤DistributedFileSystem对象中的 create()⽅法来创建⼀个⽂件(步骤1:create)。DistributedFileSystem通过RPC 调⽤在NameNode的⽂件系统命名空间中创建⼀个新⽂件,此时还没有相关的DataNode与之关联(步骤2:create)。 NameNode会通过多种验证保证新的⽂件不存在于⽂件系统中,并且确保请求客户端拥有创建⽂件的权限。当所有验证通过时, NameNode会创建⼀个新⽂件的记录,如果创建失败,则抛出⼀个 IOException异常;如果创建成功,则 DistributedFileSystem返回⼀个FSDataOutputStream 给客户端⽤来写⼊数据(步骤3:write)。这⾥ FSDataOutputStream和读取数据时的FSDataInputStream⼀样都包含⼀个数据流对象DFSOutputStream,客户端将使⽤它来处理与 DataNode和NameNode 之间的通信。
当客户端写⼊数据时,DFSOutputStream会将⽂件分割成包(这⾥利⽤ FileInputFormat类来分割⼤⽂件,FileInputFormat是所有使⽤⽂件作为其数据源的 InputFormat实现的基类。它提供了两个功能:⼀个定义哪些⽂件包含在⼀个作业的输⼊中;⼀个为输⼊⽂件⽣成分⽚的实现),然后放⼊⼀个内部队列,我们称为“数据队列”。DataStreamer会将这些⼩的⽂件包放⼊数据流中, DataStreamer的作⽤是请求NameNode 为新的⽂件包分配合适的DataNode存放副本。返回的DataNode列表形成⼀个“管道”,假设这⾥的副本数是 3,那么这个管道中就会有3个DataNode 。DataStreamer将⽂件包以流的⽅式传送给管道中的第⼀个 DataNode,第⼀个DataNode 会存储这个包,然后将它推送到第⼆个 DataNode中,随
后照这样进⾏,直到管道中的最后⼀个 DataNode(步骤4:write packet )⽂件的元数据存储在 NameNode的内存中。
DFSOutputStream同时也会保存⼀个包的内部队列,⽤来等待管道中的 DataNode返回确认信息,这个队列被称为确认队列( ack queue)。只有当所有管道中的 DataNode都返回了写⼊成功的返回信息⽂件包,才会从确认队列中删除(步骤5:ack packet)。
当数据写⼊节点失败时, HDFS会做出以下反应:⾸先管道会被关闭,任何在确认通知队列中的⽂件包都会被添加到数据队列的前端,这样管道中故障节点下游的 DataNode都不会丢失任何⼀个数据包。当前这个⽂件中的其他存放在正常⼯作的 DataNode之上的⽂件块会被赋予⼀个新的⾝份,并且和 NameNode进⾏关联,这样,如果失败的DataNode过段时间后会从故障中恢复出来,其中的部分数据块就会被删除。然后,管道会把失败的 DataNode删除,⽂件会继续被写到管道中的另外两个 DataNode中。最后,NameNode 会注意到现在的⽂件块副本数没有达到配置属性要求,会在另外的 DataNode上重新安排创建⼀个副本,随后的⽂件会正常执⾏写⼊操作。
客户端成功完成数据写⼊的操作后,会对数据流调⽤ close()⽅法(步骤6:close )。该操作将剩余的所有数据包写⼊ DataNode管线中,并在联系NameNode且发送⽂件写⼊完成信号之前,等待确认(步骤7:complete)。NameNode已经知道⽂件由哪些块组成(通
过 DataStreamer询问数据块的分配),所以它在返回成功前只需要等待数据块进⾏最⼩量的复制。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论