HDFS读写流程
写数据流程
写数据流程如图所示:

例如客户端将文件/jdk-8u212-linux-x64.tar.gz上传至HDFS具体分为如下几步:
客户端创建
HDFS客户端对象,然后利用客户端对象向NameNode请求上传文件/jdk-8u212-linux-x64.tar.gz。NameNode接收到客户端请求后,首先回去做相关校验,例如:检查用户是否有权限写入文件、该文件是否在HDFS中存在,如果存在了用户是否选择覆盖等等。当所有校验都通过了之后,给客户端响应可以上传文件。如果校验没通过则报错。客户端接收到
NameNode的响应之后,会向NameNode请求上传第一个Block,期望NameNode返回用于存储数据的DataNode。当
NameNode接收到客户端请求Block后,NameNode会按照一定的原则(详此文章1.2节),选择相应副本数量的DataNode返还给客户端。客户端接收到
NameNode的信息后,开始创建输出流对象与一个DataNode建立传输通道,不同的DataNode相互之间建立传输通道,直到所有的DataNode连接在一起。开始上传数据,例如客户端将数据传输给
DataNode1然后DataNode1同时把数据传输到DataNode2,DataNode2再将数据传输到DataNode3,这样就完成成了3个副本的数据传输。注意:客户端只在
HDFS上上传了一次数据,副本的备份是HDFS在DataNode之间自动进行的。完成数据传输后,关闭相关资源。
网络拓扑节点距离计算
当集群中的服务器有多个,甚至机架有多个时,在存储数据时DataNode节点的选择与节点的所处位置是有关系的,并且不同的节点之间相互通信时,效率也是不一样的。
比如,针对下图的集群拓扑图:

不同的节点之间进行相互通信时,距离和效率是不一样的。
我们按照以下的方式计算节点之间的距离:
节点与自身通信:0
节点与同机架的其他节点之间进行通信:2
例如:集群
d1中机架r1下的n-0节点与集群d1中机架r1的n-1节点节点与统计群中其他机架的节点进行通信:4
例如:集群
d1中机架r1下的n-0节点与集群d1中机架r2的n-0节点节点与不同集群下的节点进行通信:6
例如:集群
d1中机架r1下的n-0节点与集群d2中机架r4的n-0节点
副本选择
有了节点之间的距离计算方法后,在向HDFS中存储数据时选择存储副本的DataNode节点有什么依据呢?
在考虑改问题时,我们往往需要考虑两方面的因素:
- 速度/效率。
- 安全性
为了更详细的探讨该内容,假如我们有以下的一个集群。

当客户端向NameNode中发送存储数据的请求时,如果有三个副本,应该如何选择。
首先,第一个副本会在这9个节点中随机选择。必须选到了机架r1下的n-0节点。

那么,第二个节点应该如何选取呢?
通过前面的1.1节我们知道,第二个节点中的数据是有第一个节点传输过来的,那么现在如果考虑传输效率的话应该在机架r1下的n-1和n-2这两个节点中去选择;如果考虑安全性的话肯定是把数据存储到其他机架下的节点更安全,这样即使机架r1挂了,数据仍然有备份。显然这两个是不可兼得的,必须选择一个。在Hadoop2.x下考虑的是传输效率;而在Hadoop3.x下考虑的是安全性。我们这里使用的是Hadoop3.x故应该优先考虑安全性。
所以第二个节点会在机架r2和机架r3下选取一个节点。比如选取到了机架r2下的n-0节点。

最后一个副本的数据又是由第二个节点传输过来的,这个时候就没必要再去考虑安全性了,应该优先考虑传输效率了,所以第三个节点应该在机架r2中的n-1和n-2节点中选取一个,例如这里选取到了n-1。

这样在此次写数据操作中,选取的存储3个副本的节点为机架r1下的n-0,机架r2下的n-0和n-1。
读数据流程
读数据流程如图所示:

