HDFS是整个hadoop体系的基础,负责数据的存储与管理。HDFS有着高容错性(fault-tolerant)的特点,并且设计用来部署在低廉的(low-cost)硬件上。而且它提供高吞吐量(high throughput)来访问应用程序的数据,适合那些有着超大数据集(large data set)的应用程序。它提供了一次写入多次读取的机制,数据以块的形式,同时分布在集群不同物理机器上。


1.架构体系


HDFS即Hadoop Distributed File System的简称,采用Master/Slave主从结构模型来管理数据。在设计上采用了分而治之的思想,将单服务器无法承受的大量的数据分布在多台服务器上。HDFS主要由Client、NameNode、DataNode,SecondaryNameNode这四部分组成。



client:


1.上传文件时按照Block块大小进行文件的切分;


2.和NameNode交互,获取文件位置信息;


3.和DataNode交互,读取和写入数据;


4.管理和访问整个HDFS。



NameNode:


1.HDFS的元数据节点 master ,是一个主管,每个HDFS集群只有一个Action的NameNode对外提供服务


2.管理HDFS的名称空间( nameSpace )和数据块(Block)映射信息,配置相关副本信息,处理客户端请求,一个文件对应块的名字以及块被存储在哪里,以及每一个文件备份多少都是由NameNode来管理。


nameSpace:


nameSpace维护着文件系统树(FileSystem Tree)和文件树上的所有文件及文件夹的元数据(metadata),并使用fsimage和editlog这两个文件来管理这些信息。fsimage(空间镜像文件),它是文件系统元数据的一个完整的永久检查点,内部维护的是最近一次检查点的文件系统树和整棵树内部的所有文件和目录的元数据,如修改时间,访问时间,访问权限,副本数据,块大小,文件的块列表信息等等。editlog(编辑日志文件),当HDFS系统发生打开、关闭、创建、删除、重命名等操作产生的信息除了在保存在内存中外,还会持久化到编辑日志文件。比如上传一个文件后,日志文件里记录的有这次事务的tx id,文件的inode id,数据块的副本数,数据块的id,数据块大小,访问时间,修改时间等。



文件Block映射信息:


作为一个master,NameNode需要记录每个文件的每个块所在的数据节点的位置信息,也就是我们常说的元数据信息metaData。但是由于NameNode并不进行持久化存储,因此NameNode需要管理Block到DataNode的映射信息。一般元数据主要是文件名—> 数据块映射和数据块 —> Datanode列表映射。


其中文件名 —> 数据块映射保存在磁盘上进行持久化存储,但是NameNode并不保存数据块 —> DataNode列表映射,这份列表是通过心跳机制(heartbeat)建立起来的。NameNode执行文件系统的namespace操作,如打开、关闭、重命名文件和目录的同时决定了文件数据块到具体DataNode节点的映射。



HDFS心跳机制:


由于HDFS是master/slave结构,其中master包括namenode和resourcemanager,slave包括datanode和nodemanager。在master启动时会开启一个IPC服务,然后等待slave连接。当slave启动后,会主动以默认3秒一次的频率链接IPC服务。当然这个时间是可以调整的,这个每隔一段时间连接一次的机制,就是心跳机制(默认的 heartbeat.recheck.interval 大小为 5 分钟,dfs.heartbeat.interval 默认的大小为 3 秒)。slave通过心跳给master汇报自己信息,master通过心跳下达命令。具体来说就是:


Namenode通过心跳得知DataNode状态,Resourcemanager通过心跳得知nodemanager状态


当master很长时间没有收到slave信息时,就认为slave挂掉了。


这个判断挂掉的时间计算公式:2recheck+10heartbeat(Recheck的时间单位为毫秒,heartbeat的时间单位为秒 ),默认为10分钟30秒 。



举例:如果 heartbeat.recheck.interval 设置为 6000(毫秒), dfs.heartbeat.interval设置为 5(秒),则总的超时时间为 62 秒。



DataNode:


1.slave节点,存储实际数据,并汇报状态信息给NameNode,默认一个文件会备份3份在不同的DataNode中,实现高可靠性和容错性。


2.执行数据块的读/写操作


