hdfs写文件过程

作者:Liao hongshen

关于hadoop写文件的过程的一个描述; 首先附图hadoop指南中



关于会上讨论的数据写入一致性,真实的过程和刘喆描述是相同的,由DFSClient向第一个datanode(从namenode申请)中建立dataoutputstream,在这个中会写入复制datanode的头信息;Datanode中的DataXcevier接受这个头信息后会与前一个节点建立ack inputstream,并继续向后续复制节点发送头信息;

DFSClient维护一个

private LinkedList<Packet> ackQueue = new LinkedList<Packet>();

来接受ack确认信息;

BlockReceiver$PacketResponder.run();

// 这是从下一个节点接受ack后 写入前一个节点中

ack.readFields(mirrorInnumTargets);

long seqno = ack.getSeqno();

if (seqno == PipelineAck.HEART_BEAT.getSeqno()) {

ack.write(replyOut); // send keepalive

replyOut.flush();

}


// send my ack back to upstream datanode

PipelineAck replyAck = new PipelineAck(expected, replies);

replyAck.write(replyOut);

replyOut.flush();


当一个块写完之前,在dfsclient$ResponseProcessor保证所有target(复制节点)packetack都已经收到;


代码流程如下所示:


在创建与第一个节点的流的时候,往头中写入了复制节点信息;

private boolean DFSClient$DFSOutputStream.createBlockOutputStream(DatanodeInfo[] nodes, String client,

boolean recoveryFlag) {

out.writeInt( nodes.length - 1 );

for (int i = 1; i < nodes.length; i++) {

nodes[i].write(out);

}

}



每一个DataXceiver 有三个相关的功能

2.1 读取出前面datanode写入的复制节点的头信息

    1. 建立与前面一个节点ack stream

    2. 建立与下一个复制节点的stream,然后写入 后面的复制节点;

private void DataXceiver.writeBlock(DataInputStream in) throws IOException {

// 2.1 read DataNodeInfo from inputstream;

DatanodeInfo targets[] = new DatanodeInfo[numTargets];

for (int i = 0; i < targets.length; i++) {

DatanodeInfo tmp = new DatanodeInfo();

tmp.readFields(in);

targets[i] = tmp;

}

// 2.2 get a connection back to the previous target

replyOut = new DataOutputStream(

NetUtils.getOutputStream(sdatanode.socketWriteTimeout));





// 2.3 Connect to next backup machine

mirrorNode = targets[0].getName();

mirrorOut = new DataOutputStream(

new BufferedOutputStream(

NetUtils.getOutputStream(mirrorSock, writeTimeout),

SMALL_BUFFER_SIZE));

mirrorOut.writeBoolean(hasSrcDataNode);

if (hasSrcDataNode) { // pass src node information

srcDataNode.write(mirrorOut);

}

mirrorOut.writeInt( targets.length - 1 );

for ( int i = 1; i < targets.length; i++ ) {

targets[i].write( mirrorOut );

}

}



附上 粗略的流过程描述

这是一段最平常的在hdfs上创建序列化文件的代码。
        FileSystem fs = FileSystem.get(conf);
        SequenceFile.Writer writer = SequenceFile.createWriter(fs, conf,
                new Path("/user/kmp/filename"), Text.class, IntWritable.class,

                CompressionType.BLOCK, codec);  

        writer.append(new Text("http://qingbo.net/blog/post144.html"),
                new IntWritable(1));

        writer.close();

 

涉及的代码包括本地的FileSystem(DistributeFileSystem) ,DFSClient($DFSOutputStream,$DataStream ,$LeaseChecker);以及NameNodeFSNameSystem

 

内部工作分为以下几个步骤:

第一个流程是在namenode中注册一个文件名称,并申请租约。

SequenceFile -> DistributeFileSystem - > DFSClient create) -> DFSClient$DFSOutputStream(本地租约LeaseChecker) 

->NameNode(create,startFile) ; RPC远程调用

 public void create(String src,  FsPermission masked,
                             String clientName, 
                             boolean overwrite,
                             short replication,
                             long blockSize
 ) throws IOException;

->FSNameSystem(startFileInternal{检查租约的异常,正常情况下在FSDirectory中添加一个INodeFileUnderConstruction,并LeaseManager添加一个新租约})

 

在本地会有一个租约管理器线程,周期性的向namenode续约   

namenode.renewLease(String clientName);//1000ms间隔

 

第二个流程是在datanode中启动DFSOutputStream,并返回给上层供调用。

一句stream.start()就将流启动起来了;启动一个新线程DataStream周期性扫描(主要工作都在这里,与datanode建立连接,从package列表中取数据后写入datanode);

BEGIN [寻找合适的datanode来存储块]

->DFSOutputStreamlocateFollowingBlock

-> NameNode(addBlock)   RPC远程调用

LocatedBlock addBlock(String src, 
                               String clientName);

主要功能

->FSNameSystem(getAdditionalBlock)

-> FSDirectory

   Block addBlock(String path, INode[] inodes, Block block) ;

  在FSDirectoryBlocksMap中修改对应的信息、

END [寻找合适的datanode来存储块]

 

-> DFSClient$DFSOutputStream(nextBlockOutputStream)

 根据前面已经找到datanode位置,然后建立stream关系。(一个数据输入流,一个响应输出流双线程)

->DFSClient$DFSOutputStream( createBlockOutputStream(nodes, clientName, false));

   如果创建失败则 namenode.abandonBlock来取消该block

第三个流程是写数据的过程

LinkedList<Packet> dataQueue = new LinkedList<Packet>();是负责管理数据包的列表

DataOutputStream  blockStream; 这个在DataStream中周期性被调用来write上面的包

DFSClient$DFSOutputStream writeChunk会被不断的调用来修改dataQueue;

 

注意一个细节:当一个块快要写满的时候,则需要重新开启一个新的流

  private long bytesCurBlock = 0; // bytes writen in current block

  final private long blockSize;

writeChunk中判断是否为本块的最后一个package,在datastream中重新调用nextBlockOutputStream(再次向namenode请求一个位置)

 

第四个流程是关闭close

1flush数据

2:停止线程,关闭流,本地租约移除

3NameNode complete); RPC远程调用

  public boolean complete(String src, String clientName) throws IOException

4:FSNameSystem

synchronized CompleteFileStatus completeFileInternal(String src, 
                                                String holder) throws IOException ;

finalizeINodeFileUnderConstruction();

租约召回,正式建立INode节点取代INodeFileUnderConstruction


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值