万事俱备,我们可以来分析NameNode上的流程啦。
首先我们来看NameNode上实现的ClientProtocol,客户端通过这个接口,可以对目录树进行操作,打开/关闭文件等。
getBlockLocations用于确定文件内容的位置,它的输入参数为:文件名,偏移量,长度,返回值是一个LocatedBlocks对象(如下图),它携带的信息很多,大部分字段我们以前都讨论过。
getBlockLocations直接调用NameSystem的同名方法。NameSystem中这样的方法首先会检查权限和对参数进行检查(如偏移量和长度要大于0),然后再调用实际的方法。找LocatedBlocks先找src对应的INode,然后通过INode的getBlocks方法,可以拿到该节点的Block列表,如果返回为空,表明该INode不是文件,返回null;如果Block列表长度为0,以空的Block数组构造返回的LocatedBlocks。
如果Block数组不为空,则通过请求的偏移量和长度,就可以把这个区间涉及的Block找出来,对于每一个block,执行:
l 通过BlocksMap我们可以找到它存在于几个DataNode上(BlocksMap.numNodes方法);
l 计算包含该数据块但数据块是坏的DataNode的数目(通过NameSystem.countNodes方法,间接访问CorruptReplicasMap中的信息);
l 计算坏数据块的数目(CorruptReplicasMap.numCorruptReplicas方法,应该和上面的数相等);
l 通过上面的计算,我们得到现在还OK的数据块数目;
l 从BlocksMap中找出所有OK的数据块对应的DatanodeDescriptor(DatanodeInfo的父类);
l 创建对应的LocatedBlock。
收集到每个数据块的LocatedBlock信息后,很自然就能构造LocatedBlocks对象。getBlockLocations其实只是一个读的方法,请求到了NameNode以后只需要查表就行了。
create方法,该方法用于在目录树上创建文件(创建目录使用mkdir),需要的参数比较多,包括文件名,权限,客户端名,是否覆盖已存在文件,副本数和块大小。NameNode的create调用NameSystem的startFile方法(startFile需要的参数clientMachine从线程局部变量获取)。
startFile方法先调用startFileInternal完成操作,然后调用logSync,等待日志写完后才返回。
startFileInternal不但服务于startFile,也被appendFile调用(通过参数append区分)。方法的最开始是一堆检查,包括:安全模式,文件名src是否正确,权限,租约,replication参数,overwrite参数(对append操作是判断src指向是否存在并且是文件)。租约检查很简单,如果通过FSDirectory.getFileINode(src)得到的文件是出于构造状态,表明有客户正在操作该文件,这时会抛出异常AlreadyBeingCreatedException。
如果对于创建操作,会通过FSDirectory的addFile往目录树上添加一个文件并在租约管理器里添加一条记录。
对于append操作,执行的是构造一个新的INodeFileUnderConstruction并替换原有的节点,然后在租约管理器里添加一条记录。
总的来说,最简单的create流程就是在目录树上创建一个INodeFileUnderConstruction对象并往租约管理器里添加一条记录。
我们顺便分析一下append吧,它的返回值是LocatedBlock,比起getBlockLocations,它只需要返回数组的一项。appendFile是NameSystem的实现方法,它首先调用上面讨论的startFileInternal方法(已经在租约管理器里添加了一条记录)然后写日志。然后寻找对应文件INodeFile中记录的最后一个block,并通过BlocksMap.getStoredBlock()方法得到BlockInfo,然后再从BlocksMap中获得所有的DatanodeDescriptor,就可以构造LocatedBlock了。需要注意的,如果该Block在需要被复制的集合(UnderReplicatedBlocks)中,移除它。
如果文件刚被创建或者是最后一个数据块已经写满,那么append会返回null,这是客户端需要使用addBlock,为文件添加数据块。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。