DataNode在HDFS结构里是Slave的角色,因此就像咱们的实际生活一样,在整个HDFS集群里DataNode是有很多的。DataNode负责数据块的存储和读取,数据块存储在DataNode节点所在的本地文件系统中(包括block和block meta),并且它会利用心跳机制定期向NameNode发送自己所存储的block块映射列表。


数据块:


数据块是指存储在HDFS中的最小单元,默认会在多个DataNode节点存储3份,每个数据块默认128M。**这里为什么是128M呢?**因为考虑到了寻址时间是10ms,而传输速率为100MB/s,所以为了最小化寻址的开销同时兼顾传输效率,选择了128M,这样寻址只占用传输时间的1%。



Secondary NameNode:


镜像备份,日志与镜像的定期合并,及合并NameNode的edit logs 到fsimage文件中, 可以减少editlog的文件大小。这样就可以节省NameNode的重启时间,可以尽快的退出安全模式。


第一阶段:NameNode启动


1.第一次启动NameNode格式化后,创建fsimage和edits文件。如果不是第一次 启动,直接加载编辑日志和镜像文件到内存,


一旦在内存中成功建立文件系统元数据的映像,则会创建一个新的fsimage文件和一个空的编辑日志,然后开始监听DataNode的请求


2.客户端对元数据进行增删改的请求


3.NameNode记录操作日志,更新滚动日志


4.NameNode在内存中对数据进行增删改查


第二阶段:secondary NameNode工作


1.Secondary NameNode询问NameNode是否需要checkpoint。NameNode会返回去信息


2.如果需要,Secondary NameNode将执行checkpoint


3.将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode


4.Secondary NameNode加载日志和镜像文件到内存,并合并,生成新的镜像文件fsimage.chkpoint


5.拷贝新的镜像文件到NameNode,并重新命名为fsimage


6.在紧急情况下,可辅助恢复NameNode


检查点机制:


editlog和fsimage两个文件的合并周期,被称为检查点机制(checkpoint)


fsimage文件是文件系统元数据的持久化检查点,不会在写操作后马上更新,因为fsimage写非常慢。


由于editlog不断增长,在NameNode重启时,会造成长时间NameNode处于安全模式,不可用状态,是非常不符合Hadoop的设计初衷。所以要周期性合并editlog,但是这个工作由NameNode来完成,会占用大量资源,这样就出现了SecondaryNamenode,它可以进行image检查点的处理工作。



2.数据读写


HDFS的数据读写都分为很多步骤,想要轻松的掌握它,有个简单的小技巧------从粗略到细致。也就是我们先知道大致的流程,然后再把每一步进行细化,最终全部掌握。


开始掌握读写流程前,需要先了解3个基本概念:


block


前面也提到过block块,它是数据存储的最大单位,在进行文件上传前client会对文件进行分块,分得的块就是block,默认128M,这是在综合考虑寻址时间和传输效率的的情况下得出的最佳大小。


packet


packet是client向DataNode传输数据时候的基本单位,默认64KB。


chunk


chunk是进行数据校验的基本单位,默认512Byte,加上4Byte的校验位,实际上chunk写入packet的大小为516Byte,常见于client向DataNode进行的数据校验。


2.1、读数据


粗略流程(便于记忆):


client向namenode请求block所在的datanode节点列表;


client从最近位置逐个依次从datanode中读取block信息;


整个通过io流读取的过程需要校验每个快信息;


读取完成,关闭所有流。


细致流程(便于理解):


首先调用FileSystem的open方法获取一个DistributedFileSystem实例;


然后DistributedFileSystem实例通过RPC在NameNode里获得文件的第一批block的locations(可能是需要读取文件的全部,也可能是一部分),同一个block会按照在DataNode的重复数返回多个locations;


返回的多个locations会按照Hadoop拓扑结构排序,按照就近原则来排序;


前面三步结束后会返回一个FSDataInputStream对象,通过调用read方法时,该对象会找出离客户端最近的DataNode并与之建立连接;


数据通过io流从DataNode源源不断地流向客户端;


如果第一个block块数据读取完成,就会关闭指向第一个block块的DataNode连接,接着读取下一个block块,直到把这一批的block块数据读取完成;


每读取完一个block块都会进行checksum验证(校验每个块的信息通过偏移量和预写值对比,写的时候是校验packet的信息),如果读取 DataNode 时出现错误,客户端会 通知 NameNode,然后再从下一个拥有该 block 拷贝的 DataNode 继续读;


