HDFS写入文件操作的处理流程源码分析

来源:互联网 发布:天下三女角色捏脸数据 编辑:程序博客网 时间:2024/05/17 09:20
HDFS客户端写文件示例代码
FileSystem hdfs = FileSystem.get(new Configuration());
Path path = new Path("/testfile");
FSDataOutputStream dos = hdfs.create(path);
byte[] readBuf = "Hello World".getBytes("UTF-8");
dos.write(readBuf, 0, readBuf.length);
dos.close();
hdfs.close();
-------------------------HDFS对上述操作的内部处理流程如下------------------------------------
HDFS写入文件的处理流程
一.【DistributedFileSystem.create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress)】根据路径f在FSDirectory.rootDir中创建一个叶子节点INodeFileUnderConstruction对象,返回FSDataOutputStream对象,具体逻辑如下:
1.【DFSClient.create(String src, FsPermission permission, boolean overwrite, boolean createParent, short replication, long blockSize, Progressable progress, int buffersize)】内部调用【DFSClient.DFSOutputStream(String src, FsPermission masked, boolean overwrite, boolean createParent, short replication, long blockSize, Progressable progress, int buffersize, int bytesPerChecksum)】方法初始化DFSClient.DFSOutputStream对象,具体步骤如下:
  1.1)【NameNode.create(String src, FsPermission masked,String clientName, boolean overwrite, short replication, long blockSize)】内部调用【FSNamesystem.startFile(String src, PermissionStatus permissions, String holder, String clientMachine, boolean overwrite, boolean createParent, short replication, long blockSize)方法:
      1.1.1)调用【FSNamesystem.startFileInternal(String path,PermissionStatus permissions, String holder, String clientMachine, boolean overwrite, boolean append,boolean createParent, short replication, long blockSize)】方法,主要实现步骤如下:
       1.1.1.1)【FSDirectory.exists(String path)】判断path在FSDirectory.rootDir目录树状结构中是否已经存在,若存在(即返回true)且path为目录则抛出异常;
                1)调用rootDir.getNode(String path),在目录树结构中查找INode节点;
                2)若节点为INodeDirectory,则返回true;
                3)否则判断INodeFile的BlockInfo[]数组是否为null,若不为空则返回true,为空则返回false;
       1.1.1.2)权限检测,检测此用户对此路径是否有写权限;
       1.1.1.3)若传入参数规定不要求创建父路径,则调用【FSNamesystem.verifyParentDir(String src)】验证父路径是否存在,若检测发现父路径不存在,则抛出异常;
       1.1.1.4)【FSDirectory.getFileINode(String src)】若查找到的INode不是INodeDirectory,则调用【FSNamesystem.recoverLeaseInternal(INode fileInode,
 String src, String holder, String clientMachine, boolean force) 】管理LeaseManager,待分析;
       1.1.1.5)【FSNamesystem.verifyReplication(String src, short replication, String clientName)】检测replication是否在规定的范围内;
       1.1.1.6) 【FSDirectory.isValidToCreate(String src)】在FSDirectory.rootDir结构中检查是否存在即将创建的文件,若不存在则抛出异常,若存在且可以覆盖,则掉用【FSNamesystem.delete(String src, boolean recursive=true)】删除此文件,内部调用【FSNamesystem.deleteInternal(String src, true)】删除文件,
              1)【rootDir.getExistingPathINodes(String path)】查找path路径上的所有INode;
              2)【FSDirectory.removeChild(INode[] pathComponents,int pos)】在FSDirectory.rootDir中删除第1步中的所有INode;
              3)取出此文件所有的Block信息,并存入collectedBlocks:List<Block>队列中;
              4)【FSEditLog.logDelete(String src, long timestamp)】记录FSEditLog.OP_DELETE操作到edits文件中,记录path路径和时间信息;
              5)【FSEditLog.logSync()】同步edits文件;
              6)从FSNamesystem.blocksMap中删除collectedBlocks队列中包含的block块;
       1.1.1.7)【Host2NodesMap.getDatanodeByHost(String host)】根据客户端主机名在FSNamesystem.host2DataNodeMap中获取DatanodeDescriptor对象(关于DataNode的信息,若DFSClient在DataNode上则DatanodeDescriptor对象为非空,若DFSClient不在DataNode上则为空),在下一步初始化INodeFileUnderConstruction对象时,赋值给成员变量clientNode;在上传packet数据包时选择DataNode时,对选择的N个DataNode会按照距clientNode的距离进行排序,若clientNode为空(即DFSClient不在DataNode上时,会选择N个DataNode中的第一个假设为clientNode;进行排序;
       1.1.1.8)若是重新创建文件而不是在已有文件中追加,则调用【FSDirectory.addFile(String path, PermissionStatus permissions, short replication, long preferredBlockSize, String clientName, String clientMachine, DatanodeDescriptor clientNode, long nextGenerationStamp())】方法生成INodeFileUnderConstruction对象:
              1)创建path路径在FSDirectory.rootDir中不存在的目录:
                  a)从目录结构rootDir树中查找出父目录中已经存在的INodeDirectory节点;
                  b)创建在目录树中还不存在的目录;
                  c)【FSEditLog.logMkDir(String path, INode newNode)】以路径名和最后的INodeDirectory节点对象为参数,记录editlog的创建目录日志;
              2) 【INodeFileUnderConstruction(PermissionStatus permissions, short replication, long preferredBlockSize, long modTime, String
 clientName, String clientMachine, DatanodeDescriptor clientNode)】创建INodeFileUnderConstruction对象(表示正在创建的文件),并将此对象放入上一步中创建的父目录的叶子节点上;
              3)【FSEditLog.logOpenFile(String path,INodeFileUnderConstruction newNode)】以路径名和INodeFileUnderConstruction对象为参数,记录editlog的新增文件日志;
              4)返回INodeFileUnderConstruction对象;
        1.1.1.9)【LeaseManager.addLease(newNode.clientName, path)】添加文件的lease,待分析;
      1.1.2) 调用getEditLog().logSync()方法,同步edits文件,即提交;
  1.2)启动DFSClient.DFSOutputStream.DataStreamer线程,用于监控LinkedList<Packet>队列中的数据,当有数据时写入DataNode;
