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> |