Hadoop源代码分析二.docx
- 文档编号:29279121
- 上传时间:2023-07-21
- 格式:DOCX
- 页数:37
- 大小:1.26MB
Hadoop源代码分析二.docx
《Hadoop源代码分析二.docx》由会员分享,可在线阅读,更多相关《Hadoop源代码分析二.docx(37页珍藏版)》请在冰豆网上搜索。
Hadoop源代码分析二
Hadoop源代码分析
(二)
关键字:
分布式云计算还有源码分析三
通信的对象进行序列化
前面已经分析过getBlockLocations,create,append,setReplication,setPermission和setOwner,接下来我们继续回来讨论和文件内容相关的操作。
publicvoidabandonBlock(Blockb,Stringsrc,Stringholder
)throwsIOException;
abandonBlock用于放弃一个数据块。
普通的文件系统中并没有“放弃”操作,HDFS出现放弃数据块的原因,如下图所示。
当客户端通过其他操作(如下面要介绍的addBlock方法)获取LocatedBlock后,可以打开到一个block的输出流,由于从DataNode出错到NameNode发现这个信息,需要有一段时间(NameNode长时间收到DataNode心跳),打开输出流可能出错,这时客户端可以向NameNode请求放弃这个数据块。
abandonBlock的处理不是很复杂,首先检查租约(调用checkLease方法。
block对应的文件存在,文件处于构造状态,租约拥有者匹配),如果通过检查,调用FSDirectory的removeBlock,从INodeFileUnderConstruction/BlocksMap/CorruptReplicasMap中删除block,然后通过logOpenFile()记录变化(logOpenFile真是万能啊)。
publicLocatedBlockaddBlock(Stringsrc,StringclientName)throwsIOException;
写HDFS的文件时,如果数据块被写满,客户端可以通过addBlock创建新的数据块。
具体的创建工作由FSNamesystem的getAdditionalBlock方法完成,当然上来就是一通检查(是否安全模式,命名/存储空间限额,租约,数据块副本数,保证DataNode已经上报数据块状态),然后通过ReplicationTargetChooser,选择复制的目标(如果目标数不够副本数,又是一个异常),然后,就可以分配数据块了。
allocateBlock创建一个新的Block对象,然后调用addBlock,检查参数后把数据块加到BlocksMap对象和对应的INodeFile对象中。
allocateBlock返回后,getAdditionalBlock还会继续更新一些需要记录的信息,最后返回一个新构造的LocatedBlock。
publicbooleancomplete(Stringsrc,StringclientName)throwsIOException;
当客户端完成对数据块的写操作后,调用complete完成写操作。
方法complete如果返回是false,那么,客户端需要继续调用complete方法。
FSNamesystem的同名方法调用completeFileInternal,它会:
●∙∙∙∙∙∙∙∙∙∙检查环境;
●∙∙∙∙∙∙∙∙∙∙获取src对应的INode;
●∙∙∙∙∙∙∙∙∙∙如果INode存在,并且处于构造状态,获取数据块;
●∙∙∙∙∙∙∙∙∙∙如果获取数据块返回空,返回结果CompleteFileStatus.OPERATION_FAILED,FSNamesystem的complete会抛异常返回;
●∙∙∙∙∙∙∙∙∙∙如果上报文件完成的DataNode数不够系统最小的副本数,返回STILL_WAITING;
●∙∙∙∙∙∙∙∙∙∙调用finalizeINodeFileUnderConstruction;
●∙∙∙∙∙∙∙∙∙∙返回成功COMPLETE_SUCCESS
其中,对finalizeINodeFileUnderConstruction的处理包括:
●∙∙∙∙∙∙∙∙∙∙释放租约;
●∙∙∙∙∙∙∙∙∙∙将对应的INodeFileUnderConstruction对象转换为INodeFile对象,并在FSDirectory进行替换;
●∙∙∙∙∙∙∙∙∙∙调用FSDirectory.closeFile关闭文件,其中会写日志logCloseFile(path,file)。
●∙∙∙∙∙∙∙∙∙∙检查副本数,如果副本数小于INodeFile中的目标数,那么添加数据块复制任务。
我们可以看到,complete一个文件还是比较复杂的,需要释放很多的资源。
publicvoidreportBadBlocks(LocatedBlock[]blocks)throwsIOException;
调用reportBadBlocks的地方比较多,客户端可能调用,DataNode上也可能调用。
由于上报的是个数组,reportBadBlocks会循环处理,调用FSNamesystem的markBlockAsCorrupt方法。
markBlockAsCorrupt方法需要两个参数,blk(数据块)和dn(所在的DataNode信息)。
如果系统目前副本数大于要求,那么直接调用invalidateBlock方法。
方法invalidateBlock很简单,在检查完系统环境以后,先调用addToInvalidates方法往FSNamesystem.recentInvalidateSets添加一项,然后调用removeStoredBlock方法。
removeStoredBlock被多个方法调用,它会执行:
●∙∙∙∙∙∙∙∙∙∙从BlocksMap中删除记录removeNode(block,node);
●∙∙∙∙∙∙∙∙∙∙如果目前系统中还有其他副本,调用decrementSafeBlockCount(可能的调整安全模式参数)和updateNeededReplications(跟新可能存在的block复制信息,例如,现在系统中需要复制1个数据块,那么更新后,需要复制2个数据块);
●∙∙∙∙∙∙∙∙∙∙如果目前系统中有多余数据块等待删除(在excessReplicateMap中),那么移除对应记录;
●∙∙∙∙∙∙∙∙∙∙删除在CorruptReplicasMap中的记录(可能有)。
removeStoredBlock其实也是涉及了多处表操作,包括BlocksMap,excessReplicateMap和CorruptReplicasMap。
我们回到markBlockAsCorrupt,如果系统目前副本数小于要求,那么很显然,我们需要对数据块进行复制。
首先将现在的数据块加入到CorruptReplicasMap中,然后调用updateNeededReplications,跟新复制信息。
markBlockAsCorrupt这个流程太复杂了,我们还是画个图吧:
由于Hadoop的MapReduce和HDFS都有通信的需求,需要对通信的对象进行序列化。
Hadoop并没有采用Java的序列化,而是引入了它自己的系统。
org.apache.hadoop.io中定义了大量的可序列化对象,他们都实现了Writable接口。
实现了Writable接口的一个典型例子如下:
Java代码
1.public class MyWritable implements Writable {
2. // Some data
3. private int counter;
4. private long timestamp;
5.
6. public void write(DataOutput out) throws IOException {
7. out.writeInt(counter);
8. out.writeLong(timestamp);
9. }
10.
11. public void readFields(DataInput in) throws IOException {
12. counter = in.readInt();
13. timestamp = in.readLong();
14. }
15.
16. public static MyWritable read(DataInput in) throws IOException {
17. MyWritable w = new MyWritable();
18. w.readFields(in);
19. return w;
20. }
21.}
publicclassMyWritableimplementsWritable{
//Somedata
privateintcounter;
privatelongtimestamp;
publicvoidwrite(DataOutputout)throwsIOException{
out.writeInt(counter);
out.writeLong(timestamp);
}
publicvoidreadFields(DataInputin)throwsIOException{
counter=in.readInt();
timestamp=in.readLong();
}
publicstaticMyWritableread(DataInputin)throwsIOException{
MyWritablew=newMyWritable();
w.readFields(in);
returnw;
}
}
其中的write和readFields分别实现了把对象序列化和反序列化的功能,是Writable接口定义的两个方法。
下图给出了庞大的org.apache.hadoop.io中对象的关系。
这里,我把ObjectWritable标为红色,是因为相对于其他对象,它有不同的地位。
当我们讨论Hadoop的RPC时,我们会提到RPC上交换的信息,必须是Java的基本类型,String和Writable接口的实现类,以及元素为以上类型的数组。
ObjectWritable对象保存了一个可以在RPC上传输的对象和对象的类型信息。
这样,我们就有了一个万能的,可以用于客户端/服务器间传输的Writable对象。
例如,我们要把上面例子中的对象作为RPC请求,需要根据MyWritable创建一个ObjectWritable,ObjectWritable往流里会写如下信息
对象类名长度,对象类名,对象自己的串行化结果
这样,到了对端,ObjectWritable可以根据对象类名创建对应的对象,并解串行。
应该注意到,ObjectWritable依赖于WritableFactories,那存储了Writable子类对应的工厂。
我们需要把MyWritable的工厂,保存在WritableFactories中(通过WritableFactories.setFactory)。
目录树相关的方法
下面是和目录树相关的方法。
publicbooleanrename(Stringsrc,Stringdst)throwsIOException;
更改文件名。
调用FSNamesystem的renameTo,干活的是renameToInternal,最终调用FSDirectory的renameTo方法,如果成功,更新租约的文件名,如下:
changeLease(src,dst,dinfo);
publicbooleandelete(Stringsrc)throwsIOException;
publicbooleandelete(Stringsrc,booleanrecursive)throwsIOException;
第一个已经废弃不用,使用第二个方法。
最终使用deleteInternal,该方法调用FSDirectory.delete()。
publicbooleanmkdirs(Stringsrc,FsPermissionmasked)throwsIOException;
在做完一系列检查以后,调用FSDirectory.mkdirs()。
publicFileStatus[]getListing(Stringsrc)throwsIOException;
前面我们已经讨论了。
下面是其它和系统维护管理的方法。
publicvoidrenewLease(StringclientName)throwsIOException;
就是调用了一下leaseManager.renewLease(holder),没有其他的事情需要做,简单。
publicvoidrefreshNodes()throwsIOException;
还记得我们前面分析过NameNode上有个DataNode在线列表和DataNode离线列表吗,这个命令可以让NameNode从新读这两个文件。
当然,根据前后DataNode的状态,一共有4种情况,其中有3种需要修改。
对于从工作状态变为离线的,需要将上面的DataNode复制到其他的DataNode,需要调用updateNeededReplications方法(前面我们已经讨论过这个方法了)。
对于从离线变为工作的DataNode,只需要改变一下状态。
publicvoidfinalizeUpgrade()throwsIOException;
finalize一个升级,确认客户端有超级用户权限以后,调用FSImage.finalizeUpgrade()。
publicvoidfsync(Stringsrc,Stringclient)throwsIOException;
将文件信息持久化。
在检查租约信息后,调用FSDirectory的persistBlocks,将文件的原信息通过logOpenFile(path,file)写日志。
搞定ClientProtocol,接下来是DatanodeProtocol部分。
接口如下:
publicDatanodeRegistrationregister(DatanodeRegistrationnodeReg
)throwsIOException
用于DataNode向NameNode登记。
输入和输出参数都是DatanodeRegistration,类图如下:
前面讨论DataNode的时候,我们已经讲过了DataNode的注册过程,我们来看NameNode的过程。
下面是主要步骤:
●∙∙∙∙∙∙∙∙∙∙检查该DataNode是否能接入到NameNode;
●∙∙∙∙∙∙∙∙∙∙准备应答,更新请求的DatanodeID;
●∙∙∙∙∙∙∙∙∙∙从datanodeMap(保存了StorageIDàDatanodeDescriptor的映射,用于保证DataNode使用的Storage的一致性)得到对应的DatanodeDescriptor,为nodeS;
●∙∙∙∙∙∙∙∙∙∙从Host2NodesMap(主机名到DatanodeDescriptor数组的映射)中获取DatanodeDescriptor,为nodeN;
●∙∙∙∙∙∙∙∙∙∙如果nodeN!
=null同时nodeS!
=nodeN(后面的条件表明表明DataNode上使用的Storage发生变化),那么我们需要先在系统中删除nodeN(removeDatanode,下面再讨论),并在Host2NodesMap中删除nodeN;
●∙∙∙∙∙∙∙∙∙∙如果nodeS存在,表明前面已经注册过,则:
1. 更新网络拓扑(保存在NetworkTopology),首先在NetworkTopology中删除nodeS,然后跟新nodeS的相关信息,调用resolveNetworkLocation,获得nodeS的位置,并从新加到NetworkTopology里;
2. 更新心跳信息(register也是心跳);
●∙∙∙∙∙∙∙∙∙∙如果nodeS不存在,表明这是一个新注册的DataNode,执行
1. 如果注册信息的storageID为空,表明这是一个全新的DataNode,分配storageID;
2. 创建DatanodeDescriptor,调用resolveNetworkLocation,获得位置信息;
3. 调用unprotectedAddDatanode(后面分析)添加节点;
4. 添加节点到NetworkTopology中;
5. 添加到心跳数组中。
上面的过程,我们遗留了两个方法没分析,removeDatanode的流程如下:
●∙∙∙∙∙∙∙∙∙∙更新系统的状态,包括capacityTotal,capacityUsed,capacityRemaining和totalLoad;
●∙∙∙∙∙∙∙∙∙∙从心跳数组中删除节点,并标记节点isAlive属性为false;
●∙∙∙∙∙∙∙∙∙∙从BlocksMap中删除这个节点上的所有block,用了(三零)分析到的removeStoredBlock方法;
●∙∙∙∙∙∙∙∙∙∙调用unprotectedAddDatanode;
●∙∙∙∙∙∙∙∙∙∙从NetworkTopology中删除节点信息。
unprotectedAddDatanode很简单,它只是更新了Host2NodesMap的信息。
下面来看一个大家伙:
publicDatanodeCommandsendHeartbeat(DatanodeRegistrationnodeReg,
longcapacity,
longdfsUsed,
longremaining,
intxmitsInProgress,
intxceiverCount)throwsIOException
DataNode发送到NameNode的心跳信息。
细心的人会发现,请求的内容还是DatanodeRegistration,应答换成DatanodeCommand了。
DatanodeCommand类图如下:
前面介绍DataNode时,已经分析过了DatanodeCommand支持的命令:
DNA_TRANSFER:
拷贝数据块到其他DataNode
DNA_INVALIDATE:
删除数据块
DNA_SHUTDOWN:
关闭DataNode
DNA_REGISTER:
DataNode重新注册
DNA_FINALIZE:
提交升级
DNA_RECOVERBLOCK:
恢复数据块
有了上面这些基础,我们来看FSNamesystem.handleHeartbeat的处理过程:
●∙∙∙∙∙∙∙∙∙∙调用getDatanode方法找对应的DatanodeDescriptor,保存于变量nodeinfo(可能为null)中,如果现有NameNode上记录的StorageID和请求的不一样,返回DatanodeCommand.REGISTER,让DataNode从新注册。
●∙∙∙∙∙∙∙∙∙∙如果发现当前节点需要关闭(已经isDecommissioned),抛异常DisallowedDatanodeException。
●∙∙∙∙∙∙∙∙∙∙nodeinfo是空或者现在状态不是活的,返回DatanodeCommand.REGISTER,让DataNode从新注册。
●∙∙∙∙∙∙∙∙∙∙更新系统的状态,包括capacityTotal,capacityUsed,capacityRemaining和totalLoad;
●∙∙∙∙∙∙∙∙∙∙接下来按顺序看有没有可能的恢复数据块/拷贝数据块到其他DataNode/删除数据块/升级命令(不讨论)。
一次返回只能有一条命令,按上面优先顺序。
下面分析应答的命令是如何构造的。
首先是DNA_RECOVERBLOCK(恢复数据块),那是个非常长的流程,同时需要回去讨论DataNode上的一些功能,我们在后面介绍它。
对于DNA_TRANSFER(拷贝数据块到其他DataNode),从DatanodeDescriptor.replicateBlocks中取出尽可能多的项目,放到BlockCommand中。
在DataNode中,命令由transferBlocks执行,前面我们已经分析过啦。
删除数据块DNA_INVALIDATE也很简单,从DatanodeDescriptor.invalidateBlocks中获取尽可能多的项目,放到BlockCommand中,DataNode中的动作,我们也分析过。
我们来讨论DNA_RECOVERBLOCK(恢复数据块),在讨论DataNode的过程中,我们没有讲这个命令是用来干什么的,还有它在DataNode上的处理流程,是好好分析分析这个流程的时候了。
DNA_RECOVERBLOCK命令通过DatanodeDescriptor.getLeaseRecoveryCommand获取,获取过程很简单,将DatanodeDescriptor对象中队列recoverBlocks的所有内容取出,放入BlockCommand的Block中,设置BlockCommand为DNA_RECOVERBLOCK,就OK了。
关键是,这个队列里的信息是用来干什么的。
我们先来看那些操作会向这个队列加东西,调用关系图如下:
租约有两个超时时间,一个被称为软超时(1分钟),另一个是硬超时(1小时)。
如果租约软超时,那么就会触发internalReleaseLease方法,如下:
voidinternalReleaseLease(Leaselease,Stringsrc)throwsIOException
该方法
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Hadoop 源代码 分析