Hadoop-0.20.0源代码分析(15)

我们已经分析了org.apache.hadoop.hdfs.server.namenode.Namenode类的实现,而且知道,一个Namenode提供的主要服务是基于其内部定义的org.apache.hadoop.hdfs.server.namenode.FSNamesystem属性来实现的。可见,org.apache.hadoop.hdfs.server.namenode.FSNamesystem在具备了Namenode所提供基本服务的基础上,也可以料想到它实现的复杂性。

在阅读分析FSNamesystem类的源代码之前,还是先对与该类相关的一些其它类进行了解,也是为了更好地理解FSNamesystem类的实现。

  • Host2NodesMap类

该类org.apache.hadoop.hdfs.server.namenode.Host2NodesMap用来保存Datanode结点的主机 -> DatanodeDescriptor数组的映射的类,其实一个DatanodeDescriptor中已经包含了Datanode所在主机的字符串的信息,该类设置了一个HashMap<String, DatanodeDescriptor[]>,如下所示:

  1. private HashMap<String, DatanodeDescriptor[]> map = new HashMap<String, DatanodeDescriptor[]>();  

可见,对于一个Datanode结点,也就是对应的一台主机上,可以存在多个Datanode进程,因此存在一个Datanode主机到一组Datanode进程信息描述的映射。但是一般来说,一个主机上只单独设置一个Datanode进程。

通过上面叙述,可以想到,作为一个Host2NodesMap类,应该提供向map映射表中添加<Host, DatanodeDescriptor[]>对的操作,删除指定Host对应的DatanodeDescriptor[],获取到指定Host对应的DatanodeDescriptor[]信息,等等。

  • NetworkTopology类

该类org.apache.hadoop.net.NetworkTopology表示一个具有树状网络拓扑结构的计算机集群,例如,一个集群可能由多个数据中心(Data Center)组成,在这些数据中心分布着为计算需求而设置的很多计算机的机架(Rack)。在该类中定义了一个org.apache.hadoop.net.NetworkTopology.InnerNode类,表示数据中心(或机架)的转换器(或路由),该内部类继承层次关系如下所示:

  1. ◦org.apache.hadoop.net.NodeBase(implements org.apache.hadoop.net.Node)  
  2.      ◦org.apache.hadoop.net.NetworkTopology.InnerNode  

1、Node接口

org.apache.hadoop.net.Node接口表示网络拓扑中的结点的抽象,一个Node可能是一个Datanode,也可能是一个表示数据中心或机架的内部结点。每一个Node在网络拓扑中应该有一个名称及其位置(使用类似文件路径的方式来定义),例如,如果一个Datanode名称为hostname:port,并且该Datanode在数据中心dog里的orange机架上,则这个Datanode在网络拓扑中的位置(网络地址)为/dog/orange,下面是Node接口的定义:

  1. package org.apache.hadoop.net;  
  2.   
  3. public interface Node {  
  4.   /** 返回表示该结点的网络地址的字符串 */  
  5.   public String getNetworkLocation();  
  6.   /** 设置该结点的网络地址字符串 */  
  7.   public void setNetworkLocation(String location);  
  8.   /** 获取该结点的名称 */  
  9.   public String getName();  
  10.   /** 获取该结点的父结点 */  
  11.   public Node getParent();  
  12.   /** 设置该结点的父结点 */  
  13.   public void setParent(Node parent);  
  14.   /**  
  15.    * 获取该结点在网络拓扑中的层次,例如,如果是树状网络拓扑的根则返回0,树状网络拓扑的根的直接孩子的层次为1 
  16.    */  
  17.   public int getLevel();  
  18.   /** 设置该结点在网络拓扑中的层次*/  
  19.   public void setLevel(int i);  
  20. }  

2、NodeBase类

