现在的位置: 首页 > 综合 > 正文

hdfs关键数据详解

2019年05月23日 ⁄ 综合 ⁄ 共 7912字 ⁄ 字号 评论关闭

整理by DCL

HDFS文件系统的架构

wps_clip_image-16195

图1:HDFS文件系统的架构

Namenode: 一个hdfs cluster包含一个NameNode和若干的DataNode,NameNode是master,主要负责管理HDFS文件系统,具体地包括namespace管理(其实就是目录结构),block管理(其中包括 filename->block,block->ddatanode list的对应关系)。Namenode提供的是始终被动接收服务的server,主要有三类协议接口:ClientProtocol接口、DatanodeProtocol接口、NamenodeProtocol接口。

Datanode:Datanode(简称Datanode)主要是用来存储数据文件,HDFS将一个文件分割成一个个的block,这些block可能存储在一个Datanode上或者是多个Datanode上。Datanode负责实际的底层的文件的读写,如果客户端client程序发起了读HDFS上的文件的命令,那么首先将这些文件分成block,然后Namenode将告知client这些block数据是存储在那些Datanode上的,之后,client将直接和Datanode交互。

体系结构中还有个节点没画出来,Secondary NameNode,该部分主要是定时对NameNode进行数据snapshots进行备份,这样尽量降低NameNode崩溃之后,导致数据的丢失,其实所作的工作就是从Namenode获得fsimage和edits把二者重新合并然后发给Namenode,这样,既能减轻Namenode的负担又能保险地备份。

wps_clip_image-15481

图2:HDFS中几个重要数据结构

不管是client还是Datanode的消息发到Namenode后最终都会落到FSnamesystem身上,这是一个重量级家伙,如图,对各种服务请求的处理都转交给它完成,它提供了对各种数据结构操作的接口,这些数据结构共同维护了整个Namenode的元数据信息。

Namenode中几个关键的数据结构

FSdirectory:

FSDirectory存储整个文件系统的目录状态,对整个目录结构的管理通过调用FSImage和FSEditLog的方法从Namenode本地磁盘读取元数据信息和向本地磁盘写入元数据信息,并登记对目录结构所作的修改到日志文件。另外,FSDirectory保存了文件名和数据块的映射关系。

INode是对文件系统目录结构中一个节点的抽象

INodeFile和INodeDirectory均继承自INode类,分别表示文件节点和目录节点。

INodeFile类中最重要的数据结构是BlockInfo blocks[],它记录了一个文件所包含的所有Block,成员方法的操作大都与Block相关

INodefileUnderConstruction表示正在都建的文件

INodeDirectory的关键数据结构是List<INode> children记录了目录下所有的子节点信息

INodeDirectoryWithQuota表示有配额限制的目录,根目录就是这种类型。

FSImage:

Namenode会将HDFS的文件和目录元数据存储在一个叫fsimage的二进制文件中,每次保存fsimage之后到下次保存之间的所有HDFS操作,将会记录在editlog文件中,当editlog达到一定的大小(bytes,由fs.checkpoint.size参数定义)或从上次保存过后一定时间段过后(sec,由fs.checkpoint.period参数定义),Namenode会重新将内存中对整个HDFS的目录树和文件元数据刷到fsimage文件中。Namenode就是通过这种方式来保证HDFS中元数据信息的安全性。

Fsimage是一个二进制文件,当中记录了HDFS中所有文件和目录的元数据信息,在我的hadoop的HDFS版中,该文件的中保存文件和目录的格式如下:

wps_clip_image-6633

图3:Fsimage的中数据格式

当Namenode重启加载fsimage时,就是按照如下格式协议从文件流中加载元数据信息。从fsimag的存储格式可以看出,fsimage保存有如下信息:

1.         首先是一个image head,其中包含:

a)         imgVersion(int):当前image的版本信息

b)        namespaceID(int):用来确保别的HDFS instance中的Datanode不会误连上当前NAMENODE。

c)         numFiles(long):整个文件系统中包含有多少文件和目录

d)        genStamp(long):生成该image时的时间戳信息。

2.         接下来便是对每个文件或目录的源数据信息,如果是目录,则包含以下信息:

a)         path(String):该目录的路径,如”/user/build/build-index”

b)        replications(short):副本数(目录虽然没有副本,但这里记录的目录副本数也为3)

c)         mtime(long):该目录的修改时间的时间戳信息

d)        atime(long):该目录的访问时间的时间戳信息

e)         blocksize(long):目录的blocksize都为0

f)         numBlocks(int):实际有多少个文件块,目录的该值都为-1,表示该item为目录

g)        nsQuota(long):namespace Quota值,若没加Quota限制则为-1

h)        dsQuota(long):disk Quota值,若没加限制则也为-1

i)          username(String):该目录的所属用户名

j)          group(String):该目录的所属组

k)        permission(short):该目录的permission信息,如644等,有一个short来记录。

3.         若从fsimage中读到的item是一个文件,则还会额外包含如下信息:

a)         blockid(long):属于该文件的block的blockid,

b)        numBytes(long):该block的大小

