从HDFS的应用层面来看,咱们能够很是容易的使用其API来操做HDFS,实现目录的建立、删除,文件的上传下载、删除、追加(Hadoop2.x版本之后开始支持)等功能。然而仅仅局限与代码层面是不够的,了解其实现的具体细节和过程是颇有必要的,本文笔者给你们从如下几个方面进行剖析:缓存
下面开始今天的内容分享。网络
在HDFS上实现文件的建立,该过程并不复杂,Client到NameNode的相关操做,如:修改文件名,建立文件目录或是子目录等,而这些操做只涉及Client和NameNode的交互,过程以下图所示:并发
咱们很熟悉,在咱们使用Java 的API去操做HDFS时,咱们会在Client端经过调用HDFS的FileSystem实例,去完成相应的功能,这里咱们不讨论FileSystem和DistributedFileSystem和关系,留在之后在阅读这部分源码的时候在去细说。而咱们知道,在使用API实现建立这一功能时是很是方便的,代码以下所示:函数
public static void mkdir(String remotePath) throws IOException { FileSystem fs = FileSystem.get(conf); Path path = new Path(remotePath); fs.create(path); fs.close(); }
在代码层面上,咱们只须要获取操做HDFS的实例便可,调用其建立方法去实现目录的建立。可是,其中的实现细节和相关步骤,咱们是须要清楚的。在咱们使用HDFS的FileSystem实例时,DistributedFileSystem对象会经过IPC协议调用NameNode上的mkdir()方法,让NameNode执行具体的建立操做,在其指定的位置上建立新的目录,同时记录该操做并持久化操做记录到日志当中,待方法执行成功后,mkdir()会返回true表示建立成功来结束建立过程。在此期间,Client和NameNode不须要和DataNode进行数据交互。oop
在执行建立的过程中不涉及DataNode的数据交互,而在执行一些较为复杂的操做时,如删除,读、写操做时,须要DataNode来配合完成对应的工做。下面以删除HDFS的文件为突破口,来给你们展开介绍。删除流程图以下所示:学习
在使用API操做删除功能时,我使用如下代码,输入要删除的目录地址,而后就发现HDFS上咱们所指定的删除目录就被删除了,而然其中的删除细节和过程却并不必定清楚,删除代码以下所示:spa
public static void rmr(String remotePath) throws IOException { FileSystem fs = FileSystem.get(conf); Path path = new Path(remotePath); boolean status = fs.delete(path, true); LOG.info("Del status is [" + status + "]"); fs.close(); }
经过阅读这部分删除的API实现代码,代码很简单,调用删除的方法便可完成删除功能。但它是如何完成删除的,下面就为你们这剖析一下这部份内容。日志
在NameNode执行delete()方法时,它只是标记即将要删除的Block(操做删除的相关记录是被记录并持久化到日志当中的,后续的相关HDFS操做都会有此记录,便再也不提醒),NameNode是被动服务的,它不会主动去联系保存这些数据的Block的DataNode来当即执行删除。而咱们能够从上图中发现,在DataNode向NameNode发送心跳时,在心跳的响应中,NameNode会经过DataNodeCommand来命令DataNode执行删除操做,去删除对应的Block。而在删除时,须要注意,整个过程中,NameNode不会主动去向DataNode发送IPC调用,DataNode须要完成数据删除,都是经过DataNode发送心跳获得NameNode的响应,获取DataNodeCommand的执行命令。code
在读取HDFS上的文件时,Client、NameNode以及DataNode都会相互关联。按照必定的顺序来实现读取这一过程,读取过程以下图所示:对象
经过上图,读取HDFS上的文件的流程能够清晰的知道,Client经过实例打开文件,找到HDFS集群的具体信息(咱们须要操做的是ClusterA,仍是ClusterB,须要让Client端知道),这里会建立一个输入流,这个输入流是链接DataNode的桥梁,相关数据的读取Client都是使用这个输入流来完成的,而在输入流建立时,其构造函数中会经过一个方法来获取NameNode中DataNode的ID和Block的位置信息。Client在拿到DataNode的ID和Block位置信息后,经过输入流去读取数据,读取规则按照“就近原则”,即:和最近的DataNode创建联系,Client反复调用read方法,并将读取的数据返回到Client端,在达到Block的末端时,输入流会关闭和该DataNode的链接,经过向NameNode获取下一个DataNode的ID和Block的位置信息(若对象中为缓存Block的位置信息,会触发此步骤,不然略过)。而后拿到DataNode的ID和Block的位置信息后,在此链接最佳的DataNode,经过此DataNode的读数据接口,来获取数据。
另外,每次经过向NameNode回去Block信息并不是一次性获取全部的Block信息,需得屡次经过输入流向NameNode请求,来获取下一组Block得位置信息。然而这一过程对于Client端来讲是透明的,它并不关系是一次获取仍是屡次获取Block的位置信息,Client端在完成数据的读取任务后,会经过输入流的close()方法来关闭输入流。
在读取的过程中,有可能发生异常,如:节点掉电、网络异常等。出现这种状况,Client会尝试读取下一个Block的位置,同时,会标记该异常的DataNode节点,放弃对该异常节点的读取。另外,在读取数据的时候会校验数据的完整性,若出现校验错误,说明该数据的Block已损坏,已损坏的信息会上报给NameNode,同时,会从其余的DataNode节点读取相应的副本内容来完成数据的读取。Client端直接联系NameNode,由NameNode分配DataNode的读取ID和Block信息位置,NameNode不提供数据,它只处理Block的定位请求。这样,防止因为Client的并发数据量的迅速增长,致使NameNode成为系统“瓶颈”(磁盘IO问题)。
HDFS的写文件过程较于建立、删除、读取等,它是比较复杂的一个过程。下面,笔者经过一个流程图来为你们剖析其中的细节,以下图所示:
Client端经过实例的create方法建立文件,同时实例建立输出流对象,并经过远程调用,通知NameNode执行建立命令,建立一个新文件,执行此命令须要进行各类校验,如NameNode是否处理Active状态,被建立的文件是否存在,Client建立目录的权限等,待这些校验都经过后,NameNode会建立一个新文件,完成整个此过程后,会经过实例将输出流返回给Client。
这里,咱们须要明白,在向DataNode写数据的时候,Client须要知道它须要知道自身的数据要写往何处,在茫茫Cluster中,DataNode成百上千,写到DataNode的那个Block块下,是Client须要清楚的。在经过create建立一个空文件时,输出流会向NameNode申请Block的位置信息,在拿到新的Block位置信息和版本号后,输出流就能够联系DataNode节点,经过写数据流创建数据流管道,输出流中的数据被分红一个个文件包,并最终打包成数据包发往数据流管道,流经管道上的各个DataNode节点,并持久化。
Client在写数据的文件副本默认是3份,换言之,在HDFS集群上,共有3个DataNode节点会保存这份数据的3个副本,客户端在发送数据时,不是同时发往3个DataNode节点上写数据,而是将数据先发送到第一个DateNode节点,而后,第一个DataNode节点在本地保存数据,同时推送数据到第二个DataNode节点,依此类推,直到管道的最后一个DataNode节点,数据确认包由最后一个DataNode产生,并逆向回送给Client端,在沿途的DataNode节点在确认本地写入成功后,才会往本身的上游传递应答信息包。这样作的好处总结以下:
另外,在写完一个Block后,DataNode节点会经过心跳上报本身的Block信息,并提交Block信息到NameNode保存。当Client端完成数据的写入以后,会调用close()方法关闭输出流,在关闭以后,Client端不会在往流中写数据,于是,在输出流都收到应答包后,就能够通知NameNode节点关闭文件,完成一次正常的写入流程。
在写数据的过程中,也是有可能出现节点异常。然而这些异常信息对于Client端来讲是透明的,Client端不会关心写数据失败后DataNode会采起哪些措施,可是,咱们须要清楚它的处理细节。首先,在发生写数据异常后,数据流管道会被关闭,在已经发送到管道中的数据,可是尚未收到确认应答包文件,该部分数据被从新添加到数据流,这样保证了不管数据流管道的哪一个节点发生异常,都不会形成数据丢失。而当前正常工做的DateNode节点会被赋予新的版本号,并通知NameNode。即便,在故障节点恢复后,上面只有部分数据的Block会由于Blcok的版本号与NameNode保存的版本号不一致而被删除。以后,在从新创建新的管道,并继续写数据到正常工做的DataNode节点,在文件关闭后,NameNode节点会检测Block的副本数是否达标,在未达标的状况下,会选择一个新的DataNode节点并复制其中的Block,建立新的副本。这里须要注意的是,DataNode节点出现异常,只会影响一个Block的写操做,后续的Block写入不会收到影响。
前面说过,NameNode和DataNode之间数据交互,是经过DataNode节点向NameNode节点发送心跳来获取NameNode的操做指令。心跳发送以前,DataNode须要完成一些步骤以后,才能发送心跳,流程图以下所示:
从上图来看,首先须要向NameNode节点发送校验请求,检测是否NameNode节点和DataNode节点的HDFS版本是否一致(有可能NameNode的版本为2.6,DataNode的版本为2.7,因此第一步须要校验版本)。在版本校验结束后,须要向NameNode节点注册,这部分的做用是检测该DataNode节点是否属于NameNode节点管理的成员之一,换言之,ClusterA的DataNode节点不能直接注册到ClusterB的NameNode节点上,这样保证了整个系统的数据一致性。在完成注册后,DataNode节点会上报本身所管理的全部的Block信息到NameNode节点,帮助NameNode节点创建HDFS文件Block到DataNode节点映射关系(即保存Meta),在完成此流程以后,才会进入到心跳上报流程。
另外,若是NameNode节点长时间接收不到DataNode节点到心跳,它会认为该DataNode节点的状态处理Dead状态。若是NameNode有些命令须要DataNode配置操做(如:前面的删除指令),则会经过心跳后的DataNodeCommand这个返回值,让DataNode去执行相关指令。
简而言之,关于HDFS的建立、删除、读取以及写入等流程,能够一言以蔽之,内容以下:
这篇博客就和你们分享到这里,若是你们在研究学习的过程中有什么问题,能够加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!