2.【FSDataOutputStream(OutputStream out, Statistics stats)】初始化FSDataOutputStream对象; 其中out为第1步中创建的DFSOutputStream对象;

二.向新创建的文件中写入数据,调用FSDataOutputStream的write方法,总体思路按照hdfs的设计,对block的数据写入使用的是pipeline的方式,也即将数据分成一个个的package,如果需要复制三分,分别写入DataNode 1, 2, 3,则会进行如下的过程:(1)首先将package 1写入DataNode 1;(2)然后由DataNode 1负责将package 1写入DataNode 2,同时客户端可以将pacage 2写入DataNode 1;(3)然后DataNode 2负责将package 1写入DataNode 3, 同时客户端可以讲package 3写入DataNode 1,DataNode 1将package 2写入DataNode 2;就这样将一个个package排着队的传递下去,直到所有的数据全部写入并复制完毕。
具体的实现逻辑如下:
1.【FSDataOutputStream.write(byte b[], int off, int len)】客户端调用此方法,内部调用的是第一步中DFSClient.DFSOutputStream的write(byte b[], int off, int len)方法,但DFSOutputStream类继承自FSOutputSummer类,而DFSOutputStream类无write(byte b[], int off, int len)方法,因此实际上是调用【FSOutputSummer.write(byte[] b, int off, int len)】方法;
  1.1)【FSOutputSummer.write1(byte[] b, int off, int len)】循环调用FSOutputSummer.write1方法:当写入的字节数小于FSOutputSummer.buf[]数组长度时,一次写入;当写入的字节数大于FSOutputSummer.buf[]数组长度时,分批写入;
  1.2)调用FSOutputSummer.writeChecksumChunk方法,
     1.2.1)进行文件完整性检测,生成校验和字节数组checksum;
     1.2.2)调用【DFSClient.DFSOutputStream.writeChunk(byte[] b, int offset, int len,byte[] checksum)】方法;此方法的实现如下:
        1.2.2.1)【DFSClient.DFSOutputStream.Packet.Packet(int pktSize, int chunksPerPkt, long offsetInBlock)】初始化Packet对象,并将校验和字节数组checksum和即将写入的字节数组依次放入Packet.buf[]中;
        1.2.2.2)当检验和的个数等于最大个数,或者放入字节数组的长度等于初始化的长度blockSize时,将当前的packet添加到DFSOutputStream.dataQueue:LinkedList <Packet>队列中,并调用dataQueue.notifyAll方法,唤醒因dataQueue为空而阻塞的DataStreamer线程;把序列号赋值给DFSOutputStream.lastQueuedSeqno;若放入字节数组的长度等于初始化的长度blockSize,则设置Packet.lastPacketInBlock=true;
        1.2.2.3)【DFSOutputStream.computePacketChunkSize(int psize, int csize)】计算DFSOutputStream.chunksPerPacket和DFSOutputStream.packetSize的值;

