HDFS读写流程详解
HDFS读写流程详解
[TOC]
一、架构体系
什么是HDFS?
HDFS即Hadoop Distributed File System的简称,采用Master/Slave主从结构模型来管理数据。在设计上采用了分而治之的思想,将单服务器无法承受的大量的数据分布在多台服务器上。HDFS主要由Client、NameNode、DataNode,SecondaryNameNode这四部分组成。
组成HDFS的各模块作用
Client
HDFS客户端是在DFSClient类的基础上实现的,提供了命令行接口、API接口、浏览器接口等面向用户的接口,使用户可以不考虑HDFS的实现细节,简化操作。
客户端在整个HDFS的作用可以进行如下总结:
- 上传文件时按照Block块大小进行文件的切分;
- 和NameNode交互,获取文件位置信息;
- 和DataNode交互,读取和写入数据;
- 管理和访问整个HDFS。
NameNode
NameNode在HDFS结构模型里充当Master的就角色,因此一个HDFS集群里只会有一个active的NameNode节点。在集群里主要用来处理客户端的读写请求,它主要负责管理命名空间(NameSpace)和文件Block映射信息。
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 秒。
NameNode作用小结:
- 管理命名空间NameSpace(文件目录树);
- 管理Block映射信息;
- 配置副本策略;
- 处理客户端的读写请求。
DataNode
DataNode在HDFS结构里里是Slave的角色,因此就像咱们的实际生活一样,在整个HDFS集群里DataNode是有很多的。DataNode负责数据块的存储和读取,数据块存储在DataNode节点所在的本地文件系统中(包括block和block meta),并且它会利用心跳机制定期向NameNode发送自己所存储的block块映射列表。
**数据块(Block)**:
数据块是指存储在HDFS中的最小单元,默认会在多个DataNode节点存储3份,每个数据块默认128M。这里为什么是128M呢?因为考虑到了寻址时间是10ms,而传输速率为100MB/s,所以为了最小化寻址的开销同时兼顾传输效率,选择了128M,这样寻址只占用传输时间的1%。
DataNode作用小结:
存储实际的Block数据块
执行数据块的读/写操作
SecondaryNameNode
SecondaryNameNode不是NameNode的热备份,因为当NameNode停止服务时,它不能很快的替换NameNode。它更像是咱们现实生活中的老板NameNode的秘书,平时整理整理文档,帮老板分担工作。它主要是用来辅助NameNode进行fsimage和editlog的合并工作,可以减少editlog的文件大小。这样就可以节省NameNode的重启时间,可以尽快的退出安全模式。
检查点机制:
editlog和fsimage两个文件的合并周期,被称为检查点机制(checkpoint)
fsimage文件是文件系统元数据的持久化检查点,不会在写操作后马上更新,因为fsimage写非常慢。
由于editlog不断增长,在NameNode重启时,会造成长时间NameNode处于安全模式,不可用状态,是非常不符合Hadoop的设计初衷。所以要周期性合并editlog,但是这个工作由Namenode来完成,会占用大量资源,这样就出现了SecondaryNamenode,它可以进行image检查点的处理工作。
具体步骤如下:
- SecondaryNamenode请求Namenode进行editlog的滚动也就是创建一个新的editlog,然后将新的编辑操作记录到新生成的editlog文件;
- 通过http get方式,读取NameNode上的fsimage和edits文件,到SecondaryNamenode上;
- 读取fsimage到内存中,即加载fsimage到内存,然后执行edits中所有操作,并生成一个新的fsimage文件(fsimage.ckpt临时文件),也就是这个检查点被创建了;
- 通过http post方式,将fsimage.ckpt临时文件传送到Namenode;
- Namenode使用新的fsimage替换原来的fsimage文件(fsimage.ckpt重命名为fsimage),并且用第一步创建的edits(editlog)替代原来的edits文件;然后更新fsimage文件的检查点时间。
SecondaryNameNode作用小结:
辅助NameNode,分担其部分工作
定期合并fsimage和fsedits,并推送给NameNode
在紧急情况下,可辅助恢复NameNode
二、数据读写
HDFS的数据读写都分为很多步骤,想要轻松的掌握它,有个简单的小技巧——从粗略到细致。也就是我们先知道大致的流程,然后再把每一步进行细化,最终全部掌握。
开始掌握读写流程前,需要先了解3个基本概念:
block
前面也提到过block块,它是数据存储的最大单位,在进行文件上传前client会对文件进行分块,分得的块就是block,默认128M,这是在综合考虑寻址时间和传输效率的的情况下得出的最佳大小。
packet
packet是client向DataNode传输数据时候的基本单位,默认64KB。
chunk
chunk是进行数据校验的基本单位,默认512Byte,加上4Byte的校验位,实际上chunk写入packet的大小为516Byte,常见于client向DataNode进行的数据校验。
读数据
粗略流程(便于记忆):
- 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读取完成,这时就会关闭所有的流。
写数据
粗略流程(便于记忆):
- 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 把文件标示为已完成,到这里整个写入过程就结束了。
三、HDFSの优缺点
数据的读写是HDFS的核心和考察重点,下面在结合着总结一下优缺点:
优点
- 简单一致性模型:一次写入,多次读取;保证数据的一致性,但要注意不能修改,只能追加;
- 高容错,低成本:可以搭建在廉价的机器上,并且数据以多副本保存在不同的服务器上,某个副本丢失,也能通过别的副本进行恢复;
- 流式数据访问:不是随机读写;
- 适合大规模数据集:能够进行批处理,支持横向扩展,支持PB级数据和10k节点规模。
缺点
- 延迟高:不支持低延迟数据访问,做不到毫秒级存储数据,但是适合高吞吐率(某一时间内写入大量的数据)的场景;
- 不适合小文件:每条元数据占用空间是一定的,因此大量小文件会占用NameNode大量的内存来存储文件、目录和块信息;
- 不支持并发写入:一个文件只允许一个线程进行写操作,不适合并发写入;
- 不能修改和随机读写:文件不允许修改,只支持追加,同时也不是随机读写
ps:为什么hdfs不是个存储小文件?
这是和HDFS系统底层设计实现有关系的,HDFS本身的设计就是用来解决海量大文件数据的存储.,他天生喜欢大数据的处理,大文件存储在HDFS中,会被切分成很多的小数据块,任何一个文件不管有多小,都是一个独立的数据块,而这些数据块的信息则是保存在元数据中的,在之前的博客HDFS基础里面介绍过在HDFS集群的namenode中会存储元数据的信息,这里再说一下,元数据的信息主要包括以下3部分:
1:抽象目录树
2:文件和数据块的映射关系,一个数据块的元数据大小大约是150byte
3:数据块的多个副本存储地
而元数据的存储在磁盘(1和2)和内存中(1、2和3),而服务器中的内存是有上限的,举个例子:
- 有100个1M的文件存储进入HDFS系统,那么数据块的个数就是100个,元数据的大小就是100*150byte,消耗了15000byte的内存,但是只存储了100M的数据。
- 有1个100M的文件存储进入HDFS系统,那么数据块的个数就是1个,元数据的大小就是150byte,消耗量150byte的内存,存储量100M的数据。
所以总结如下:
1、小文件过多,会过多占用namenode的内存,并浪费block
2、文件过小,寻址时间大于数据读写时间,这不符合HDFS的设计
四、HDFS的高可靠性策略
1、文件的完整性
- 检验和:在文件建立时,每个数据块都会产生校验和,检验和会保存在
.meta
文件内。客户端获取数据通过检查校验和,发现数据块是否损坏,从而确定是否要读取副本。NameNode标记该块已经损坏,然后复制Block达到与其设置的文件副本数。 - ***验证checksum***:DataNode 在文件创建后三周验证其checksum
2、网络或机器失效时
- 副本冗余:数据存储在这些HDFS中的节点上,为了防止因为某个节点宕机而导致数据丢失,HDFS对数据进行冗余备份,至于具体冗余多少个副本,在dfs.replication中配置。
- 机架感知策略:集群一般放在不同机架上,机架间带宽要比机架内带宽要小;HDFS具有“机架感知”能力,它能自动实现在本机架上存放一个副本,然后在其它机架再存放另一副本,这样可以防止机架失效时数据丢失,也可以提高带宽利用率。故又称为副本放置策略
- 心跳机制:DataNode节点定时向NameNode节点发送心跳包,以确保DataNode没有宕机。如果宕机,会采取相应措施,比如数据*副本的备份。
3、NameNode挂掉时
- 主备切换(HA):Active节点的NN挂掉,StrandBy的NN顶上。
- Fsimage和Editslog磁盘存储
- Fsimage和Editslog阔以存储多份,多磁盘存储
4、其他保障机制
- 快照:支持存储某个时间点的映像,需要时可以使数据重返这个时间点的状态。
- 回收站机制:删除文件,会先到回收站/trash,其里面文件可以快速回复。
- 安全模式:Namenode启动时会先经过一个“安全模式”阶段。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!