程序员人生 网站导航

Hadoop Namenode以regular方式启动代码流程分析

栏目:互联网时间:2014-09-18 09:00:24

 编者按:在hadoop中,Namenode负责对HDFS的metadata的持久化存储,并且处理来自客户端的对HDFS的各种操作的交互反馈。张晓丰的博客给我们详细讲述了hadoop-0.20.2-cdh3u1版本下,通过regular方式启动时的代码流程分析。以下为原文:

在Namenode启动时会首先去构造Configuration对象,这个对象会贯穿代码的整个执行过程,不过在构造的时候它并没有去加载解析core-site.xml、hdfs-site.xml等配置文件,而是在第一次要使用到这些配置的时候才去解析,解析后保存在Configuration类里的一个Properties的对象里,在这之后才真正的去用Namenode的构造方法构造Namenode对象。

这里简单讲一下Namenode类。在Namenode里有一个FSNamesystem类的对象,这个类才真正保存了文件系统的信息。它的内部主要是通过保存几个重要的映射来达到这个目的的,比如文件与block的映射,机器(也就是各个datanode节点)与block的映射,block与机器的映射等。除了FSNamesystem类的对象,Namenode 里还有两个Server类型的成员,Server类主要是用于RPC请求的服务端实现。为什么会有两个RPC请求的服务端实现呢?其实这只是cdh3u1版本在apache的0.20.2版本上做的二次开发。在apache的0.20.2版本里只有一个RPC请求的服务端,它处理了hadoop集群内部节点和客户端发起的所有RPC请求;然而在cdh3u1里把这两种来源的RPC请求分开来处理了,一个Server处理hadoop集群内部节点的请求,另一个Server处理来自客户端发起的RPC请求;当然,要在配置文件里配置是否使用单独一个Server来处理内部的RPC请求,否则还是会只启动一个Server来处理。个人觉得分开来处理是有好处的,这样的话内部和客户端的RPC请求不会相互影响了,也会提高客户端得到响应的速度。

在Namenode的构造方法里会去调用initialize方法,在这个方法里才做了实质性的工作,包括启动RPC服务,启动web服务和构造FSNamesystem对象。RPC留在最后讲,这里先进一下FSNamesystem类。

FSNamesystem类里有几个重要的数据结构,它们对应的属性是BlocksMap、CurruptReplicasMap、DatanodeMap、RecentInvalidateSets、ExcessReplicateMap、Heartbeats。BlocksMap是BlocksMap类的对象,它保存了block和blockInfo的映射(在apache的0.20.2版本里保存的是blockInfo到blockInfo的映射,),BlocksMap类(cdh3u1版)的实现与JDK里HashMap的实现类似,里面也有一个内部类用于存放实际的element,在构造BlocksMap对象的时候会计算出系统所能所能管理的block的数量,该数量是用JVM的位数和能使用的最大内存计算出来的。CurruptReplicasMap也是一个map,它保存了所有损坏的block,只有block的所有副本都损坏了,该block才被认为是损坏的,如果后来又收到了该block的副本report,那么会从CurruptReplicasMap里remove掉该block。DatanodeMap里保存了所有的曾经连接过该Namenode的datanode的描述,用DatanodeDescriptor类表示。

RecentInvalidateSets保存了最近失效的block,这些block被认为放在出现了某些问题的机器上。ExcessReplicateMap保存了机器与该机器上多余的block的映射,这些block最终会被删除掉。Heartbeats里保存了当前发送心跳包的datanode节点,它是DatanodeMap的一个子集。

除了以上讲的数据结构外,FSNamesystem里还有一个重要的属性dir,它是FSDirectory的对象,这个类维护了分布式文件系统的层次结构,它内部有一个属性rootDir,INodeDirectoryWithQuota类型的,代表了文件系统的根目录。这里先讲一下层次结构的组成。Hadoop里有一个INode类,该类是构造文件系统层次结构最基础的一个类,它包含了目录和文件共有的一些属性,比如访问时间,最近修改时间,权限等。从INode类派生下来的有INodeDirectory和INodeFile两个类,分别代表了目录和文件。INodeDirectory里有一个List类型的成员children,正是有了这个成员才构筑起了文件系统的层次结构;由于文件内部不可能还有文件或目录,所以INodeFile里没有类似的成员;但是Hadoop里文件是分成许多block的,所以INodeFile里维护了一个BlockInfo的数组。另外INodeDirectoryWithQuota继承了INodeDirectory,INodeFileUnderConstruction继承了INodeFile;前者表示一个带有配额的目录,后者表示正在被客户端修改的文件,它内部保存了文件与客户端的租约。综上所述,INode,INodeDirectory,INodeFile,INodeDirectoryWithQuota,INodeFileUnderConstruction五个类共同构筑起了HDFS的文件层次结构。