2.DFSClient.DFSOutputStream.DataStreamer线程监听DFSOutputStream.dataQueue队列,处理队列中的packet数据包,具体实现逻辑如下:
 2.1)【DFSOutputStream.nextBlockOutputStream()】 新建Block块信息,并添加到目录树状结构中;查找replication个目标DataNode;返回以新建的Block和查找到的目标DataNode信息列表来初始化LocatedBlock对象;
    2.1.1)【DFSOutputStream.locateFollowingBlock(long start,DatanodeInfo[] excludedNodes)】,将新建的block对象,DatanodeInfo[]数组封装成LocatedBlock对象返回;内部通过RPC调用【NameNode.addBlock(String path, String clientName, DatanodeInfo[] excludedNodes)】方法,在NameNode端继续调用【FSNamesystem.getAdditionalBlock(String src, String clientName, HashMap<Node, Node> excludedNodes)】方法:
      2.1.1.1)【rootDir.getNode(src)】根据path在FSNamesystem.dir.rootDir目录树结构中查找在第一步中创建的INodeFileUnderConstruction对象;
      2.1.1.2)【BlockPlacementPolicy.chooseTarget(String srcPath, int numOfReplicas, DatanodeDescriptor writer, HashMap<Node, Node> excludedNodes, long blocksize)】选择replication个用于存放文件block块的DataNode;从NameNode生成的关于DataNode的网络位置拓扑图(FSNamesystem.clusterMap)中随机选择replication个DataNode;(内部方法调用流程:BlockPlacementPolicy.chooseTarget --->BlockPlacementPolicyDefault.chooseTarget;)
            1)选择replication个DataNode,放入List<DatanodeDescriptor>队列中;
            2)【BlockPlacementPolicyDefault.getPipeline(DatanodeDescriptor writer, DatanodeDescriptor[] nodes)】,对nodes距离writer的距离进行排序;
            3)返回DatanodeDescriptor[]数组;
      2.1.1.3)获取生成的INodeFileUnderConstruction对象pendingFile,首先检查文件的lease,然后将上一步中获得的DatanodeDescriptor[]数组set到INodeFileUnderConstruction.targets中;
      2.1.1.4)【FSNamesystem.allocateBlock(String src, INode[] inodes)】分配新的block块信息;
            1)初始化Block对象;生成一个随机的blockid和generationStamp信息;
            2)【FSDirectory.addBlock(String path, INode[] inodes,Block block)】:首先将此Block对象加入FSNamesystem.blocksMap中;用此Block对象初始化一个BlockInfo对象(BlockInfo类继承了Block类,在BlockInfo中设置了此Block对象所属的INode节点(INodeFileUnderConstruction对象),并在BlockInfo中初始化了一个三元组Object[]);然后将此BlockInfo添加到INodeFileUnderConstruction对象的BlockInfo[]中;
     2.1.1.5)【FSEditLog.logOpenFile(String path,INodeFileUnderConstruction newNode)】记录editlog的新增操作日志,同时记录INodeFileUnderConstruction对象的BlockInfo[]数组,clientName等信息,为以后的回滚提供资料;
     2.1.1.6)【LocatedBlock.LocatedBlock(Block b, DatanodeInfo[] locs, long startOffset)】以新建的Block和查找到的目标DataNode信息列表来初始化LocatedBlock对象;
   2.1.2)【DFSOutputStream.createBlockOutputStream(DatanodeInfo[] nodes, String client, boolean recoveryFlag)】,与DatanodeInfo[]数组(在2.1.1生成的)的第一个DataNode建立连接:创建用于向DataNode写入数据的DFSOutputStream.blockStream:DataOutputStream对象和用于获取DataNode回执数据的DFSOutputStream.blockReplyStream:DataInputStream对象;
     2.1.2.1)与DatanodeInfo[0]建立socket输出流blockStream和输入流blockReplyStream;
     2.1.2.2)向输出流DFSOutputStream.blockStream中写入消息头,包括:DATA_TRANSFER_VERSION,DataTransferProtocol.OP_WRITE_BLOCK,blockid,GenerationStamp,剩下的DatanodeDescriptor[]数组个数以及剩下的每个数组元素DatanodeInfo的信息(包括ipport,大小,使用率,剩余大小,最后更新时间,位置,hostname等),检验和字节数组;传递剩下的DataNodeInfo信息主要是DataNode将block文件复制到其他DataNode时使用;
     2.1.2.3)【blockReplyStream.readShort()】从DFSOutputStream.blockReplyStream中获取DataNode的响应消息;当响应消息不是成功时抛出异常;
  2.2)【DFSOutputStream.ResponseProcessor.ResponseProcessor(DatanodeInfo[] targets)】初始化DFSOutputStream.ResponseProcessor线程并开启,用于监听DFSOutputStream.blockReplyStream输入流,接受DataNode的响应消息;
  2.3)从DFSOutputStream.dataQueue队列获取第一个Packet,删除此packet并调用notifyAll(),唤醒因dataQueue满而阻塞的线程;
  2.4)将此packet添加到DFSOutputStream.ackQueue队列的尾部;
  2.5)【blockStream.write(byte b[], int off, int len)】调用此方法将packet中的字节数组存入DataNode端;DataNode端会监听此链路并对写入的字节流进行处理,具体实现逻辑见DataNode端的处理逻辑;
  2.6)若此packet是Block的最后一包:
        2.6.1)调用blockStream.writeInt(0)方法;
        2.6.2)等待接受所有packet的响应消息(即DFSOutputStream.ackQueue.size()==0);然后关闭DFSOutputStream.ResponseProcessor线程;
        2.6.3)nodes = null;blockStream = null; blockReplyStream = null;
  2.7)调用blockStream.flush();
  2.8)若DFSOutputStream.progress!=null,即用户定义了一个进度类,则调用progress.progress();给用户报告进度;此进度是以一个packet包中的字节数组为刻度的,即成功写入一个packet才能调用进度方法向用户反映进度;


