NameNode接收到crete请求后,会进行合法性校验,比如是否已存在想通文件,Client是否有相关权限。如果校验通过,NameNode会为新文件创建一个记录,并返回一些可用的DataNode。否则客户端抛出一个IOException
DistributedFileSystem 会返回一个FSDataOutputStream个Client,与读取数据类似,FSDataOutputStream封装了一个DFSOutputStream,负责NameNode与DataNode之间交互。
Client调用write()
DFSOutputStream会将数据切分为一个一个packets,并且将之放入一个内部队列(data queue),这个队列会被DataStreamer消费,DataStreamer通过选择一组合合适DataNodes来写入副本,并请求NameNode分配新的数据块。与此同时,DFSOutputStream还维护一个等待DataNode确认的内部包队列(ack queue)
这些DataNodes会被组成一个管道(假设备份数量为3)
一旦pipeline建立,DataStreamer 将data queue中存储的packet流式传入管道的第一个DataNode,第一个DataNode存储Packet并将之转发到管道中的第二个DataNode,同理,从第二个DataNode转发到管道中的第三个DataNode。
当所一个packet已经被管道中所有的DataNode确认后,该packet会从ack queue移除。
当Client完成数据写入,调用close(),此操作将所有剩余的数据包刷新到DataNode管道,等待NameNode返回文件写入完成的确认信息。
NameNode已经知道文件是由哪个块组成的(因为是DataStreamer请求NameNode分配Block的),因此,它只需要等待Block被最小限度地复制,最后返回成功。
如果在写入的过程中国发生了错误,会采取以下的操作:
关闭管道,并将所有在ack queue中的packets加到 data queue的前面,避免故障节点下游的DataNode发生数据丢失。
给该Block正常DataNode一个新的标记,将之告知NameNode,以便后续故障节点在恢复后能删除已写入的部分数据。
将故障节点从管道中移除,剩下的两个正常DataNodes重新组成管道,剩余的数据写入正常的DataNodes。
当NameNode发现备份不够的时候,它会在另一个DataNode上创建一个副本补全,随后该Blcok将被视为正常
针对多个DataNode出现故障的情况,我们只要设置 dfs.NameNode.replication.min的副本数(默认为1),Block将跨集群异步复制,直到达到其目标复制因子( dfs.replication,默认为3)为止.
通俗易懂的理解上面的读写过程可以做一个类比,
NameNode 可以看做是一个仓库管理员;
DataNode 可以看作是仓库;
管理员负责管理商品,记录每个商品所在的仓库;
仓库负责存储商品,同时定期想管理员上报自己仓库中存储的商品;
此处的商品可以粗略的理解为我们的数据。
管理员只能有一个,而仓库可以有多个。
当我们需要出库的时候,得先去找管理员,在管理员处取得商品所在仓库的信息;
我们拿着这个信息到对应仓库提取我们需要的货物。
当我们需要入库的时候,也需要找管理员,核对权限后告诉我们那些仓库可以存储;
我们根据管理员提供的仓库信息,将商品入库到对应的仓库。
上面是关于Hdfs读写流程介绍,虽然我分了简单和详细,但是实际的读写比这个过程复杂得多。
比如如何切块?
为何小于块大小的文件按照实际大小存储?
备份是如何实现的?
Block的结构等等。
这些内容会在后续的源码部分详细解答。
此外,有人也许发现了,前文大数据系列1:一文初识Hdfs中Hdfs架构的介绍和本文读写的流程的介绍中,存在一个问题。
就是NameNode的单点故障问题。虽然之前有SecondaryNameNode 辅助NameNode合并fsiamge和edits,但是这个还是无法解决NameNode单点故障的问题。
很多人听过HA(High Availability) 即高可用,误以为高可用就是SecondaryNameNode,其实并不是。