c)         genStamp(long):该block的时间戳

当该文件对应的numBlocks数不为1,而是大于1时,表示该文件对应有多个block信息,此时紧接在该fsimage之后的就会有多个blockid,numBytes和genStamp信息。

因此,在Namenode启动时,就需要对fsimage按照如下格式进行顺序的加载,以将fsimage中记录的HDFS元数据信息加载到内存中。

BlockMap

从以上fsimage中加载如Namenode内存中的信息中可以很明显的看出,在fsimage中,并没有记录每一个block对应到哪几个Datanodes的对应表信息,而只是存储了所有的关于namespace的相关信息。而真正每个block对应到Datanodes列表的信息在hadoop中并没有进行持久化存储,而是在所有Datanode启动时,每个Datanode对本地磁盘进行扫描,将本Datanode上保存的block信息汇报给Namenode,Namenode在接收到每个Datanode的块信息汇报后,将接收到的块信息,以及其所在的Datanode信息等保存在内存中。HDFS就是通过这种块信息汇报的方式来完成
block -> Datanodes list的对应表构建。Datanode向Namenode汇报块信息的过程叫做blockReport,而Namenode将block -> Datanodes list的对应表信息保存在一个叫BlocksMap的数据结构中。

BlocksMap的内部数据结构如下:

wps_clip_image-12369

图4:BlocksMap的内部数据结构

如上图显示,BlocksMap实际上就是一个Block对象对BlockInfo对象的一个Map表,其中Block对象中只记录了blockid,block大小以及时间戳信息,这些信息在fsimage中都有记录。而BlockInfo是从Block对象继承而来,因此除了Block对象中保存的信息外,还包括代表该block所属的HDFS文件的INodeFile对象引用以及该block所属Datanodes列表的信息(即上图中的DATANODE1,DATANODE2,DATANODE3,该数据结构会在下文详述)。

因此在Namenode启动并加载fsimage完成之后,实际上BlocksMap中的key,也就是Block对象都已经加载到BlocksMap中,每个key对应的value(BlockInfo)中,除了表示其所属的Datanodes列表的数组为空外,其他信息也都已经成功加载。所以可以说:fsimage加载完毕后,BlocksMap中仅缺少每个块对应到其所属的Datanodes list的对应关系信息。所缺这些信息,就是通过上文提到的从各Datanode接收blockReport来构建。当所有的Datanode汇报给Namenode的blockReport处理完毕后,BlocksMap整个结构也就构建完成。

BlockMap中Datanode列表数据结构

在BlockInfo中,将该block所属的Datanodes列表保存在一个Object[]数组中,但该数组不仅仅保存了Datanodes列表,还包含了额外的信息。实际上该数组保存了如下信息:

wps_clip_image-5921

图5:Object[] triplets(三元组)结构

上图表示一个block包含有三个副本,分别放置在DATANODE1,DATANODE2和DATANODE3三个Datanode上,每个Datanode对应一个三元组,该三元组中的第二个元素,即上图中prev block所指的是该block在该Datanode上的前一个BlockInfo引用。第三个元素,也就是上图中next Block所指的是该block在该Datanode上的下一个BlockInfo引用。每个block有多少个副本,其对应的BlockInfo对象中就会有多少个这种三元组。

Namenode采用这种结构来保存block->Datanode list的目的在于节约Namenode内存。由于Namenode将block->Datanodes的对应关系保存在了内存当中,随着HDFS中文件数的增加,block数也会相应的增加,Namenode为了保存block->Datanodes的信息已经耗费了相当多的内存,如果还像这种方式一样的保存Datanode->block list的对应表,势必耗费更多的内存,而且在实际应用中,要查一个Datanode上保存的block list的应用实际上非常的少,大部分情况下是要根据block来查Datanode列表,所以Namenode中通过上图的方式来保存block->Datanode
list的对应关系,当需要查询Datanode->block list的对应关系时,只需要沿着该数据结构中next Block的指向关系,就能得出结果,而又无需保存Datanode->block list在内存中。

Datanode: hadoop-0.20.2版本 (参考资料4)

首先在Datanode中,每一个block都对应一个数据存储文件,数据存储文件以_blk开头;同时,每一个block还对应一个元数据文件,元数据文件以.meta结尾(注意:meta file的命名格式如下blokcFileName_generationStamp.meta)。

在HDFS中有storage的概念,每一个节点对应一个storage。我们可以理解为一个Datanode是一个storage,一个Namenode也是一个storage。一个storage可以对应于多个存储目录(按照此段资料作者的理解,不同的存储目录应该是对应于不同的硬盘或者存储装置)。在Datanode中代表实际数据存储的是FSDataset。FSDataset由多个FSVolume组成。每一个FSVolume对应于storage中一个存储目录(相当于一个FSVolume对应于一个硬盘)。最终,由FSVolume进行数据的存储。每个FSVolume包含了几个文件夹,包括dataDir(用来保存blocks和meta
file)、tmpDir(临时文件夹)、detachDir(copy on write for blocks in snapshot,在写block时,在detachDir中保存一个备份,以便进行数据恢复)。