三.DataNode端的处理逻辑:在(二.2.5)中客户端DFSClient将字节流写入到Socket流中,DataNode端在启动的时候就启动了线程DataXceiverServer专门监听客户端或其他DataNode到来的请求链接;DataXceiverServer线程监听【ss.accept()】由“dfs.datanode.address”参数配置的链路,当有请求到达时启动DataXceiver线程,由该线程处理具体逻辑,如下:
1.在监听的Socket上建立一个DataInputStream对象;
2.解析输入流,从输入流中获取操作信息,根据不同的操作调用响应的处理逻辑:
  2.1)若是DataTransferProtocol.OP_READ_BLOCK操作(读操作),则调用【DataXceiver.readBlock(DataInputStream in)】方法,在HDFS读文件步骤解析中分析;
  2.2)若是DataTransferProtocol.OP_REPLACE_BLOCK操作;调用【DataXceiver.replaceBlock(DataInputStream in)】方法;
  2.3)若是DataTransferProtocol.OP_COPY_BLOCK操作;调用【DataXceiver.copyBlock(DataInputStream in)】方法;
  2.4)若是DataTransferProtocol.OP_BLOCK_CHECKSUM操作;调用【DataXceiver.getBlockChecksum(DataInputStream in)】方法;
  2.5)若是DataTransferProtocol.OP_WRITE_BLOCK操作(写操作);调用【DataXceiver.writeBlock(DataInputStream in)】方法,具体实现如下:
    2.5.1)从输入流解析blockid,GenerationStamp信息,并用于初始化Block对象:block;
    2.5.2)从输入流解析传来的剩下的DatanodeInfo数组信息,生成targets[]:DatanodeInfo数组;
    2.5.3)初始化replyOut:DataOutputStream输出流,用于给客户端传递响应消息;
    2.5.4)【BlockReceiver(Block block, DataInputStream in, String inAddr, String myAddr, boolean isRecovery, String clientName, DatanodeInfo srcDataNode,DataNode datanode)】初始化BlockReceiver对象,
       2.5.4.1)调用本datanode.data变量的【FSDataset.writeToBlock(Block b, boolean isRecovery, boolean replicationRequest)】方法;
          2.5.4.1.1)检查Block是否已经存在:根据Block在FSDataset.volumeMap中查找DatanodeBlockInfo,若DatanodeBlockInfo不为空并通过DatanodeBlockInfo获取File对象,若File不为空,且文件存在则返回此对象,但若文件不存在,则检查磁盘;
          2.5.4.1.2)若上一步判断Block已经存在,且isRecovery=true,则调用【FSDataset.detachBlock(Block block, int numLinks)】将current下面的block文件和对应的meta文件复制到detach目录下面,称为“写入时复制”机制;
          2.5.4.1.3)判断此block对应的文件是否在FSDataset.ongoingCreates中,若在则返回此File对象,并从FSDataset.ongoingCreates中删除;
          2.5.4.1.4)若isRecovery=false,则:
                   1)调用getNextVolume(long blockSize)在FSVolume[](数组个数为dfs.data.dir参数值个数)中找到可用空间大于b.getNumBytes()的FSVolume对象vol;
                   2)调用【FSVolume.createTmpFile(Block b, boolean replicationRequest)】,若replicationRequest=false,则在此FSVolume对应的${dfs.data.dir}/blocksBeingWritten目录下面创建blk_blockid文件;否则replicationRequest=true(clientName == null || clientName.length() == 0时,即是从其他DataNode来的请求)在此FSVolume对应的${dfs.data.dir}/tmp目录下面创建blk_blockid文件;
          2.5.4.1.5)否则若(2.5.4.1.3)步中获得的File对象不为空,则:
                   1)【volumeMap.get(b).getVolume()】根据block在FSDataset.volumeMap中获取的FSVolume对象vol;
                   2)初始化DatanodeBlockInfo(FSVolume vol, File file)对象;
                   3)以block为Key,DatanodeBlockInfo为Value添加到FSDataset.volumeMap中;
          2.5.4.1.6)否则,重新打开block块用于append操作:
                   1)将旧meta文件名重命名为新meta文件名,并放入${dfs.data.dir}/tmp目录;
                   2)将旧block文件名重命名为新block文件名,并放入${dfs.data.dir}/tmp目录;
          2.5.4.1.7)若replicationRequest=true,则【DatanodeBlockInfo(FSVolume vol)】初始化DatanodeBlockInfo对象,此对象的File=null;以block为Key,DatanodeBlockInfo为Value添加到FSDataset.volumeMap中;
          2.5.4.1.8)若replicationRequest=false,则【DatanodeBlockInfo(FSVolume vol, File file)】初始化DatanodeBlockInfo对象;以block为Key,DatanodeBlockInfo为Value添加到FSDataset.volumeMap中;
          2.5.4.1.9)初始化ActiveFile(File f, List<Thread> list)对象(list=null),以block为key,此ActiveFile对象为value放入FSDataset.ongoingCreates中,表示正在创建的文件;
          2.5.4.1.10)获取meta文件的File对象;
          2.5.4.1.11)【FSDataset.createBlockWriteStreams(File f, File metafile)】创建BlockWriteStreams对象,此对象封装了block文件的输出流dataOut和meta文件的输出流checksumOut;返回此BlockWriteStreams对象;
       2.5.4.2)将上一步返回的BlockWriteStreams对象中的两个输出流分别赋值给BlockReceiver.out和BlockReceiver.cout变量;并在meta文件输出流checksumOut的基础上新建一个输出流赋值给BlockReceiver.checksumOut变量;
       2.5.4.3)if(datanode.blockScanner != null && isRecovery)则【datanode.blockScanner.deleteBlock(block)】;
    2.5.5)若targets[]数组大于0,即还有待复制的目标DataNode,则:
       2.5.5.1)取第一个DatanodeInfo元素,并与此目标DataNode建立socket连接,创建输出流DataOutputStream: mirrorOut(用于复制此block到此目标DataNode上)和输入流DataInputStream:mirrorIn(用于获取目标DataNode的响应消息);
       2.5.5.2)向输出流mirrorOut中写入消息头,包括:DATA_TRANSFER_VERSION,DataTransferProtocol.OP_WRITE_BLOCK,blockid,GenerationStamp,剩下的DatanodeDescriptor[]数组个数以及剩下的每个数组元素DatanodeInfo的信息(包括ipport,大小,使用率,剩余大小,最后更新时间,位置,hostname等),检验和字节数组;传递剩下的DataNodeInfo信息主要是DataNode将block文件复制到其他DataNode时使用;
       2.5.5.3)等待目标DataNode的回执消息;
     注释:当目标DataNode获得block后,又将block复制到剩下的DataNode数组的第一个DataNode上,并等待下一个DataNode的回执消息,将block按此方式在多个DataNode上进行复制;因此前面的DataNode会等待后面的DataNode复制完成后的响应消息;
    2.5.6)执行到此,说明DataNode之间的复制工作已经完成,并返回了结果,将此结果写入replyOut输出流中(在2.5.3步创建的),返回结果给客户端或上一个DataNode;
    2.5.7)【blockReceiver.BlockReceiver.receiveBlock(DataOutputStream mirrOut, DataInputStream mirrIn, DataOutputStream replyOut, String mirrAddr, DataTransferThrottler throttlerArg, int numTargets)】
        2.5.7.1)【BlockMetadataHeader.writeHeader(checksumOut, checksum)】将检验和写入BlockReceiver.checksumOut输出流,即写入本地的meta文件中;
        2.5.7.2)开启PacketResponder线程,处理后续DataNode返回的响应消息,若此请求来自DFSClient客户端,则在此线程中调用【datanode.data.finalizeBlock(block)】方法将blocksBeingWritten/目录(在2.5.4.1.4步中创建的)下的数据块移到current/目录下。
        2.5.7.3)【BlockReceiver.receivePacket()】接受从客户端或其他DataNode传来的packet:
            2.5.7.3.1)【BlockReceiver.readNextPacket()】从第1步建立的输入流中读取客户端或其他DataNode传来的字节流,并放入BlockReceiver.buf中:
                     1)为BlockReceiver.buf分配空间;
                     2)【BlockReceiver.readToBuf(-1)】从第1步建立的输入流中获取客户端或其他DataNode传来的字节流,并放入BlockReceiver.buf中;
            2.5.7.3.2)将上一步读取到的字节流写入mirrorOut输出流(在2.5.5.1步创建的)中;
            2.5.7.3.3)将读取到的字节流写入BlockReceiver.out(在2.5.4.2步创建的)中;
        2.5.7.4)调用mirrOut.writeInt(0)和mirrOut.flush();   
        2.5.7.4)关闭PacketResponder线程
        2.5.7.5)若clientname=“”(即表示有其他DataNode传来的),则调用【datanode.data.finalizeBlock(block)】:
              2.5.7.5.1)根据Block从FSDataset.ongoingCreates中获取ActiveFile对象activeFile,并获取activeFile.file文件对象;
              2.5.7.5.2)根据Block从FSDataset.volumeMap中获取FSVolume对象;
              2.5.7.5.3)【FSVolume.addBlock(Block b, File f)】:将tmp/目录下的数据块移到current/目录下。因为:当有数据块到达DataNode节点时,DataNode并不是马上在current/中为这个数据块选择合适的存储目录,而是先把它存放到存储路径的tmp/子目录(在2.5.4.1.4步创建的)下,当这个数据块被DataNode节点成功接受之后,才把它移动到current/下的合适目录中。
                     1)若numBlocks<maxBlocksPerDir(maxBlocksPerDir的值由配置参数“dfs.datanode.numblocks”决定,缺省为64),则将tmp/目录下的数据块移到current/目录下。                         
                     2)当子目录current/中已经存储了maxBlocksPerDir个数据块之后,就会在目录current/下创建maxBlocksPerDir个子目录,然后从中选择一个子目录,把数据块存储到这个子目录中;如果所有的子目录也已经存储了maxBlocksPerDir个数据块,则又在这个子目录下创建maxBlocksPerDir个子目录,从这些子目录中选一个来存储数据块,就这样一次递归下去,直到存储路径的剩余存储空间不够存储一个数据块为止。maxBlocksPerDir的默认值是64,但也可以通过DataNode的配置文件来设置,它对应的配置选项是dsf.datanode.numblocks。
              2.5.7.5.4)以FSVolume对象和activeFile.file文件为参数初始化DatanodeBlockInfo(FSVolume vol, File file)对象;以block为Key,DatanodeBlockInfo为Value添加到FSDataset.volumeMap中;
              2.5.7.5.5)将FSDataset.ongoingCreates中为此Block对象为key值的K-V删除;      
    2.5.8)调用DataBlockScanner.addBlock(Block block)将此block加入DataNode.blockScanner中;