该类实现了Node接口,是一个最基本的结点的实现。下面是该类定义的属性,都是与一个结点的基本属性信息相关的:

  1. public final static char PATH_SEPARATOR = '/'// 路径分隔符   
  2. public final static String PATH_SEPARATOR_STR = "/";  
  3. public final static String ROOT = ""// 网络拓扑的根结点   
  4.   
  5. protected String name; // host:port#   
  6. protected String location; // 该结点的网络位置   
  7. protected int level; // 该结点在网络拓扑中的层次   
  8. protected Node parent; // 该结点的父结点  

该类定义的方法就不多说了,就是实现了Node接口中定义的基本方法。

3、NetworkTopology.InnerNode类

该类表示一个数据中心(或机架)的转换器(或路由),它不同于网络拓扑中的叶结点(主机),因此它具有非空的孩子结点。该内部结点定义了如下两个属性:

  1. private ArrayList<Node> children=new ArrayList<Node>();  
  2. private int numOfLeaves;  

作为内部结点,它是其它节点之间连接的桥梁。上面属性children列表表示该内部结点所维护的孩子结点的列表,numOfLeaves用来记录该结点所维护的结点的数量。

现在,我们可以分析NetworkTopology类的实现了。该类定义了如下属性:

  1. public final static String DEFAULT_RACK = "/default-rack"// 默认机架名称   
  2. public final static int DEFAULT_HOST_LEVEL = 2// 主机层次   
  3.   
  4. InnerNode clusterMap = new InnerNode(InnerNode.ROOT); // 定义网络拓扑的根结点   
  5. private int numOfRacks = 0;  // 机架计数   
  6. private ReadWriteLock netlock; // 读写锁  

下面,对一个网络拓扑实例可以执行的操作,及其功能描述列表如下:

  1. /**  
  2.  * 向网络中加入一个(叶)结点 
  3.  * 如果必要的话,更新结点计数和机架计数 
  4.  */  
  5. public void add(Node node);  
  6.   
  7. /**  
  8.  * 删除一个结点 
  9.  */   
  10. public void remove(Node node);  
  11.   
  12. /**  
  13.  * 判断当前网络中是否包含指定节点node 
  14.  */  
  15. public boolean contains(Node node);  
  16.   
  17. /**  
  18.  * 获取指定位置所表示的结点,返回该结点的引用 
  19.  */  
  20. public Node getNode(String loc);  
  21.   
  22. /** 返回网络中机架总数 */  
  23. public int getNumOfRacks();  
  24.   
  25. /** 返回总结点数量 */  
  26. public int getNumOfLeaves();  
  27.   
  28. /**  
  29.  * 返回两个节点之间的距离 
  30.  * 假定某个结点到其父结点之间的距离为1 
  31.  */  
  32. public int getDistance(Node node1, Node node2);  
  33.   
  34. /**  
  35.  * 两个结点是否在同一个机架上 
  36.  */  
  37. public boolean isOnSameRack( Node node1,  Node node2);  
  38.   
  39. /**  
  40.  * 从指定范围scope内,随机选择一个结点并返回 
  41.  */  
  42. public Node chooseRandom(String scope);  
  43.   
  44. /**  
  45.  * 返回指定范围scope内,且不在excludedNodes列表中的可用结点的数量 
  46.  */  
  47. public int countNumOfAvailableNodes(String scope, List<Node> excludedNodes);  
  48.   
  49. /**  
  50.  * 根据网络中到结点reader的距离,对nodes结点数组进行排序 
  51.  */  
  52. public void pseudoSortByDistance( Node reader, Node[] nodes );  

  • DNSToSwitchMapping接口类

该接口是一个支持插件的定义,通过插件定义DNS-name/IP-address -> RackID之间转换的解析器。该接口定义了如下一个解析的方法:

  1. /** 
  2.  * 解析给定列表中DNS-names/IP-addresses,返回一个经过解析以后的网络路径的列表 
  3.  *    
  4.  * 例如,列表中带解析的一个域名x.y.com,转换后应该是一个网络路径,形如/foo/rack, 其中/表示根,foo是连接到rack的转换器。 
  5.  * 
  6.  * 注意:hostname/ip-address并不是经过解析返回路径的一部分,一个集群网络拓扑实例应该能够确定在该网络中的组件的数量。 
  7.  */  
  8. public List<String> resolve(List<String> names);  