例如将HDFS中的/jdk-8u212-linux-x64.tar.gz文件下载到本地具体分为以下几个步骤:
客户端创建
HDFS客户端对象,然后利用客户端对象向NameNode请求下载文件/jdk-8u212-linux-x64.tar.gz。NameNode接收到客户端的请求后,开始进行请求校验,用户是否有读取权限;元数据中是否存在该文件。如果校验通过后,NameNode会给客户端一个响应,告知客户端你要的数据被分为了几个块,分别在哪几个DataNode里面。接下来客户端接收到
NameNode的响应后,首先在本地路径下创建一个对应文件名的空文件,根据响应中的DataNode去获取相应的数据,然后依次去请求每一个块的数据,写入到本地的文件中。这里有同学可能会存在疑问,块数据在多个
DataNode中都存在,去哪个DataNode中去取数据呢?由于用户是从集群外部对集群进行访问,所以到任何一个DataNode节点的距离都一样,故不存在DataNode的选取问题,随机选取一个即可。
NameNode和SecondaryNameNode
NN和2NN的工作机制
NN和2NN的协同工作机制如下图所示:

要想看懂这个图,首先需要思考一个问题:NameNode中的元数据是存储在哪里的?
我们做个假设,如果存储在NameNode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。因此,元数据需要存放在内存中,但如果只存在内存中,一旦断电,元数据丢失,整个集群就无法工作了,因此元数据需要同时在内存和硬盘存在。
那么在NameNode中是如何处理这个问题使得在内存和硬盘中同时存在元数据,相互补充协同工作。
为了处理这个问题,在NameNode的工作机制中创建了两类文件FsImage和Edits。
FSImage文件:负责存储历史的元数据。
Edits文件:用来存储对HDFS的所有数据操作命令。
Edits文件又分为两种:
- 普通的
Edits文件,用于存储历史的文件操作命令。- 文件名中包含
Edits_inprogress_的Edits文件,该文件表示当前进行相关操作时,正在使用的Edits文件。
客户端向NameNode发送请求写入数据到HDFS,整个NameNode的处理过程如下:
将写入数据的命令存储在
NameNode中的edits_inprogress_XXX文件中,然后在NameNode节点中运行该命令,这个时候在NameNode的内存中就存在了该文件的元数据。在这里,可能有的同学觉得,都把数据放到内存中去了,直接再从内存中存储到硬盘中不就行了。事情往往没有这么简单,
NameNode负责的工作已经很多了,如果它还要负责把数据从内存中存储到硬盘中,对于该节点的要求就太高了。这个时候SecondaryNameNode就是专门用来处理这个问题的,将内存中的数据持久化存储到硬盘中。并且SecondaaryNameNode往往和NameNode不在一个节点下,不在一个节点中,是无法直接使用NameNode中的内存数据的。这个时候在硬盘中是没有该元数据的,直到执行
CheckPoint操作时,才会将元数据存储到硬盘中。CheckPoint操作触发条件:- 时间超过1小时。
edits_inprogress_XXX中操作次数达到100万次。
CheckPoint操作流程:- 将
Edits_inprogress_001文件名中的inprogress删除,重命名为Edits_001,然后重新创建一个新的Edits_inprogress_002文件用于后续文件操作命令的存储。 - 将
Edits_001拷贝到2NN节点中,然后2NN节点去执行Edits_001中的相关操作,使得在2NN的内存中生成相关的元数据,这个时候2NN就可以将数据存储到它自己的硬盘下了,生成一个FSImage文件,然后再将该FSImage文件传输到NameNode的硬盘中即可完成对元数据的持久化存储。
还有一个疑问,既然在
2NN节点下有所有的FSImage文件,那么是不是2NN可以完全替代NameNode的功能进行工作呢?答案是否定的。在
2NN中只有NameNode中的一部分数据,有一个数据在2NN中是永远都没有的,就是在Edits_inprogress_XXX下的操作。因为Edits_inprogress_XXX下的操作命令,在为做CheckPoint操作时,是不会进行持久化存储的,也就不会在2NN中生成。正是因为这个原因,集群在每次重新启动
NameNode时都会去利用已有的元数据文件FSImage和Edits_inprogress_中的操作进行元数据的恢复,并且此时也会重新生成新的Edits_inprogress_文件。这个我们是可以验证的。
首先记录一下当前
NameNode下的Edits文件情况如下:
这个时候重新
NameNode。1
2hdfs --daemon stop namenode
hdfs --daemon start namenode