四.向DataNode端写入数据之后,关闭FSDataOutputStream流【FSDataOutputStream.close()】,实质调用DFSClient.DFSOutputStream.close() 方法:
1)【flushBuffer()】,生成检验和,并写入输出流;
2)【flushInternal()】生成最后一个空的Packet数据包并添加到DFSOutputStream.dataQueue中,然后等待最后一个数据包的确认消息:若收到的序号小于客户端的序号则继续等待,当收到的确认消息中的序号大于等于DFSClient客户端记录的最后序号则返回;
3)关闭DFSClient.DFSOutputStream.DataStreamer线程,ResponseProcessor线程;
4) blockStream.writeInt(0); blockStream = null; blockReplyStream = null;
5)【NameNode.complete(String src, String clientName)】将NameNode端目录树中的INodeFileUnderConstruction对象转变成INodeFile对象;内部调用【FSNamesystem.completeFile(String src,String holder)】方法 :
  5.1)【FSNamesystem.checkLease(String src, String holder)】检查文件的租约lease:
     5.1.1)【dir.getFileINode(src)】根据path从FSNamesystem.dir中获得INodeFileUnderConstruction对象;
     5.1.2)【FSNamesystem.checkLease(String src, String holder,INode file)】检测租约lease;
     5.1.3)返回此INodeFileUnderConstruction对象;
  5.2)【dir.getFileBlocks(src)】根据path从FSNamesystem.dir中获取文件的Block[]数组(即src对应文件的Block信息);若Block[]数组为空,则返回操作失败的状态码;
  5.3)【FSNamesystem.checkFileProgress(INodeFile v,true)】根据INodeFile中三元组的个数计算复制的DataNode,判断是否小于最小复制数(minReplication),若是,则返回状态:CompleteFileStatus.STILL_WAITING;
  5.3)【FSNamesystem.finalizeINodeFileUnderConstruction(String src, INodeFileUnderConstruction pendingFile)】将目录树中的INodeFileUnderConstruction对象替换为INodeFile对象,具体步骤如下:
     5.3.1)【LeaseManager.removeLease(String holder, String src)】删除租约;从FSNamesystem.leaseManager中移除此文件所属clientName的lease和path信息;
     5.3.2)【INodeFileUnderConstruction.convertToInodeFile()】将INodeFileUnderConstruction对象转变成INodeFile对象newFile;
     5.3.3)【FSDirectory.replaceNode(String path, INodeFile oldnode, INodeFile newnode)】完成替换工作;其中参数oldnode=pendingFile,newnode=newFile,具体步骤如下:
         5.3.3.1)从oldnode对象的父节点parent的子节点集合中删除此oldnode节点;oldnode对象的parent置为null;
         5.3.3.2)【rootDir.addNode(String path,  INodeFile newNode)】将newNode节点放入rootDir目录树状结构的path路径下,作为树的叶子节点;
         5.3.3.3)将此文件所有的block对象封装成BlockInfo对象后放入FSNamesystem.blocksMap中,并将blockinfo对象放入INodeFile.blocks[]中;
      4.4.4)【FSDirectory.closeFile(String path, INodeFile file)】往Editlog中写入FSEditLog.OP_CLOSE日志;    
     5.3.4)【getEditLog().logSync()】提交日志文件;
     5.3.5)返回CompleteFileStatus.COMPLETE_SUCCESS结果码;
6)close操作结束;