实现该接口的类的继承层次关系如下:

 

  1. ◦org.apache.hadoop.net.StaticMapping(implements org.apache.hadoop.net.DNSToSwitchMapping)  
  2. ◦org.apache.hadoop.net.ScriptBasedMapping.RawScriptBasedMapping(implements org.apache.hadoop.net.DNSToSwitchMapping)  
  3. ◦org.apache.hadoop.net.CachedDNSToSwitchMapping(implements org.apache.hadoop.net.DNSToSwitchMapping)  
  4.      ◦org.apache.hadoop.net.ScriptBasedMapping  

这里,我们只看CachedDNSToSwitchMapping类的实现,该类将解析DNS-name/IP-address -> RackID,并将解析后的RackID缓存起来。

该类定义了一个缓存:

  1. private Map<String, String> cache = new ConcurrentHashMap<String, String>();  

通过该类内部定义的一个DNSToSwitchMapping实例:

  1. protected DNSToSwitchMapping rawMapping;  
 

来解析输入列表中的DNS-name/IP-address,然后通过解析获取到指定的DNS-name/IP-address所对应的RackID,并将DNS-name/IP-address -> RackID键值对加入到缓存cache中。

对于一个DNSToSwitchMapping接口的实现类,例如RawScriptBasedMapping,是从与Hadoop配置文件相关的配置类实例来获取到解析DNS-name/IP-address -> RackID映射的脚本文件,通过执行脚本来实现解析。关于具体实现可以查看对应的实现类的源代码。

  • ReplicationTargetChooser类

该类org.apache.hadoop.hdfs.server.namenode.ReplicationTargetChooser是对指定的块副本的存放位置进行定位选择的实现类。

对指定块副本的存放位置进行定位选择的基本策略是:如果一个写操作是在一个Datanode上,第一个块副本被存放在本地机器上,否则就随机选择一个Datanode来存放第一个块副本;第二个块副本存放在与第一个块副本不同的机架的一个Datanode上;第三个块副本存放在与第一个块副本相同的机架上,也就是同一个Datanode上。

该类主要就是针对块副本的存放位置的选择,因此在选择指定机架的过程中,需要获取到指定Datanode的DatanodeDescriptor信息,通过它来验证是否可以存放块副本。在三个块副本都已经存放好以后,可能还需要对指定块的块副本进行检查验证,是否满足实际的存储需要,比如满足副本因子。

  • HostsFileReader类

该类org.apache.hadoop.util.HostsFileReader用来跟踪Datanode的,哪些Datanode允许连接到Namenode,哪些不能够连接到Namenode,都在该类中指定的列表中记录着,如下所示:

  1. private Set<String> includes; // 允许连接到Namenode的Datanode列表   
  2. private Set<String> excludes; // 不允许连接到Namenode的Datanode列表   
  3. private String includesFile;  // 允许连接到Namenode的Datanode记录文件   
  4. private String excludesFile;  // 不允许连接到Namenode的Datanode记录文件  

该类能够将的Datanode记录列表加载的内存中,这是通过refresh方法,在该方法中调用readFileToSet方法实现的,以此刷新内存中的includes与excludes列表。 

  • GenerationStamp类

该类org.apache.hadoop.hdfs.server.common.GenerationStamp是Hadoop文件系统的时间戳的实现,通过它可以设置时间戳。它实现了WritableComparable接口,因此又是可序列化、可进行比较的。

  • BlocksMap类

通过上面,已经知道,FSNamesystem跟踪几个重要的数据映射表,其中BlocksMap类维护块(Block)到其元数据的映射表,元数据信息包括块所属的inode、存储块的Datanode。