这个时候
edits_inprogress_0000000000000000355就被修改成了edits_0000000000000000355-0000000000000000355,同时生成了一个新的edits_inprogress_0000000000000000356。
FSImage文件和Edits文件解析
为了探究这两个文件的内容,我们找了两个文件拷贝到家目录,如下所示。

使用以下命令可以将FSImage文件转化为XML文件
1 | hdfs oiv -p XML -i fsimage_0000000000000000352 -o fsimage.xml |


在该文件中存储了HDFS中的元数据信息,包含文件的权限和文件类型。还有文件夹与文件之间的包含关系。
但是在该文件中并没有存储每个文件的副本在哪些节点中,因为这个操作是由DataNode节点向NameNode汇报的。
这个很好理解,为什么要这样去设置。如果
NameNode中存储了这个信息,那么用户在去获取某个DataNode下的Block时,万一该DataNode挂了,这个时候就会对用户获取数据产生影响。如果是在集群开启时,由DataNode向NameNode汇报就不会出现这个问题,节点如果挂了就没有该节点的副本信息给到用户。
使用以下命令可以将Edits文件转化为XML文件
1 | hdfs oev -p XML -i edits_0000000000000000212-0000000000000000252 -o edits.xml |

看着这个文件信息后,有的同学可能对这个文件有点疑问,有些操作并不是我们做的,为什么也被记录下来了。
原因是因为,在你眼里只是一个简单的操作,集群在执行起来的时候,可能会有很多操作之间相互配合达到你的要求。
这中设计原则,就像是一个饭店在炒菜时,在你眼里只是点了一个菜,但是饭店会做很多操作,例如洗菜、切菜、炒菜、上菜等等,最后才能达到你的要求。
CheckPoint时间设置
通常情况下,SecondaryNameNode每隔一小时执行一次。
1 | [hdfs-default.xml] |
一分钟检查一次操作次数,当操作次数达到1百万时,SecondaryNameNode执行一次。
1 | <property> |
DataNode
DataNode工作机制
工作机制图如下所示:

DataNode在启动时首先会在NameNode中进行注册,告知NameNode我可以正常提供服务。为了保证
NameNode中元数据的可靠性,DataNode会每隔6小时上报自己的块信息。为了判断
DataNode服务在集群运行的过程中没有挂掉,DataNode会每隔3s向NameNode发送依次心跳信号,告知NameNode自己是正常工作的。不是超过3s
NameNode没有收到DataNode的心跳信号就判断DataNode挂掉了,而是超过10分钟+30s才会判断DataNode挂掉。心跳信号在
Web页面中可以看到。
例如,此时我们把节点
hadoop103中的DataNode服务关闭。1
hdfs --daemon stop datanode

这个时候上一次发送心跳信号的时间就会超过
3s,但是还没有直接判定hadoop103下的DataNode服务挂掉。(前面还是绿色的√)只需要重新启动
hadoop103下的DataNode服务即可恢复正常。1
hdfs --daemon start datanode

数据完整性
所谓数据完整性,是HDFS在上传和下载时,为了保证文件在传输的过程中不损坏和不被其他人篡改而产生的一种对文件进行校验的操作。
例如上传过程中,首先在上传之前使用不可逆的算法(例如:md5、sha256等等)计算文件的一个特殊编码,然后在等到文件上传到HDFS后再次使用相同的算法计算一下上传后的文件编码,对比这两次计算出来的编码看是否一致,如果一致才说明文件是完整的。
掉线时参数设置
前面我们说的,当DataNode心跳信号停止时,超过10分钟+30s才会判断DataNode挂掉。
这里的结果是可以计算的,计算公式如下:
需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。
1 | <property> |