讲了hadoop是怎样构建起分布式文件系统的层次结构后,揭下来我们再来看看FSDirectory对象的创建。FSDirectory类里有一个FSImage类型的成员,而FSImage类里又有一个FSEditLog类型的成员;FSImage类代表了存放文件系统元数据的fsimage文件,FSEditLog代表了对文件系统的修改,对应的文件是edits。每次Namenode启动的时候会读取fsimage文件来构造文件系统的层次结构,而且还会将edits和fsimage合并为一个新的fsimage文件。这里要注意一下,hadoop的本地存放元数据以及检查点的目录由一个特别的类Storage来表示,FSImage类就继承了Storage类。我刚开始读源码的时候,总是以为Storage就表示了分布式文件系统的目录,其实不然,它表示的是本地的存放目录。Storage类继承了StorageInfo类,StorageInfo类里是一些目录的共有信息,包括创建时间,namespaceID等;在Storage类里,有一个枚举类型StorageState表示Storage的状态,还有一个接口StorageDirType表示Storage的类型,在FSImage类里实现了这个接口,从中可以看出Namenode Storage有三种类型,分别是EDITS,IMAGE,IMAGE_AND_EDITS。其实FSDirectory的创建的主要工作就是初始化fsimage对象,真正的读取fsimage文件的操作在FSDirectory类的loadFSImage(File)方法里,该方法读取fsimage文件来构造FSDirectory类里的rootDir成员,它代表了分布式文件系统的根目录。下面是读取fsimage文件的代码:




在FSNamesystem对象创建完后会启动几个后台守护线程,分别是PendingReplicaitonBlocks$PendingReplicationMonitor、FSNamesystem$HeartbeatMonitor,FSNamesystem$ReplicationMonitor、LeaseManager$Monitor、DecommissionManager$Monitor。HeartbeatMonitor会定期检查已注册的数据节点发来的心跳包,每次检测的时间间隔为heartbeatRecheckInterval ,可以通过heartbeat.recheck.interval来配置;该线程会根据上次发送心跳包的时间与现在的时间差来判断是否超出heartbeatExpireInterval,若超出则认为该数据节点已dead。FSNamesystem$ReplicationMonitor线程会监视整个集群的副本数量是否满足设定的值,如果某个block的副本不足,它会选择一些datanode去copy副本,副本复制是有优先级的,副本数量越少的block具有更高的优先级,系统会首先去copy这些block。当然副本的copy是需要时间的,系统是怎么知道副本copy成功的呢?这就是PendingReplicationMonitor线程的作用了,它内部有一个成员保存了正在copy的block的信息,它会定期去检查这些在某个数据节点上copy的副本是否copy成功,如果超出了设定的时间间隔,如果超出了,那么对应的block会被放入timedOutItems里,对于这些超时的block,replicationMonitor会再交给其他数据节点复制。DecommissionManager$Monitor会监视正在退役的数据节点的状态,当某个数据节点被标示为退役状态时,系统并不能马上让该数据节点退役,而是要等该数据节点上的block被复制完成后,才能允许它退役,DecommissionManager$Monitor就是用来检测这些数据节点上的block是否满足了副本因子,只有满足了副本因子,才能允许该节点真正地退役。最后讲一下LeaseManager$Monitor。首先我们要知道hdfs上的文件是不允许被多个客户端同时操作的,那hadoop是怎样避免冲突的呢?当然是通过leaseManager了。当客户端向Namenode发起请求要操作某个文件的时候,首先会得到了一个文件租约lease,一个文件租约和一个文件,一个客户端相关联;若此时其他客户端要想操作这个文件,那么它得不到这个租约,也就不能操作该文件,这样就避免了多客户端同时操作某个文件。客户端会定期去更新租约的开始时间使得该客户端可以持续操作这个文件。当客户端释放这个文件的时候,客户端也会发请求告诉服务端删除掉该租约。在客户端突然宕机来不及告诉Namenode释放租约的情况下,这些租约会过期。LeaseManager$Monitor会检查那些过期的租约并释放掉,好让其他客户端能操作该文件。

最后再来谈谈hadoop的RPC框架。首先我们要搞清楚RPC框架类的继承结构。有关RPC框架所涉及的类都在ipc包下。ipc包下有三个主要的类,分别是Server,Client和RPC;RPC类里还有一个Server内部类,该内部类继承了ipc下的Server类。这里我讲的只涉及到RPC框架的服务端实现,也就是Server类和RPC类,不会涉及到Client类。在Server类里,存在Call,Listener,Responer,Connection,Handler这几个内部类;在Listener类里还有一个Reader内部类。下图是我从网上找的RPC机制图:


当一个请求到达时,listener会选取一个reader并调用该reader得registerChannel方法将得到的socketChannel注册到reader的selector中。然后构造一个Connection,将该connection放入Server类里的connectionList成员里。源代码如下:


接下来就到了reader线层的执行了,在reader的run方法里会调用listener的doRead(SelectionKey)方法,然后从selectionKey里得到Connection对象,最后再去调用connection对象的readAndProcess()方法,之后还调用了connection对象的几个方法,最后再connection的processData方法里创建一个call对象,加入到server类的call队列里。接下来就到了Handler线层的执行了。在handler的run方法里会从call队列中取出一个call对象,然后用这个call的信息去调用Server类的call方法得到一个value(其实是调用了server类保存的Namenode实例的方法,通常情况下,会调用到FSNamesystem类的方法上去)。最后调用setupResponse方法将value以及这次调用所产生的其他信息,如错误信息等赋值给call对象的response属性,再调用Responser的doResponse方法将这个call加入到responseQuene中。最后就剩下一个Responser了,该线程的工作很简单,也就是从responseQuene中取出call,将这次调用的结果发回给客户端,再关闭连接而已。

原文链接:Namenode启动过程分析(责编:Arron)

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