在该类内部,定义了一个块元数据的实体类org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo,该实体类包含了块所属的inode与存储块的Datanode。该内部类继承自org.apache.hadoop.hdfs.protocol.Block类,Block是块的基本抽象,其中包含了块的下面三个属性信息:

  1. private long blockId; // 块ID   
  2. private long numBytes; // 块大小   
  3. private long generationStamp; // 时间戳  

并且,由于Block实现了org.apache.hadoop.io.Writable接口,因此Block类是可序列化的。

再看内部类org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo,该内部类中定义了两个属性:

  1. private INodeFile          inode; // 文件结点   
  2. private Object[] triplets; // 文件的块列表  

org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo的第一个是inode属性,简单看一下INodeFile类的继承层次关系:

  1. ◦java.lang.Comparable<byte[]>  
  2.     ◦org.apache.hadoop.hdfs.server.namenode.INode  
  3.         ◦org.apache.hadoop.hdfs.server.namenode.INodeFile  

其中,INode表示了一个文件或者目录的inode,包括如下属性:

  1. protected byte[] name; // 文件(或目录)名称   
  2. protected INodeDirectory parent; // 父目录   
  3. protected long modificationTime; // INode修改时间   
  4. protected long accessTime; // 访问时间   
  5. private long permission;  
  

INodeFile继承自INode类,又增加了如下几个描述块信息的属性:

  1. protected BlockInfo blocks[] = null// 块列表   
  2. protected short blockReplication; // 块副本因子   
  3. protected long preferredBlockSize; // 标准块大小  
 

org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo的第二个属性是一个Object[] triplets数组,该数组包含了块的三组引用。具体信息描述如下:

对于块所属的第i个Datanode,数组元素triplets[3*i]引用了描述该Datanode结点的DatanodeDescriptor实例,数组元素triplets[3*i+1]与triplets[3*i+2]引用的分别是前(previous)一个块与后(next)一个块。注意,triplets[3*i+1]与triplets[3*i+2]所引用的块应该在指定的同一个Datanode上的块列表中。

BlocksMap类只定义了一个映射表Map,如下所示:

  1. private Map<Block, BlockInfo> map = new HashMap<Block, BlockInfo>();  
 

围绕该映射表,该类中定义了一系列与该映射表相关的操作,包括实现的内部类org.apache.hadoop.hdfs.server.namenode.BlocksMap.BlockInfo.NodeIterator,内部类定义如下所示: 

  1. private static class NodeIterator implements Iterator<DatanodeDescriptor>  

该内部类用于迭代映射表map中块对应的元数据实体BlockInfo中封装的Datanode的状态的属性DatanodeDescriptor,从而获取该块所在的Datanode的状态。

  • CorruptReplicasMap类 

该类是有关失效块的映射表。如果一个块被认为是失效的,是指该块对应的全部块副本都失效而不可用。org.apache.hadoop.hdfs.server.namenode.CorruptReplicasMap类定义了一个有序Map映射表:

  1. private Map<Block, Collection<DatanodeDescriptor>> corruptReplicasMap =  
  2.   new TreeMap<Block, Collection<DatanodeDescriptor>>();  

表示,一个块对应的块副本可能在其它Datanode上的,corruptReplicasMap表示一个块到对应的全部块副本所在的Datanode集合的映射。

  • UnderReplicatedBlocks类

org.apache.hadoop.hdfs.server.namenode.UnderReplicatedBlocks类是描述某些块的副本数量不足块的实体类,而且,对于块设定了优先级,通过一个优先级队列来管理块副本不足的块的集合。权限为0的块,具有最高的优先级,而且,一个块的副本只能有一个具有最高优先级。看该类定义的优先级队列,如下所示:

  1. private List<TreeSet<Block>> priorityQueues = new ArrayList<TreeSet<Block>>();  

如果某些块副本数量不足,可以通过该队列来根据块的优先级执行块复制操作,以满足副本因子的要求。如果某个块的副本数量满足副本因子,则需要从队列中删除,更新统计数据。该类提供了想队列中添加和删除块等等基本操作。

  • PendingReplicationBlocks类

