作者: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(mirrorIn, numTargets);
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(复制节点)的packet的ack都已经收到;
代码流程如下所示:
1 在创建与第一个节点的流的时候,往头中写入了复制节点信息;
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);
}
}
2 每一个DataXceiver 有三个相关的功能
2.1 读取出前面datanode写入的复制节点的头信息
-
建立与前面一个节点ack stream
-
建立与下一个复制节点的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(s, datanode.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);以及NameNode和FSNameSystem。
内部工作分为以下几个步骤:
第一个流程是在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来存储块]
->DFSOutputStream(locateFollowingBlock)
-> NameNode(addBlock) ;RPC远程调用
LocatedBlock addBlock(String src,
String clientName);
主要功能
->FSNameSystem(getAdditionalBlock)
-> FSDirectory
Block addBlock(String path, INode[] inodes, Block block) ;
在FSDirectory和BlocksMap中修改对应的信息、
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
1:flush数据
2:停止线程,关闭流,本地租约移除
3:NameNode (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