wps_clip_image-520

图6:Datanode的存储格式

Block是datanode的基本数据结构,表示一个数据块的信息,每个Block都有1个数据文件和1个元数据文件。与Block相关的类有DatanodeBlockInfo,该类有3个属性:volume是块所属的卷;file是块对应的数据文件;detached表示块是否完成copy-on-write。

Datanode并不知道关于文件的任何东西,除了将文件中的数据保存在本地的文件系统上。它把每个HDFS数据块存储在本地文件系统上隔离的文件中。Datanode并不在同一个目录创建所有的文件,相反,它用启发式地方法来确定每个目录的最佳文件数目,并且在适当的时候创建子目录。在同一个目录创建所有的文件不是最优的选择,因为本地文件系统可能无法高效地在单一目录中支持大量的文件。当一个Datanode启动时,它扫描本地文件系统,对这些本地,文件产生相应的一个所有HDFS数据块的列表,然后发送报告到Namenode,这个报告就是Blockreport。

void getVolumeMap(HashMap<Block, DatanodeBlockInfo> volumeMap,

FSVolume volume)

这个方法的主要目的就是遍历整个目录,得到所有block文件列表,并添加所有block的记录到FSDataset的volumeMap属性中,参数中的volumeMap传入的正是FSDataset的volumeMap属性,这个对象保存block与DatanodeBlockInfo的映射关系,便于通过block查询具体的block文件信息。

datanodeBlockInfo对象主要保存了block属于哪一个FSVolume,以及block块实际的存放文件是哪个。

Client与Datanode交互:(参考资料5)

wps_clip_image-19200

图7:客户端与HDFS的交互

Datanode中包含DataXceiverServer。DataXceiverServer是一个socket server,负责接收client发起的socket连接。DataXceiverServer接收到一个socket连接后,启动一个线程DataXceiver,由DataXceiver具体负责该scoket的处理。DataXceiver从client读取client想要进行操作的操作码。如果操作码是OP_READ_BLOCK,则DataXceiver负责读取该block,并将其发送给client。

读取block数据传输格式

Client为了从Datanode读取block,按照一定的格式向Datanode发送指令及其他一些数据,具体的格式如下:

version

operator

blockid

generationStamp

startOffset

length

clientName

accessToken

图8:client与Datanode之间传输命令的格式

Ø operator:byte Client所需要的操作,读取一个block、写入一个block等等

Ø version:short Client所需要的数据与Datanode所提供数据的版本是否一致

Ø blockId:long 所要读取block的blockId

Ø generationStamp:long 所需要读取block的generationStamp

Ø startOffset:long 读取block的的起始位置

Ø length:long 读取block的长度

Ø clientName:String Client的名字

Ø accessToken:Token Client提供的验证信息,用户名密码等

读取过程

DataXceiver首先按照上面的数据格式依次读取各变量。读取完成后,如果需要进行访问控制,则根据Client提供的accessToken进行验证。验证通过后,产生一个BlockSender实例blockSender,通过blockSender进行数据的读取与传输。这里有一个需要注意的地方,HDFS在保存文件的时候有几个非常重要的概念:一个文件由多个block构成。HDFS在进行block读写的时候是以packet为单位进行的。每一个packet由若干个chunk组成。Chunk是进行数据校验的基本单位,对每一个chunk生成一个校验和并将校验和进行存储(在默认情况下一个chunk的大小是512byte,生成的校验和是4byte)。在读取一个block的时候,数据传输的基本单位是packet,每个packet由若干个chunk组成。

为了读取一个block,首先从block的.meta文件中读取block的版本号、校验类型等,并生成相应的校验工具类。Meta文件的格式如下所示:

version

Checksum type

bytesPerChecksum

Checksum

checksum

….

图9:.mete数据的数据格式

Ø version:short 所保存block的版本号

Ø checksumType:int 校验码的类型,要么没有校验码,要么是CRC32校验码(CHECKSUM_NULL、CHECKSUM_CRC32)

Ø bytesPerChecksum:int 表示这么校验和是由多少byte的源数据计算而来

Ø checksum:每一个chunk的校验和。

这里有一个需要注意的地方,当读取数据的时候读取的起始位置必行是一个chunk的起始位置,如果client读取的起始位置不是chunk的起始位置,那么必须回退到chunk的起始位置开始读取。确定好位置后,Datanode以packet为单位依次发送block的数据,packet的具体格式如下图所示:

pachetLen

offset

sequenceNum

islastPacket

dataLen

checksum

data

图10:Packet数据格式

Ø packetLen:int packet的长度,包括数据、数据的校验等等

Ø offset:long packet在block中的偏移量

Ø sequenceNum:long 该packet在这次block读取时的序号

Ø isLastPacket:byte packet是否是最后一个

Ø dataLen:int 该packet所包含block数据的长度,纯数据不包括校验和其他

Ø checksum:该packet每一个chunk的校验和,有多少个chunk就有多少个校验和

Ø data:该packet所包含的block数据

抱歉!评论已关闭.