该类org.apache.hadoop.hdfs.server.namenode.PendingReplicationBlocks是描述当前尚未完成块副本复制的块的列表。因为HDFS支持块的流水线复制,当文件系统客户端开始向第一个Datanode复制数据块的时候,会在第一个Datanode上启动复制进程,将该结点上接收到的(部分)数据块开始向第二个Datanode上传输复制,而第二个Datanode结点又向第三个Datanode结点进行流水线复制,……,直到满足副本因子要求。所以,在执行流水线复制的过程中,因为这种可并行的复制方式使得某些块副本的复制完成时间呈现阶梯状,也就是使用一个数据结构来保存这些尚未复制完成的块副本,PendingReplicationBlocks就是,可以看到该类中定义了一个映射表:

  1. private Map<Block, PendingBlockInfo> pendingReplications;  

其中Block是块,作为映射表的键,而值是org.apache.hadoop.hdfs.server.namenode.PendingReplicationBlocks.PendingBlockInfo,该内部类定义了如下两个属性来表示执行流水线复过程中的元数据:

  1. private long timeStamp; // 时间戳   
  2. private int numReplicasInProgress; // 执行块的流水线复制的块副本数量  

而且,该类中还定义了一个内部后台线程类org.apache.hadoop.hdfs.server.namenode.PendingReplicationBlocks.PendingReplicationMonitor,用来监视流水线复制过程中块复制的进度情况。

  • LeaseManager类

该类org.apache.hadoop.hdfs.server.namenode.LeaseManager表示对文件的租约进行管理。很自然地,对于租约的表示与管理,在该类的内部定义了两个内部类:

Lease内部类:一个用来表示租约的实体类org.apache.hadoop.hdfs.server.namenode.LeaseManager.Lease,因为文件系统客户端需要向Namenode请求写数据(向Namenode结点写数据块),因此一个文件系统客户端必须持有一个Lease实例,该Lease实例包含文件系统客户端的持有身份(客户端名称)、最后更新Lease时间(用于判断是否超时)、所要写数据的路径(应该与指定的Datanode上文件系统的Path相关)。对于每一个文件系统客户端,都应该持有一个Lease,Lease管理一个客户端的全部锁(写锁),其中Lease中包含的最后更新时间需要文件系统客户端周期地检查来实现更新,这样写数据才不会因为超时而放弃一个Lease。当然,如果一个文件系统客户端发生故障,或者它不需要持有该Lease,也就是不需要执行文件的写操作,那么它会释放掉由它所持有的Lease管理的全部的锁,以便满足其它客户端的请求。

Monitor内部线程类:一个内部线程类org.apache.hadoop.hdfs.server.namenode.LeaseManager.Monitor,它是用来周期性地(每2s检查一次)检查LeaseManager管理器所维护的Lease列表中是否有Lease过期的文件系统客户端,如果过期则从Lease列表中删除掉。

基本可以清楚LeaseManager的是如何管理Lease,其中LeaseManager还提供了向它维护的列表中添加Lease、删除Lease、更新Lease等等操作。

  • SafeModeInfo类

该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.SafeModeInfo是FSNamesystem的一个内部类,定义了与安全模式相关的信息和操作。看该类定义的属性信息:

  1. // 配置Field,从与配置文件相关的配置类实例获取到这些属性值   
  2.   
  3. /** 只有当条件(是个比率)大于threshold的时候,才能进入安全模式*/  
  4. private double threshold;  
  5. /** 进入安全模式 */  
  6. private int extension;  
  7. /** 安全模式要求的最小副本因子 */  
  8. private int safeReplication;  
  9.     
  10. // 内部Field   
  11.   
  12. /**  
  13.  * 当达到threshold的值的时间 
  14.  * -1 离开安全模式 
  15.  * 0 正在安全模式下,但是threshold是不可达的  
  16.  */  
  17. private long reached = -1;    
  18. /** 块总数 */  
  19. int blockTotal;   
  20. /** 安全的块的总数 */  
  21. private int blockSafe;  
  22. /** 输出最后状态的时间 */  
  23. private long lastStatusReport = 0;  

