HDFS架构师 4- DataNode写数据流程详细分析
UNIT4
image.png1. 创建INodeFile流程分析 0:15:00 ~ 0:29.19
2步:
FileSystem.create
↓
DistributedFileSystem.create
——》DFSClient.create
——》DFSOutputStream.newStreamForCreate
——》dfsClient.namenode.create(远程代理类)
↓ 远程
NamenodeRpcserver.create
▼
namesystem.startFile
—》FSNamesystem:startFileInt
—》FSNamesystem: startFileInternal
▼
iip = dir.addFile //添加文件
leaseManager.addLease //添加租约
2. 添加契约流程分析 0:29.19 ~ 0:36.51
,因为HDFS上的文件是不允许并发写的,比如并发的追加一些数据什么。 同一时间只能有一个客户端获取NameNode上面一个文件的契约,然后才可以向获取契约的文件写 入数据。 此时如果其他客户端尝试获取文件契约的时候,就获取不到,只能干等着。 通过这个机制,可以保证同一时间只有一个客户端在写一个文件。 在获取到了文件契约之后,在写文件的过程期间,那个客户端需要开启一个线程,不停的发送请求给 NameNode进行文件续约,告诉NameNode: 我还在写文件啊,你给我一直保留那个契约好吗? 而NameNode内部有一个专门的后台线程,负责监控各个契约的续约时间。 如果某个契约很长时间没续约了,此时就自动过期掉这个契约,让别的客户端来写。
接上 leaseManager.addLease 租约
—》 leases.put sortedLeases.add 可排序
3. DataStreamer启动流程分析 0:36:51 ~ 0:52:29
接上回 1 DFSOutputStream.newStreamForCreate 方法
—》DataStreamer#start() -- run方法
▼
//dataqueue里面没有数据,代码阻塞在此,有数据则notify
dataQueue.wait(timeout);
4. 启动文件续约流程分析 52:29 ~ 1:00:00
退回 DFSClient.create 分析 beginFileLease 开启续约
/* 这个方法完成3件事 :
1、往文件目录里面加了 文件
2、添加了租约
3、 启动了 DataStreamer流程 , dataqueue 有数据则notify
*/
DFSClient#create
—》 beginFileLease() 开始续约
—》(LeaseManager)getLeaseRenewer().put
—》LeaseManager#run(final int id)
—》LeaseManager#renew
—》DFSClient#renewLease 57:17
↓ 远程RPC
—》 NamenodeRpcserver#renewLease
—》FSNamesystem#renewLease
—》LeaseManager#renewLease
逻辑如下
//先移除
sortedLeases.remove(lease);
//修改心跳
lease.renew();
//把改后的加入
sortedLeases.add(lease);
5. 契约扫描机制分析 1:00:00 ~ 1:15:11
LeaseManager#Monitor#run
—》LeaseManager#checkLeases
try {
//拿出最早的租约
leaseToCheck = sortedLeases.first();
} catch(NoSuchElementException e) {}
while(leaseToCheck != null) {
if (!leaseToCheck.expiredHardLimit()) {
break;
}
6. chunk封装为packet 写入DataQueue流程剖析 1:14:11~ 1:50:11
接上回 1
客户端代码为:
FileSystem.create()// 这个上面做过分析了
现在到
FSDataOutputStream.write
FSDataOutputStream.write()
↓
HdfsDataOutputStream的父类FSDataOutputStream.write()
▼
//TODO out 即为DFSOutputStream
out.write(b);
↓
DFSOutputStream的父类FSOutputSummer.write()
—》FSOutputSummer#flushBuffer
—》FSOutputSummer#writeChecksumChunks
▼
writeChunk(b, off + i, chunkLen, checksum, ckOffset, getChecksumSize());
↓
——》子类DFSOutputStream#writeChunk
——》DFSOutputStream#writeChunkImpl
——》DFSOutputStream#waitAndQueueCurrentPacket
——》DFSOutputStream#queueCurrentPacket//把packet加入 list
——》DFSOutputStream# dataQueue.notifyAll();
——》DFSOutputStream#DataStreamer#run()
7. pipline数据管道流程之 namenode 申请Block 流程剖析 1:53:40 ~ 1:59:40
接上回 DFSOutputStream#DataStreamer#run 里面 这句setPipeline(nextBlockOutputStream());
这个方法 nextBlockOutputStream() 即为Block申请流程
DFSOutputStream#DataStreamer#nextBlockOutputStream
——》lb = locateFollowingBlock(excluded.length > 0 ? excluded : null);
return dfsClient.namenode.addBlock ;
↓ RPC 远程调用
——》 NameNodeRpcServer#addBlock
▼
namesystem.getAdditionalBlock
——》FSnamesystem#getAdditionalBlock 选择存放datablock的主机-负载均衡,机架感知
1 saveAllocatedBlock 修改内存内的目录树,修改元数据
——》 dir.addBlock
▼
getBlockManager().addBlockCollection //BlockManager记录block信息
fileINode.addBlock //新产生的block放到文件节点下
2 persistNewBlock(src, pendingFile); 元数据落盘
回到 setPipeline() 下面开始建立数据管道
8. pipline数据管道流程建立分析 2:00:00 ~
DFSOutputStream#DataStreamer#setPipeline() 方法
DFSOutputStream#DataStreamer#setPipeline
—》DFSOutputStream#DataStreamer#setPipeline#nextBlockOutputStream
—》DFSOutputStream#DataStreamer#createBlockOutputStream
▼
out = new DataOutputStream(new BufferedOutputStream(unbufOut,
HdfsConstants.SMALL_BUFFER_SIZE))
new Sender(out).writeBlock
—》Sender#writeBlock()
—》Sender#send()
out.flush();
↓ RPC 远程调用
DataXceiverServer#run()
▼
//TODO 接受socket请求
peer = peerServer.accept();
//每一个block都启动一个DataXceiver
new Daemon(datanode.threadGroup,
DataXceiver.create(peer, datanode, this))
.start();
↓
DataXceiver.run()
—》 processOp()
▼
case WRITE_BLOCK:
opWriteBlock(in);
—》 DataXceiver.writeBlock
9. 管道建立容错处理 (retry ,排除刚才问题机器) 2.25:00~ 2.30:00
接上 7 节
DFSOutputStream#DataStreamer#nextBlockOutputStream 方法
▼
//TODO 整个管道建立是这段代码
success = createBlockOutputStream(nodes, storageTypes, 0L, false);
.....
if (!success) {
DFSClient.LOG.info("Abandoning " + block);
//管道不成功就 放弃block
dfsClient.namenode.abandonBlock(block.getCurrentBlock(),
..... 排除节点hadoop3
excludedNodes.put(nodes[errorIndex], nodes[errorIndex]);
.....
lb = locateFollowingBlock
10. ResponseProcessor组件初始化流程分析 2:31:00 ~ 2:41:00
接上回7: DFSOutputStream#DataStreamer#run 方法
DFSOutputStream#DataStreamer#setPipeline后面
//TODO ResponseProcessor组件初始化
initDataStreaming();
▼
response = new ResponseProcessor(nodes);
response.start();
↓
DFSOutputStream# ResponseProcessor.run()
▼
//TODO
ackQueue.removeFirst();
dataQueue.notifyAll();
11. BlockReceiver和PacketResponder初始化 2:40:00~
接上回7: DFSOutputStream#DataStreamer#run 方法
▼
// write out data to remote datanode
TraceScope writeScope = Trace.startSpan("writeTo", span);
try {
one.writeTo(blockStream);
blockStream.flush();
↓
DataXceiverServer.run
▼
new Daemon(datanode.threadGroup,
DataXceiver.create(peer, datanode, this)) .start();
↓
DataXceiver.run
——》 DataXceiver.processOp()
——》DataXceiver.opWriteBlock()
——》DataXceiver.writeBlock()
▼
blockReceiver = new BlockReceiver(block, storageType, in,
blockReceiver.receiveBlock
——》BlockReceiver.receiveBlock()
▼
responder = new Daemon(datanode.threadGroup, new PacketResponder(replyOut, mirrIn, downstreams));
↓
BlockReceiver.PacketResponder.run()
▼
removeAckHead()
▼
ackQueue.removeFirst();
ackQueue.notifyAll();