元数据:
先看下方提供的,结构图,了解元数据的结构,以及相关的同步操作;
客户端事件请求:
客户端:DFSClient类可以和namenode通信,也可以和datanode通信读写block数据,使用者要获取DistributedFileSystem实例,即基类FileSystem;
创建目录:
- 先看FileSystem类是个抽象类,本地实现看LocalFileSystem,分布式实现看DistributedFileSystem,我们看分布式的;
- mkdirs-》mkdirs-》primitiveMkdir-》namenode.mkdirs,namenode是客户端代理,接下来看namenode rpc server;
写数据:
-
FileSystem-》create-》DistributedFileSystem实现了create,DFSClient.create-》DFSOutputStream.newStreamForCreate,若捕获异常重试多次-》dfsClient.namenode.create
-
out = new DFSOutputStream-》computePacketChunkSize,数据组成,dir->file->block(128M)->package(64k)->chunk(512byte)+chunk sum(4byte)
-
out.start-》启动线程,查看run方法,dataQueue为空则wait,直到超时;
-
接下来看namenode rpc server的create方法-》向目录树添加文件,添加契约
-
beginFileLease,每隔一段时间进行续约,LeaseRenewer.run-》namenode.renewLease,查看namenode rpc的处理方法;
-
最终返回dfs.createWrappedOutputStream-》HdfsDataOutputStream
-
接着调用,write-》DFSOutputStream-》FSOutputSummer.write-》flushBuffer-》writeChecksumChunks-》writeChunkImpl-》waitAndQueueCurrentPacket-》queueCurrentPacket,添加数据到dataQueue,然后,notifyAll(),唤醒等待的dataQueue;即run方法;
-
nextBlockOutputStream,建立数据管道,向namenode申请block-》locateFollowingBlock,namenode.addBlock,看namenode rpc;
-
createBlockOutputStream-》createSocketForPipeline创建socket连接server,创建输入/输出流-》writeBlock-》send-》out.flush(),目的是,各个datanode建立管道,接下来看datanode DataXceiverServer
-
如果建立管道不成功,放弃block,把datanode节点放入excludeNodes然后,重新申请block,并排除excludeNodes中的datanode;
-
- initDataStreaming-》new ResponseProcessor(nodes),看run方法,readFields读取下游处理结果,处理成功的话
ackQueue.removeFirst(); dataQueue.notifyAll();
- 写数据的时候,把数据从dataqueue拿出来放到ackqueue,然后,发送数据到datanode,one.writeTo(blockStream),接下来看datanode的DataXceiverServer;
- 写数据出问题,异常处理,关闭responseProcessor服务 /socket/输入输出流,把ackqueue添加到dataqueue,清空ackqueue,如果出问题的datanonde不多,会切换datanode进行写入,如果出问题的datanode多,向namenode重新发请求申请block,重新建立数据管道;
namenode rpc server:
mkdirs:
- FSNamesystem.mkdirs,检查是否安全模式,是的话创建不了-》FSDirMkdirOp.mkdirs
- getFSDirectory获取目录树,FSDirectory(内存目录树),相比较,FSNamesystem持久化操作到磁盘;
- 获取请求目录的inode把信息存入INodesInPath,获取最后一个inode,如果为空继续处理;
- createChildrenDirectories,循环,创建目录-》createSingleDirectory-》unprotectedMkdir-》创建INodeDirectory然后添加inode,FSDirectory.addLastINode;
- FSDirectory.getEditLog(),即FSEditLog-》logMkDir-》logEdit-》
- 创建txid,editLogStream.write(op)-》遍历journalSet包含的JournalAndStream,获取包含的流然后写入,jas.getCurrentStream().write(op);即EditLogFileOutputStrem、QuorumOutputStream;
- 通过双缓冲,选取当前buffer,写入TxnBuffer;
- logSync-》临时变量获取editLogStream,然后调用flush,然后分两条线(鱼和熊掌不可兼得,想要性能高,就要损失安全性,问题来了,假如出现元数据丢失怎么办?):
- EditLogFileOutputStrem的实现 flushAndSync-》调用双缓冲的readybuffer写入文件,doubleBuf.flushTo(fp);
- QuorumOutputStream含有AsyncLoggerSet,里面包含了多个AsyncLogger,遍历,调用sendEdits-》getProxy().journal,这里使用rpc调用,接下来可用看journalrpc的处理方法,journal协议;
create:
- checkNNStartup检查namenode启动状态,FSNamesystem.startFile
- startFileInternal-》addFile-》newINodeFile,创建INodeFile-》addINode-》addLastINode-》addChild契约
- startFileInternal-》addLease-》类似全局锁,防止一个文件多个节点写入,因此,写入时,获取一个文件的契约;
addBlock:
- getAdditionalBlock-》chooseTarget4NewBlock负载均衡,选择存放block的多台主机-》saveAllocatedBlock修改内存目录树-》addBlock-》addBlockCollection,fileINode.addBlock
- persistNewBlock-》logAddBlock-》logEdit
renewLease:
- 更新契约时间
- 这里额外说下契约检查,LeaseManager,看监控线程-》FSNamesystem 非安全模式下,checkLeases,检查过期契约,过期解除契约;
涉及的类:
FSNamesystem:
- loadFromDisk
- 创建FSImage,创建FSNamesystem,如果启动模式时RECOVER,则进入安全模式;
- loadFSImage-》如果启动模式时FORMAT,fsimage format处理;
- 获取storage的image目录,editslog的目录,若空则异常;
- recoverStorageDirs
- storage获取getLayoutVersion,小于-3则checkVersionUpgradable
- loadFSImage-》readAndInspectDirs,获取imageFiles
- initEditLog
- 遍历imageFiles,loadFSImageFile,通过不同部署版本不同处理
- loadFSImage读文件
- FSNamesystem.getBlockIdManager,设置数据
- 如果支持snapshot,FSNamesystem.getSnapshotManager,设置数据;
- loadLocalNameINodesWithSnapshot,loadLocalNameINodes,loadFullNameINodes-》loadINode,然后添加到FSNamesystem.dir
- loadEdits
FSDirectory:
- INodeDirectory 根目录
- List<INode> children,存储节点;
- INode实现有INodeDirectory,INodeFile等,表示目录和文件;
standby namenode:
- startStandbyServices-》
- new StandbyCheckpointer(初始化,CheckpointerThread),start,启动线程;
- CheckpointerThread 满足条件(100W条日志,或者1小时没checkpoint)会把内存中的image数据写入到磁盘,替换fsimage;
- 向namenode发起http请求,"/imagetransfer",同步fsimage;namenode servlet处理,写磁盘,替换fsimage;疑问?(为什么namenode不自己更新fsimage呢?)
- 第一种方式namenode可以直接写磁盘更新fsimage,但是会不可用;第二种,快照,再写磁盘,但耗内存;第三种,如何加载editlog更新fsimage,难;
- 因此,当前的方式可能效果更好,但也有几个问题,比如同步故障问题等;
DataXceiverServer:
- writeblock下建立管道操作
- 看run方法-》DataXceiver.create-》看run方法-》readOp读取操作类型,processOp处理操作-》
- opWriteBlock-》writeBlock-》启动BlockReceiver-》PIPELINE_SETUP_CREATE创建管道-》createRbw(写文件时会放到rbw目录,完成后放到finalized目录),获取volume,然后createRbwFile
- 连接到下游机器,new Sender(mirrorOut).writeBlock,发送数据到下游的datanode;