下面看有关安全模式下的操作:

1、进入安全模式:

  1. void enter() {  
  2.   this.reached = 0;  
  3. }  

设置标志reached=0,表示进入安全模式。

2、离开安全模式:

  1.    synchronized void leave(boolean checkForUpgrades) {  
  2.      if(checkForUpgrades) {  
  3.        boolean needUpgrade = false// 是否是因为要启动分布式升级进程而离开安全模式   
  4.        try {  
  5.          needUpgrade = startDistributedUpgradeIfNeeded(); // 调用Namenode的升级管理器的操作来返回是否需要启动分布式升级进程   
  6.        } catch(IOException e) {  
  7.          FSNamesystem.LOG.error(StringUtils.stringifyException(e));  
  8.        }  
  9.        if(needUpgrade) { // 如果需要,进入手动安全模式   
  10.          safeMode = new SafeModeInfo();  
  11.          return;  
  12.        }  
  13.      }  
  14.      // 如果不需要启动安全升级进程   
  15.      processMisReplicatedBlocks(); // 验证安全模式下块副本是否满足要求,如果不满足根据实际情况进行处理,例如块副本过多,对其进行标识,然后等待时机删除多余的块副本   
  16.      long timeInSafemode = now() - systemStart;  
  17.      NameNode.stateChangeLog.info("STATE* Leaving safe mode after " + timeInSafemode/1000 + " secs.");  
  18.      NameNode.getNameNodeMetrics().safeModeTime.set((int) timeInSafemode);  
  19.        
  20.      if (reached >= 0) {  
  21.        NameNode.stateChangeLog.info("STATE* Safe mode is OFF.");   
  22.      }  
  23.      reached = -1// 设置离开安全模式   
  24.      safeMode = null;  
  25.      NameNode.stateChangeLog.info("STATE* Network topology has " +clusterMap.getNumOfRacks()+" racks and "  
  26. +clusterMap.getNumOfLeaves()+ " datanodes");  
  27.      NameNode.stateChangeLog.info("STATE* UnderReplicatedBlocks has " +neededReplications.size()+" blocks");  
  28.    }  

3、计算安全块比率:

  1. private float getSafeBlockRatio() {  
  2.   return (blockTotal == 0 ? 1 : (float)blockSafe/blockTotal);  
  3. }  

这个比率为blockSafe/blockTotal,即安全块数量与总块数量的比值,这个比率用于与threshold,当调用needEnter方法需要进入安全模式进行状态检查的时候,必须满足getSafeBlockRatio() < threshold才能进入安全模式。

其它还有一些与安全模式相关的操作,比如对块的检查等等操作。

  • SafeModeMonitor线程类

该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.SafeModeMonitor是一个后台线程类,主要用于在SafeModeInfo类中,用来周期性地检查是否达到离开安全模式的条件,因此,该线程必须在进入安全模式之后启动(也就是达到threshold)。

  • HeartbeatMonitor线程类

该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.HeartbeatMonitor是一个后台线程类,周期性地调用FSNamesystem类定义的heartbeatCheck方法,来监视Datanode结点发送的心跳状态信息,并做出处理,后面会对heartbeatCheck方法详细分析的。

  • ReplicationMonitor线程类

该类org.apache.hadoop.hdfs.server.namenode.FSNamesystem.ReplicationMonitor是一个后台线程类,周期性地调用FSNamesystem类定义的两个方法:

[c-sharp] view plain copy print ?
  1. computeDatanodeWork(); // 计算块副本数量,以制定计划并调度Datanode处理   
  2. processPendingReplications(); // 处理未完成块的流水线复制的副本  

具体实现在后面详细分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值