• HDFS源码分析心跳汇报之整体结构


    我们知道,HDFS全称是Hadoop Distribute FileSystem,即Hadoop分布式文件系统。既然它是一个分布式文件系统,那么肯定存在很多物理节点,而这其中,就会有主从节点之分。在HDFS中,主节点是名字节点NameNode,它负责存储整个HDFS中文件元数据信息,保存了名字节点第一关系和名字节点第二关系。名字节点第一关系是文件与数据块的对应关系,在HDFS正常运行期间,保存在NameNode内存和FSImage文件中,并且在NameNode启动时就由FSImage加载,之后的修改则保持在内容和FSEdit日志文件中,而第二关系则是数据块与数据节点的对应关系,它并非由名字节点的FSImage加载而来,而是在从节点DataNode接入集群后,由其发送心跳信息汇报给主节点NameNode。

            那么,何为心跳呢?心跳就是HDFS中从节点DataNode周期性的向名字节点DataNode做汇报,汇报自己的健康情况、负载状况等,并从NameNode处领取命令在本节点执行,保证NameNode这一HDFS指挥官熟悉HDFS的全部运行情况,并对从节点DataNode发号施令,以完成来自外部的数据读写请求或内部的负载均衡等任务。

            我们知道,Hadoop2.x版本中,做了两个比较大的改动:一是引入了联邦的概念,允许一个HDFS集群提供多个命名空间服务,第二个是利用HA解决了NameNode单点故障问题,引入了Active NN和Standby NN的概念。

            本篇文章,结合Hadoop2.6.0的源码,我们先来看下心跳汇报的整体结构。

            众所周知,心跳汇报是从节点DataNode主动发起的周期性向主节点NameNode汇报的一个动作,所以这个问题的突破口自然而然就落在了数据节点DataNode上了。在DataNode内部,有这么一个成员变量blockPoolManager,定义如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. // 每个DataNode上都有一个BlockPoolManager实例  
    2. private BlockPoolManager blockPoolManager;  

            它是每个DataNode上都会存在的BlockPoolManager实例。那么这个BlockPoolManager是什么呢?看下它的定义及成员变量就知道了,代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * Manages the BPOfferService objects for the data node. 
    3.  * Creation, removal, starting, stopping, shutdown on BPOfferService 
    4.  * objects must be done via APIs in this class. 
    5.  *  
    6.  * 为DataNode节点管理BPOfferService对象。 
    7.  * 对于BPOfferService对象的创建、移除、启动、停止等操作必须通过该类的api来完成。 
    8.  */  
    9. @InterfaceAudience.Private  
    10. class BlockPoolManager {  
    11.   private static final Log LOG = DataNode.LOG;  
    12.     
    13.   // NameserviceId与BPOfferService的对应关系  
    14.   private final Map<String, BPOfferService> bpByNameserviceId =  
    15.     Maps.newHashMap();  
    16.     
    17.   // BlockPoolId与BPOfferService的对应关系  
    18.   private final Map<String, BPOfferService> bpByBlockPoolId =  
    19.     Maps.newHashMap();  
    20.     
    21.   // 所有的BPOfferService  
    22.   private final List<BPOfferService> offerServices =  
    23.     Lists.newArrayList();  
    24.   
    25.   // DataNode实例dn  
    26.   private final DataNode dn;  
    27.   
    28.   //This lock is used only to ensure exclusion of refreshNamenodes  
    29.   // 这个refreshNamenodesLock仅仅在refreshNamenodes()方法中被用作互斥锁  
    30.   private final Object refreshNamenodesLock = new Object();  
    31.     
    32.   // 构造函数  
    33.   BlockPoolManager(DataNode dn) {  
    34.     this.dn = dn;  
    35.   }  
    36. }  

            由类的注释我们可以知道,BlockPoolManager为DataNode节点管理BPOfferService对象。对于BPOfferService对象的创建、移除、启动、停止等操作必须通过类BlockPoolManager的API来完成。而且,BlockPoolManager中主要包含如下几个数据结构:

            1、保存nameserviceId与BPOfferService的对应关系的HashMap:bpByNameserviceId;

            2、保存blockPoolId与BPOfferService的对应关系的HashMap:bpByBlockPoolId;

            3、保存所有BPOfferService的ArrayList:offerServices;

            4、DataNode实例dn;

            5、refreshNamenodes()方法中用于线程间同步或互斥锁的Object:refreshNamenodesLock。

            由前三个成员变量,我们可以清楚的知道,BlockPoolManager主要维护的就是该DataNode上的BPOfferService对象,及其所属nameserviceId、blockPoolId。nameserviceId我们可以理解为HDFS集群中某一特定命名服务空间的唯一标识,blockPoolId则对应为该命名服务空间中的一个块池,或者说一组数据块的唯一标识,那么,什么是BPOfferService呢?我们继续往下看BPOfferService的源码,看下它类的定义及其成员变量,代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * One instance per block-pool/namespace on the DN, which handles the 
    3.  * heartbeats to the active and standby NNs for that namespace. 
    4.  * This class manages an instance of {@link BPServiceActor} for each NN, 
    5.  * and delegates calls to both NNs.  
    6.  * It also maintains the state about which of the NNs is considered active. 
    7.  *  
    8.  * DataNode上每个块池或命名空间对应的一个实例,它处理该命名空间到对应活跃或备份状态NameNode的心跳。 
    9.  * 这个类管理每个NameNode的一个BPServiceActor实例,在两个NanmeNode之间调用。 
    10.  * 它也保存了哪个NameNode是active状态。 
    11.  */  
    12. @InterfaceAudience.Private  
    13. class BPOfferService {  
    14.   static final Log LOG = DataNode.LOG;  
    15.   
    16.   /** 
    17.    * Information about the namespace that this service 
    18.    * is registering with. This is assigned after 
    19.    * the first phase of the handshake. 
    20.    *  
    21.    * 该服务登记的命名空间信息。第一阶段握手即被分配。 
    22.    * NamespaceInfo中有个blockPoolID变量,显示了NameSpace与blockPool是一对一的关系 
    23.    */  
    24.   NamespaceInfo bpNSInfo;  
    25.   
    26.   /** 
    27.    * The registration information for this block pool. 
    28.    * This is assigned after the second phase of the 
    29.    * handshake. 
    30.    *  
    31.    * 块池的注册信息,在第二阶段握手被分配 
    32.    */  
    33.   volatile DatanodeRegistration bpRegistration;  
    34.     
    35.   /** 
    36.    * 服务所在DataNode节点 
    37.    */  
    38.   private final DataNode dn;  
    39.   
    40.   /** 
    41.    * A reference to the BPServiceActor associated with the currently 
    42.    * ACTIVE NN. In the case that all NameNodes are in STANDBY mode, 
    43.    * this can be null. If non-null, this must always refer to a member 
    44.    * of the {@link #bpServices} list. 
    45.    *  
    46.    * 与当前活跃NameNode相关的BPServiceActor引用 
    47.    */  
    48.   private BPServiceActor bpServiceToActive = null;  
    49.     
    50.   /** 
    51.    * The list of all actors for namenodes in this nameservice, regardless 
    52.    * of their active or standby states. 
    53.    * 该命名服务对应的所有NameNode的BPServiceActor实例列表,不管NameNode是活跃的还是备份的 
    54.    */  
    55.   private final List<BPServiceActor> bpServices =  
    56.     new CopyOnWriteArrayList<BPServiceActor>();  
    57.   
    58.   /** 
    59.    * Each time we receive a heartbeat from a NN claiming to be ACTIVE, 
    60.    * we record that NN's most recent transaction ID here, so long as it 
    61.    * is more recent than the previous value. This allows us to detect 
    62.    * split-brain scenarios in which a prior NN is still asserting its 
    63.    * ACTIVE state but with a too-low transaction ID. See HDFS-2627 
    64.    * for details.  
    65.    *  
    66.    * 每次我们接收到一个NameNode要求成为活跃的心跳,都会在这里记录那个NameNode最近的事务ID,只要它 
    67.    * 比之前的那个值大。这要求我们去检测裂脑的情景,比如一个之前的NameNode主张保持着活跃状态,但还是使用了较低的事务ID。 
    68.    */  
    69.   private long lastActiveClaimTxId = -1;  
    70.   
    71.   // 读写锁mReadWriteLock  
    72.   private final ReentrantReadWriteLock mReadWriteLock =  
    73.       new ReentrantReadWriteLock();  
    74.     
    75.   // mReadWriteLock上的读锁mReadLock  
    76.   private final Lock mReadLock  = mReadWriteLock.readLock();  
    77.     
    78.   // mReadWriteLock上的写锁mWriteLock  
    79.   private final Lock mWriteLock = mReadWriteLock.writeLock();  
    80.   
    81.   // utility methods to acquire and release read lock and write lock  
    82.   void readLock() {  
    83.     mReadLock.lock();  
    84.   }  
    85.   
    86.   void readUnlock() {  
    87.     mReadLock.unlock();  
    88.   }  
    89.   
    90.   void writeLock() {  
    91.     mWriteLock.lock();  
    92.   }  
    93.   
    94.   void writeUnlock() {  
    95.     mWriteLock.unlock();  
    96.   }  
    97.   
    98.   BPOfferService(List<InetSocketAddress> nnAddrs, DataNode dn) {  
    99.     Preconditions.checkArgument(!nnAddrs.isEmpty(),  
    100.         "Must pass at least one NN.");  
    101.     this.dn = dn;  
    102.   
    103.     // 每个namenode一个BPServiceActor  
    104.     for (InetSocketAddress addr : nnAddrs) {  
    105.       this.bpServices.add(new BPServiceActor(addr, this));  
    106.     }  
    107.   }  
    108. }  

            由上述代码我们可以得知,BPOfferService为DataNode上每个块池或命名空间对应的一个实例,它处理该命名空间到对应活跃或备份状态NameNode的心跳。这个类管理每个NameNode的一个BPServiceActor实例,同时它也保存了哪个NameNode是active状态。撇开其他成员变量先不说,该类有两个十分重要的成员变量,分别是:

            1、bpServiceToActive:BPServiceActor类型的,表示与当前活跃NameNode相关的BPServiceActor引用;

            2、bpServices:CopyOnWriteArrayList<BPServiceActor>类型的列表,表示该命名服务对应的所有NameNode的BPServiceActor实例列表,不管NameNode是活跃的还是备份的。

            由此可以看出,BPOfferService实际上是每个命名服务空间所对应的一组BPServiceActor的管理者,这些BPServiceActor全部存储在bpServices列表中,并且由bpServices表示当前与active NN连接的BPServiceActor对象的引用,而bpServices对应的则是连接到所有NN的BPServiceActor,无论这个NN是active状态还是standby状态。那么,问题又来了?BPServiceActor是什么呢?继续吧!

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.  * A thread per active or standby namenode to perform: 
    3.  * <ul> 
    4.  * <li> Pre-registration handshake with namenode</li> 
    5.  * <li> Registration with namenode</li> 
    6.  * <li> Send periodic heartbeats to the namenode</li> 
    7.  * <li> Handle commands received from the namenode</li> 
    8.  * </ul> 
    9.  *  
    10.  * 每个活跃active或备份standby状态NameNode对应的线程,它负责完成以下操作: 
    11.  * 1、与NameNode进行预登记握手; 
    12.  * 2、在NameNode上注册; 
    13.  * 3、发送周期性的心跳给NameNode; 
    14.  * 4、处理从NameNode接收到的请求。 
    15.  */  
    16. @InterfaceAudience.Private  
    17. // 实现Runnable接口意味着BPServiceActor是一个线程  
    18. class BPServiceActor implements Runnable {  
    19.     
    20.   static final Log LOG = DataNode.LOG;  
    21.     
    22.   // NameNode地址  
    23.   final InetSocketAddress nnAddr;  
    24.     
    25.   // HA服务状态  
    26.   HAServiceState state;  
    27.   
    28.   // BPServiceActor线程所属BPOfferService  
    29.   final BPOfferService bpos;  
    30.     
    31.   // lastBlockReport, lastDeletedReport and lastHeartbeat may be assigned/read  
    32.   // by testing threads (through BPServiceActor#triggerXXX), while also   
    33.   // assigned/read by the actor thread. Thus they should be declared as volatile  
    34.   // to make sure the "happens-before" consistency.  
    35.   volatile long lastBlockReport = 0;  
    36.   volatile long lastDeletedReport = 0;  
    37.   
    38.   boolean resetBlockReportTime = true;  
    39.   
    40.   volatile long lastCacheReport = 0;  
    41.   
    42.   Thread bpThread;  
    43.   DatanodeProtocolClientSideTranslatorPB bpNamenode;  
    44.   private volatile long lastHeartbeat = 0;  
    45.   
    46.   /** 
    47.    * 枚举类,运行状态,包括 
    48.    * CONNECTING 正在连接 
    49.    * INIT_FAILED 初始化失败 
    50.    * RUNNING 正在运行 
    51.    * EXITED 已退出 
    52.    * FAILED 已失败 
    53.    */  
    54.   static enum RunningState {  
    55.     CONNECTING, INIT_FAILED, RUNNING, EXITED, FAILED;  
    56.   }  
    57.   
    58.   // 运行状态runningState默认为枚举类RunningState的CONNECTING,表示正在连接  
    59.   private volatile RunningState runningState = RunningState.CONNECTING;  
    60.   
    61.   /** 
    62.    * Between block reports (which happen on the order of once an hour) the 
    63.    * DN reports smaller incremental changes to its block list. This map, 
    64.    * keyed by block ID, contains the pending changes which have yet to be 
    65.    * reported to the NN. Access should be synchronized on this object. 
    66.    *  
    67.    *  
    68.    */  
    69.   private final Map<DatanodeStorage, PerStoragePendingIncrementalBR>  
    70.       pendingIncrementalBRperStorage = Maps.newHashMap();  
    71.   
    72.   // IBR = Incremental Block Report. If this flag is set then an IBR will be  
    73.   // sent immediately by the actor thread without waiting for the IBR timer  
    74.   // to elapse.  
    75.   private volatile boolean sendImmediateIBR = false;  
    76.   private volatile boolean shouldServiceRun = true;  
    77.   private final DataNode dn;  
    78.   private final DNConf dnConf;  
    79.   
    80.   private DatanodeRegistration bpRegistration;  
    81.   
    82.   // 构造方法,BPServiceActor被创建时就已明确知道NameNode地址InetSocketAddress类型的nnAddr,和BPOfferService类型的bpos  
    83.   BPServiceActor(InetSocketAddress nnAddr, BPOfferService bpos) {  
    84.     this.bpos = bpos;  
    85.     this.dn = bpos.getDataNode();  
    86.     this.nnAddr = nnAddr;  
    87.     this.dnConf = dn.getDnConf();  
    88.   }  
    89. }  

            聪明的您,是不是一眼就能看出,BPServiceActor就是实际与某个特定NameNode通信的工作线程呢?它是每个活跃active或备份standby状态NameNode对应的线程,它负责完成以下操作:

            1、与NameNode进行预登记握手;

            2、在NameNode上注册;

            3、发送周期性的心跳给NameNode;

            4、处理从NameNode接收到的请求。

            关于BPServiceActor的具体实现,我们放到以后再讲,下面我们再折回去,稍微总结下HDFS心跳的整体架构,忽略掉部分细节后,大体架构如图所示:

            首先,每个DataNode上都有一个BlockPoolManager实例;

            其次,每个BlockPoolManager实例管理着所有命名服务空间对应的BPOfferService实例:命名服务空间你可以理解为HDFS中逻辑意义上的某个单独的文件系统;

            然后,每个BPOfferService实例则管理者它所对应命名服务空间内到所有NameNode的BPServiceActor工作线程:包含一个Active与若干Standby状态的NN;

            最后,BPServiceActor对应的是针对特定的NameNode进行通讯和完成心跳与接收响应命令的工作线程。

            上述就是HDFS中心跳汇报的整体结构,由DataNode上BlockPoolManager、BPOfferService和BPServiceActor等三层架构实现,由上到下体现了HDFS中存在多个命名服务空间NameService,每个命名服务空间NameService对应着一个BPOfferService,它负责管理多个BPServiceActor工作线程,每个BPServiceActor则是DataNode上具体与每个NameNode通信完成心跳的工作线程,而这些对应关系,特别是HDFS上有多少命名服务NS,每个命名服务涉及哪些名字节点NN,则是从HDFS的配置文件中获取的。

            关于HDFS中心跳涉及的数据结构如何初始化,我们下节再讲!

  • 相关阅读:
    spring原理
    mybatis原理
    数据结构与算法
    JVM内存模型及垃圾回收算法
    dorado动态修改数据验证
    dorado在dialog中使用js通过控件id修改控件值,值闪烁一下消失问题
    由于;引发的Oracle的BadSqlExecption
    swagger配置
    SpringBoot整合mybatis碰到的问题
    关于浏览器的自动缓存问题
  • 原文地址:https://www.cnblogs.com/jirimutu01/p/5556193.html
Copyright © 2020-2023  润新知