HDFS pipeline写 -- 客户端
上一篇说了datanode端如何处理pipeline写请求的,这里主要看DFSClient。
这里以append为例, write差不多。
创建一个pipeline用于append操作的流程:
FileSystem.get(configuration) 返回一个已经初始化完成的DistributedFileSystem对象,内部包含一个DFSClient对象
DistributedFileSystem.append(Path)内部调用DFSClient的append方法返回一个FSDataOutputStream(实际是HdfsDataOutputStream)
,应用往这个stream中write数据即可。
看看DFSClient.append()做了什么
-
调namenode.getFileInfo(file)向namenode得到file的相关信息,包括文件长度,
mtime,atime,block_size,是否为目录,权限,owner,group等. -
调namenode.append(file,clientName)获取file最后一个block的位置信息,以LocatedBlock形式返回.具体看看这个函数.
2.1 namenode内部调用FSNameSystem的appendFile(file, clientName)
从FSDirectory中拿到file对应的INode,做一些check,比如权限,file是文件,不是目录等。
然后调用recoverLeaseInternal检查是否file需要recover lease,详细看看这个函数:
从INodeFile可以知道这个文件是否处于under construction状态,如果不是,则说明不需要进 行lease recovery. 如果是,那么从LeaseManager拿到clientname对应的Lease,以及file所在的Lease,检查是否这个file的Lease已经被当前clientname持有,如果是,则抛异常,客户端append失败。否则,从INodeFile中拿到当前file的lease holder
,然后去LeaseManager中根据lease holder拿到Lease,然后判断Lease是否过期(lease最后更新时间离现在已经超过soft limit,默认60s)。如果没有过期,说明当前文件的lease正被别人持有,不能进行append,抛异常,客户端append失败.如果过期,开始
进入lease recovery阶段, 执行internalReleaseLease(),函数的目的就是使这个file的最后一个block的所有副本数据一致,副本数据不一致可能由之前写这个文件的客户端意外宕机导致。这个问题后续专门分析。2.2 检查最后一个block是否副本数足够,如果不足够,同样抛异常,客户端失败。
2.3 将INodeFile设置为under construction状态,将clientname作为lease holder设置到INodeFile中。同时将clientname,filename添加到Lease中。
2.4 从INodeFile拿出最后一个block的BlockInfo,如果block已经满了,则返回null,后续客户端会发addBlock请求给namenode申请block进行append。如果block不满,将最后一个block设置为under construction状态。更新blocksMap.最后,将最后一个block的LocatedBlock返回给DFSClient.
-
构造DFSOutputStream对象,并且启动对象内部的DataStreamer线程,这里会根据上一步的LocatedBlock来设置DataStreamer的stage状态,显然,如果上一步返回的LocatedBlock不为空,说明append的最后一个block没有满,则stage设置为PIPELINE_SETUP_APPEND,isAppend设置为true,如果LocatedBlock为空,则stage设置为PIPELINE_SETUP_CREATE,isAppend设置false,在这种情况下,会调用nextBlockOutputStream函数来建立pipeline,内部会调用addBlock()向namenode申请一个新的block用于append。最后返回一个包装了DFSOutputStream的HdfsDataOutputStream的对象给客户端.
客户端拿到HdfsDataOutputStream后,往流里写数据,数据以packet的形式被放如DataStreamer
的dataQueue中。DataStreamer线程就是建立pipeline后,往pipeline中写一个个packet,最后关闭pipeline.
对于append来说,建立pipeline的函数是setupPipelineForAppendOrRecovery,函数内部会处理
建立pipeline过程中的失败和重试,最后函数的返回值就代表pipeline是否建立成功.
下面看setupPipelineForAppendOrRecovery是如何建立pipeline和建立失败时pipeline的重试工作.
DFSOutputStream中的DataStreamer建立pipeline时,如果出现某个坏的datanode,那么pipeline会断掉,例如client->A->B->C,B给C建立pipeline时,C是一个坏的datanode,那么在B的DataXceiver.writeBlock()中会设置BlockOpResponseProto的firstBadLink,然后回复给A,A收到后,主动断开和B的连接,以此类推。
pipeline建立失败会将这个第一个坏的datanode踢出pipeline并且放入到一个失败集合中.然后对剩下的datanode检查是否满足了replace-datanode policy,简单来说,就是检查是否需要新增一个datanode进来构成新的pipeline集合,是否需要增加新的datanode进来,主要考虑几个因素, 副本数,是否执行的hflush,是否append操作,当前pipeline中datanode个数.如果最后判定需要新增加datanode,会调用getAdditionalDatanode请求namenode重新分配一个datanode,这里会将失败集合发给namenode,namenode这次不会再选择这些坏的datanode.随后调updateBlockForPipeline向namenode重新申请这个block的新的generation stamp和token.然后调用createBlockOutputStream()重新建立pipeline.一旦建立pipeline成功,会调用updatePipeline向namenode更新pipeline,将旧block和新block的信息发给namenode,namenode根据旧block信息从BlockManager中找到对应的BlockInfo,确定其处于UNDER_CONSTRUCTION状态,并且从BlockInfo中拿出这个Block所在的HDFS文件对应的inode结构INodeFile,并且确定file处于under construction状态,并且确定从INodeFile拿出的clientname和传进来的clientname是相等的。然后,从INodeFile中取出最后一个BlockInfo,将其cast成BlockInfoUnderConstruction,然后再次进行check,如果新block的generation stamp小于等于最后一个block的generation stamp,或者字节数小于最后一个block的generation stamp,那么抛异常.否则更新最后一个block的字节数和generation stamp为新BlockInfo的值,并且设置好最后一个block的List
pipeline 建立好后,DataStreamer启动ResponseProcessor线程用于处理下游对packet的ack,接着DataStreamer从dataQueue中取出一个个的packet往pipeline发.最后拆除pipeline.
参考资料
hadoop-hdfs-2.4.1.jar