本节书摘来自华章计算机《深入理解大数据:大数据处理与编程实践》一书中的第3章,第3.3节,作者 主 编:黄宜华(南京大学)副主编:苗凯翔(英特尔公司),更多章节内容可以访问云栖社区“华章计算机”公众号查看。
3.3 HDFS文件存储组织与读写
作为一个分布式文件系统,HDFS内部的数据与文件存储机制、读写过程与普通的本地文件系统有较大的差别。下面具体介绍HDFS中数据的存储组织和读写过程。
3.3.1 文件数据的存储组织
如前所述,HDFS中最主要的部分就是NameNode和DataNode。NameNode存储了所有文件元数据、文件与数据块的映射关系,以及文件属性等核心数据,DataNode则存储了具体的数据块。那么,在HDFS中,具体的文件存储组织结构是怎样的呢?
1.?NameNode目录结构
图3-3是NameNode的目录结构和内容。NameNode借助本地文件系统来保存数据,保存的文件夹位置由配置选项{dfs.name.dir}决定(未配置此选项,则为hadoop安装目录下的/tmp/dfs/name),所以,这里我们以${dfs.name.dir}代表NameNode节点管理的根目录。目录下的文件和子目录则以${dfs.name.dir}/file和${dfs.name.dir}/subdir的格式表示。
在NameNode的${dfs.name.dir}之下有3个文件夹和1个文件:
1)current目录:主要包含如下的内容和结构:
a)文件VERSION:保存了当前运行的HDFS版本信息。
b)FsImage:是整个系统的空间镜像文件。
c)Edit:EditLog编辑日志。
d)Fstime:上一次检查点的时间。
2)previous.checkpoint目录:和current内容结构一致,不同之处在于,此目录保存的是上一次检查点的内容。
3)image目录:旧版本(版本<0.13)的FsImage存储位置。
4)in_use.lock:NameNode锁,只有在NameNode有效(启动并且能和DataNode正常交互)时存在;不满足上述情况时,该文件不存在。这一文件具有“锁”的功能,可以防止多个NameNode共享同一目录(如果一个机器上只有一个NameNode,这也是最常见的情况,那么这个文件基本不需要)。
2.?DataNode目录结构
图3-4是DataNode的目录结构和内容。DataNode借助本地文件系统来保存数据,一般情况下,保存的文件夹位置由配置选项{dfs.data.dir}决定(未配置此选项,则为hadoop安装目录下的/tmp/dfs/data)。所以,这里我们以${dfs.data.dir}代表DataNode节点管理的数据目录的根目录,目录下的文件和子目录则以${dfs.data.dir}/file和${dfs.data.dir}/subdir的格式表示。
图3-4 HDFS DataNode目录结构
一般来说,在${dfs.data.dir}之下有4个子目录和2个文件:
1)current目录:已经成功写入的数据块,以及一些系统需要的文件。包括以下内容:
a)文件VERSION:保存了当前运行的HDFS版本信息。
b)Blk_XXXXX和Blk_XXXXX.Meta:分别是数据块和数据块对应的元数据(比如校验信息等)。
c)subdirXX:当同一目录下文件数超过一定限制(比如64)时,会新建一个subdir目录,保存多出来的数据块和元数据;这样可以保证同一目录下目录+文件数不会太多,可以提高搜索效率。
2)tmp目录和blocksBeingWritten目录:正在写入的数据块,tmp目录保存的是用户操作引发的写入操作对应的数据块,blocksBeingWritten目录是HDFS系统内部副本创建时(当出现副本错误或者数量不够等情况时)引发的写入操作对应的数据块。
3)detach目录:用于DataNode升级。
4)storage文件:由于旧版本(版本<0.13)的存储目录是storage,因此如果在新版本的DataNode中启动旧版的HDFS,会因为无法打开storage目录而启动失败,这样可以防止因版本不同带来的风险。
5)in_use.lock文件:DataNode锁,只有在DataNode有效(启动并且能和NameNode正常交互)时存在;不满足上述情况时,该文件不存在。这一文件具有“锁”的功能,可以防止多个DataNode共享同一目录(如果一个机器上只有一个DataNode,这也是最常见的情况,那么这个文件基本不需要)。
3.?CheckPointNode目录结构
图3-5是CheckPointNode的目录结构和内容。CheckPointNode和旧版本的SecondaryName
Node作用类似,所以目录结构也十分相近。
图3-5 HDFS CheckPointNode目录结构
CheckPointNode借助本地文件系统来保存数据,一般情况下,保存的文件夹位置由配置选项{dfs.checkpoint.dir}决定(未配置此选项,则为hadoop安装目录下的/tmp/dfs/namesecondary)。所以,这里我们以${dfs.checkpoint.dir}代表CheckPointNode节点管理的数据目录的根目录,目录下的文件和子目录则以${dfs.checkpoint.dir}和file,${dfs.checkpoint.dir}/subdir的格式表示。
CheckPointNode目录下的文件和NameNode目录下的同名文件作用基本一致,不同之处在于CheckPointNode保存的是自上一个检查点之后的临时镜像和日志。
3.3.2 数据的读写过程
数据的读写过程与数据的存储是紧密相关的,以下介绍HDFS数据的读写过程。
1.?数据读取过程
一般的文件读取操作包括open、read、close等,具体可参见3.5节的HDFS编程接口介绍。这里介绍一下客户端连续调用open、read、close时,HDFS内部的整个执行过程。图3-6可以帮助我们更好地理解这个过程。
图3-6 HDFS数据读取过程
以下是客户端读取数据的过程,其中1、3、6步由客户端发起:
客户端首先要获取FileSystem的一个实例,这里就是HDFS对应的实例。
1)首先,客户端调用FileSystem实例的open方法,获得这个文件对应的输入流,在HDFS中就是DFSInputStream。
2)构造第1步中的输入流DFSInputStream时,通过RPC远程调用NameNode可以获得NameNode中此文件对应的数据块保存位置,包括这个文件的副本的保存位置(主要是各DataNode的地址)。注意,在输入流中会按照网络拓扑结构,根据与客户端距离对DataNode进行简单排序。
3~4)获得此输入流之后,客户端调用read方法读取数据。输入流DFSInputStream会根据前面的排序结果,选择最近的DataNode建立连接并读取数据。如果客户端和其中一个DataNode位于同一机器(比如MapReduce过程中的mapper和reducer),那么就会直接从本地读取数据。
5)如果已到达数据块末端,那么关闭与这个DataNode的连接,然后重新查找下一个数据块。
不断执行第2~5步直到数据全部读完,然后调用close。
6)客户端调用close,关闭输入流DFSInputStream。
另外,如果DFSInputStream和DataNode通信时出现错误,或者是数据校验出错,那么DFSInputStream就会重新选择DataNode传输数据。
2.?数据写入过程
一般的文件写入操作不外乎create、write、close几种,具体可参见3.5节的HDFS编程接口介绍。这里介绍一下客户端连续调用create、write、close时,HDFS内部的整个执行过程,见图3-7。
以下是客户端写入数据的过程,其中1、3、6步由客户端发起:
客户端首先要获取FileSystem的一个实例,这里就是HDFS对应的实例。
1~2)客户端调用FileSystem实例的create方法,创建文件。NameNode通过一些检查,比如文件是否存在,客户端是否拥有创建权限等;通过检查之后,在NameNode添加文件信息。注意,因为此时文件没有数据,所以NameNode上也没有文件数据块的信息。创建结束之后,HDFS会返回一个输出流DFSDataOutputStream给客户端。
3)客户端调用输出流DFSDataOutputStream的write方法向HDFS中对应的文件写入数据。数据首先会被分包,这些分包会写入一个输出流的内部队列Data队列中,接收完数据分包,输出流DFSDataOutputStream会向NameNode申请保存文件和副本数据块的若干个DataNode,这若干个DataNode会形成一个数据传输管道。
4)DFSDataOutputStream会(根据网络拓扑结构排序)将数据传输给距离上最短的DataNode,这个DataNode接收到数据包之后会传给下一个DataNode。数据在各DataNode之间通过管道流动,而不是全部由输出流分发,这样可以减少传输开销。
5)因为各DataNode位于不同机器上,数据需要通过网络发送,所以,为了保证所有DataNode的数据都是准确的,接收到数据的DataNode要向发送者发送确认包(ACK Packet)。对于某个数据块,只有当DFSDataOutputStream收到了所有DataNode的正确ACK,才能确认传输结束。DFSDataOutputStream内部专门维护了一个等待ACK队列,这一队列保存已经进入管道传输数据、但是并未被完全确认的数据包。
不断执行第3~5步直到数据全部写完,客户端调用close关闭文件。
6)客户端调用close方法,DFSDataInputStream继续等待直到所有数据写入完毕并被确认,调用complete方法通知NameNode文件写入完成。
7)NameNode接收到complete消息之后,等待相应数量的副本写入完毕后,告知客户端即可。
在传输数据的过程中,如果发现某个DataNode失效(未联通,ACK超时),那么HDFS执行如下操作:
1)关闭数据传输的管道。
2)将等待ACK队列中的数据放到Data队列的头部。
3)更新正常DataNode中所有数据块的版本;当失效的DataNode重启之后,之前的数据块会因为版本不对而被清除。
4)在传输管道中删除失效的DataNode,重新建立管道并发送数据包。
以上就是HDFS中数据读写的大致过程。