如果第一批blocks读取完成,且文件读取还没有结束,也就是文件还没读完。FSDataInputStream就会向NameNode获取下一批blocks的locations,然后重复上面的步骤,直到所有blocks读取完成,这时就会关闭所有的流。



2.2、写数据


粗略流程(便于记忆):


client向NameNode发送写文件请求;


NameNode检查文件,如果通过就返回输出流对象;


client切分文件并且把数据和NameNode返回的DataNode列表一起发送给最近的一个DataNode节点;


DataNode写完之后返回确认信息;


数据全部写完,关闭输入输出流,并发送完成信号给NameNode。



细致流程(便于理解):


客户端使用Configuration类加载配置文件信息,然后调用FileSystem的get()方法,获取一个分布式文件系统对象DistributedFileSystem。然后通过调用这个对象的create方法,向NameNode发送写文件请求;


客户端通过RPC与NameNode进行通信,NameNode需要经过各种不同的检查,比如命名空间里该路径文件是否存在,客户端是否有相应权限。如果没有通过,返回IOException,反之如果检查通过,NameNode就会在命名空间下新建该文件(此时新文件大小为0字节),并记录元数据,返回一个FSDataOutputStream输出流对象;


FSDataOutputStream封装了一个DFSOutputStream对象,由该对象负责处理datanode和namenode之间的通信。(DistributedFileSystem.create()会调用DFSClient.create()方法创建DFSOutputStream输出流并构造一个HdfsDataOutputStream来包装DFSOutputStream);


客户端把数据按照block块进行切分;


然后调用DFSOutputStream的create方法,开始执行写入操作(FSDataOutputStream封装了一个DFSOutputStream对象,由该对象负责处理datanode和namenode之间的通信),DFSOutputStream会把数据切成一个个小packet,然后排成队列 dataQueue;


DataStreamer(DFSOutputStream的内部线程类)会去处理dataQueue,它先问询 NameNode 这个新的 block 最适合存储的在哪几个DataNode里,比如重复数是3,那么就找到3个最适合的 DataNode,把它们排成一个 pipeline。DataStreamer 把 packet 按队列输出到管道的第一个 DataNode 的内存中,然后第一个 DataNode又把 packet 输出到第二个 DataNode 中,以此类推;


在DataStreamer将packet写入pipeline时,同时也会将该packet存储到另外一个由ResponseProcessor线程管理的缓存队列ackqueue确认队列中。ResponseProcessor线程会等待DataNode的确认响应。当收到所有的DataNode的确认信息后,该线程再将ackqueue里的packet删除;


如果写入期间发生故障,会首先关闭pipeline,把ackqueue的所有packet都放回dataqueue的最前端,以确保故障节点后的节点不会漏掉任一packet。同时,会标识正常的DataNode,方便在故障节点恢复后,删除错误的部分数据块。然后从管线中删除故障节点,基于新的DataNode构建一个新的管线;


在一个block块大小的n个packet数据包写完后,客户端会调用FSDataOutputStream的close方法关闭写入流,当然在调用close之前,DataNode会将内存中的数据写入本地磁盘;


最后DataStreamer会继续向NameNode请求下一个块的DataNode列表,开始下一个块的写入。直到写完整个文件的最后一个块数据,然后客户端通知 NameNode 把文件标示为已完成,到这里整个写入过程就结束了。


3.优缺点


数据的读写是HDFS的核心和考察重点,下面在结合着总结一下优缺点:


优点:


简单一致性模型:一次写入,多次读取,但要注意不能修改,只能追加;


高容错,低成本:可以搭建在廉价的机器上,并且数据以多副本保存在不同的服务器上,某个副本丢失,也能通过别的副本进行恢复;


流式数据访问:不是随机读写;


适合大规模数据集:能够进行批处理,支持横向扩展,支持PB级数据和10k节点规模。


缺点:


延迟高:不支持低延迟数据访问,做不到毫秒级存储数据,但是适合高吞吐率(某一时间内写入大量的数据)的场景;


不适合小文件:每条元数据占用空间是一定的,因此大量小文件会占用NameNode大量的内存来存储文件、目录和块信息;


不支持并发写入:一个文件只允许一个线程进行写操作,不适合并发写入;


不能修改和随机读写:文件不允许修改,只支持追加,同时也不是随机读写。