Hdfs简介
一般而言,Hdfs是由一个NameNode节点和若干个DataNode节点组成(非高可用,高可用还有一个SecondNameNode)。
- NameNode:管理分布式文件系统的元数据,这些元数据是一些诸如描述文件的存储路径以及block具体在哪些DataNode上的具体位置等;
- DataNode:DataNode节点用来保存文件数据块(block),它只负责接受存储、查询发送文件,不负责文件的切块;
- 文件切割:文件的切割默认是以128M为标准(该数值可通过参数设定),小于该标准的文件不切割,超过该标准的文件会被切成若干个block块;一个文件切割出来的多个block在存储时,每个block的存储地址都由NameNode决定,当一个block存储完毕后,下一个block要重新向NameNode申请存储地址;这些被切割出来的block块会根据replication(复制因子)复制出多个副本,且副本存放在不同的DataNode上;
- 汇报机制:DataNode会定时向NameNode汇报自身的block信息,NameNode会负责保存这些DataNode汇报上来的元数据,并保存文件的副本数量,一旦副本数量不满足复制因子规定的数目,则NameNode会指定一台DataNode(没有改副本的节点)从有该副本的DataNode上拷贝(一个副本ID在一台DataNode上有且仅有一份);
- 容错机制:如果存在DataNode宕机,那么当集群中的DataNode出发定时向NameNode汇报时,NameNode就会得知哪些机器宕机了,统计完副本分布情况之后,NameNode就知道哪些副本少了,于是NameNode就负责寻找一个没有这个副本节点从有这个副本的DataNode上拷贝一份(一个副本ID在一台DataNode上有且仅有一份);
- 客户端请求方式:Hdfs的内部工作机制对客户端保持透明,客户端请求访问Hdfs都是通过NameNode申请实现的。
写数据处理流程
假设有一台NameNode和4台DataNode(非HA),我们的文件上传客户端可以在任何地方,前提是客户端能够与NameNode、DataNode连接。
一个大文件现在客户端进行分割,然后向NameNode请求上传block到指定文件夹,比如如下上传命令:
hadoop dfs -put 9F83F0668.mp4 /test/9F83F0668/
NameNode首先会看上传目标文件夹,然后它会去查询内部元数据该路径存在不存在,跟据查询结果会存在以下几种情况:
- 路径“/test/9F83F0668/”不存在
创建路径“/test/9F83F0668/”,符合文件上传条件。 - 路径“/test/9F83F0668/”存在,该路径下存在文件“9F83F0668.mp4”
返回文件已存在,不符合文件上传条件(此举是为了方式覆盖文件)。 - 路径“/test/9F83F0668/”存在,该路径下不存在文件“9F83F0668.mp4”
符合文件上传条件。
如果符合文件上传条件,就意味着我们的客户端可以向Hdfs上传文件,这个时候客户端还不知往哪儿传送数据,客户端通过rpc请求上传一个block,NameNode分配三台DataNode(假设我们的复制因子为3)。
这里交代一下NameNode分配存储节点DataNode的机制:
- 第一台DataNode
第一台DataNode的分配规则是考虑节点的存储空间以及与NameNode的距离,优先选择存储空间大且离NameNode近的节点(因为第一台DataNode要实时向NameNode汇报,所以第一台择优分配); - 第二台DataNode
第二台DataNode的分配规则是优先选择存储空间大且离NameNode远的节点(处于安全考虑); - 第三台DataNode
第三台DataNode的选择标准与第一台DataNode一致,优先选择存储空间大且离NameNode近的节点;
当客户端拿到了存储对象(即NameNode传递过来的信息),解析一下就明白了上传地址在哪儿,于是它便尝试与DataNode1建立流(通道),执行以下流程:- 客户端首先向DataNode1发送请求建立block传输通道channel,同时告诉DataNode1客户端还要发送block至DataNode2、DataNode3处;
- DataNode1接到DataNode1的请求,知道还要向DataNode2、DataNode3传输数据,DataNode1向DataNode2发送请求建立channel,并告诉DataNode2还要向DataNode3传输block;
- DataNode2接到DataNode1的请求后,知道还要向DataNode3传输block,于是向DataNode3发送建立channel请求;
4)DataNode3接到DataNode2的请求之后,知道自己是最后一个,没有其他要存储的节点了,于是返回应答给DataNode2,表示DataNode2与DataNode3之间的通道建立;
5)同理DataNode2返回应答给DataNode1,表示DataNode1与DataNode2之间的通道建立;
6)DataNode1再返回应答给客户端,至此整个文件传输通道建立完成,开始写数据。
客户端与DataNode1之间建立的连接管道名为pipeline,值得注意的是这个大小为“128M”的block块在往DataNode1上写的时候不是一次性传输的,而是又分成一个个packet数据包进行传输(默认64K),DataNode1每收到一个64k大小的packet包都要先验证其正确性,如果没有传输错误那就写到自己的文件目录下,在校验packet正确性和本地的过程之间还存在一个缓冲区,这个缓冲区向DataNode1自身的文件系统写校验正确的packet,同时向DataNode2发送该packet,DataNode2上面发生的事情和DataNode1上一样(也校验、进入缓冲区),DataNode3同理(校验、进入缓冲区,不发送了直接写入)。
宏观上看,客户端向DataNode1、DataNode2、DataNode3传输block的过程几乎是同步的,只会有一两个packet的差别。
- 那么传输过程如果校验失败了怎么办?
每一个节点收到前驱传来的packet包之后,都会向它的前驱应答,前驱再向前驱应答,以此类推,如果packet在DataNode1就失败了,那么客户端会重新向NameNode申请该block的存储地址(NameNode的小本本也会记下这几台性能不是很可靠的机器,下次不优先分配),如果packet在DataNode2或者DataNode3上失败,但是DataNode1传输成功这也不打紧,因为有NameNode的存在,NameNode会异步地从DataNode1上复制出错的packet到相应失败的节点上(NameNode依旧会在小本本上记下这些不可靠的机器,下次不优先分配)。
等到一整个block传输成功以后,下一个block的传输过程一样是先请求NameNode分配存储地址(重新分配的地址可能是一样的,但是上一个block传输建立的pipeline不能够复用,必须重新请求建立),如此往复完成整个大文件“9F83F0668.mp4”的存储,整个过程会被NameNode记录下来,记录的数据就称为元数据。
注意:DataNode在校验packet的传输正确性的时候并不是以64k为单位校验一次,而是以一个chunk(512byte)为单位校验一次。
读数据处理流程
假设有一台NameNode和4台DataNode(非HA),我们的读取数据客户端可以在任何地方,前提是客户端能够与NameNode、DataNode连接。
客户端向Hdfs发送请求下载路径“/test/9F83F0668/”下的9F83F0668.mp4,保存到本地的“/home/jack_roy/”路径下,命令如下:
hadoop dfs -get /test/9F83F0668/9F83F0668.mp4 /home/jack_roy/
==假设在Hdfs上9F83F0668.mp4的副分布在DataNode1、DataNode2、DataNode3、DataNode1上,我们的下载请求连接这四个节点,==现客户端向Hdfs读数据(下载文件)流程如下:
- 首先,客户端先去找NameNode,向NameNode发送rpc请求,请求下载“/test/9F83F0668/9F83F0668.mp4 ”;
- NameNode内维护的有一块元数据区域,该区域记录了以前存储在整个分布式文件系统里的文件的相关元数据信息,NameNode找到“/test/9F83F0668/9F83F0668.mp4 ”分成了哪几个block块、各个block块在相应节点上的存储路径等元数据信息;
- NameNode将元数据信息发送给客户端,客户端得知自己要下载的文件的第一个block块在哪些DatNode上,寻找条件最优的节点(对于“hadoop dfs -put ”命令来说,就是选择与客户端距离最近的节点,比如block在本地有,则优先从本地取该block)请求建立channel(管道),这里我们假设第一个block块的最优下载节点是DataNode1;
- DataNode1收到请求以后,找到自己文本区域下存储的block块,然后初始化一个FileinputStream来读取该block块,同时它也相应客户端建立管道(NioSocket通信);
- DataNode1在做好准备后,开始向管道输入流,该输入流通过SocketOutpuStream(端口输出流)实现;
- 客户端通过端口输入流SocketInpuStream接受,再由FileOutputStream写入到本地建立的文件目录中,来存储该block块(本地文件夹若不存在,则在NioSocket建立成功时建立);
- 待第一块block传输完成之后,再去寻找第二块block的最优存储节点,过程同理;
- 剩余的block块传输过程同理,直至将“/test/9F83F0668/9F83F0668.mp4 ”完全下载至本地。
注意:以上传输过程依旧是以packet(64K)为单位传输,客户端本地缓存,然后写入目标文件。
后记
对Hdfs读、写数据处理流程的理解交代完了,后面再交代NameNode的元数据机制,个人理解恐有失偏颇,欢迎留言指正。