我们知道,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,定义如下:

  1. // 每个DataNode上都有一个BlockPoolManager实例
  2. private BlockPoolManager blockPoolManager;

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

  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. // NameserviceId与BPOfferService的对应关系
  13. private final Map<String, BPOfferService> bpByNameserviceId =
  14. Maps.newHashMap();
  15. // BlockPoolId与BPOfferService的对应关系
  16. private final Map<String, BPOfferService> bpByBlockPoolId =
  17. Maps.newHashMap();
  18. // 所有的BPOfferService
  19. private final List<BPOfferService> offerServices =
  20. Lists.newArrayList();
  21. // DataNode实例dn
  22. private final DataNode dn;
  23. //This lock is used only to ensure exclusion of refreshNamenodes
  24. // 这个refreshNamenodesLock仅仅在refreshNamenodes()方法中被用作互斥锁
  25. private final Object refreshNamenodesLock = new Object();
  26. // 构造函数
  27. BlockPoolManager(DataNode dn) {
  28. this.dn = dn;
  29. }
  30. }

由类的注释我们可以知道,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的源码,看下它类的定义及其成员变量,代码如下:

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

由上述代码我们可以得知,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是什么呢?继续吧!

  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. static final Log LOG = DataNode.LOG;
  20. // NameNode地址
  21. final InetSocketAddress nnAddr;
  22. // HA服务状态
  23. HAServiceState state;
  24. // BPServiceActor线程所属BPOfferService
  25. final BPOfferService bpos;
  26. // lastBlockReport, lastDeletedReport and lastHeartbeat may be assigned/read
  27. // by testing threads (through BPServiceActor#triggerXXX), while also
  28. // assigned/read by the actor thread. Thus they should be declared as volatile
  29. // to make sure the "happens-before" consistency.
  30. volatile long lastBlockReport = 0;
  31. volatile long lastDeletedReport = 0;
  32. boolean resetBlockReportTime = true;
  33. volatile long lastCacheReport = 0;
  34. Thread bpThread;
  35. DatanodeProtocolClientSideTranslatorPB bpNamenode;
  36. private volatile long lastHeartbeat = 0;
  37. /**
  38. * 枚举类,运行状态,包括
  39. * CONNECTING 正在连接
  40. * INIT_FAILED 初始化失败
  41. * RUNNING 正在运行
  42. * EXITED 已退出
  43. * FAILED 已失败
  44. */
  45. static enum RunningState {
  46. CONNECTING, INIT_FAILED, RUNNING, EXITED, FAILED;
  47. }
  48. // 运行状态runningState默认为枚举类RunningState的CONNECTING,表示正在连接
  49. private volatile RunningState runningState = RunningState.CONNECTING;
  50. /**
  51. * Between block reports (which happen on the order of once an hour) the
  52. * DN reports smaller incremental changes to its block list. This map,
  53. * keyed by block ID, contains the pending changes which have yet to be
  54. * reported to the NN. Access should be synchronized on this object.
  55. *
  56. *
  57. */
  58. private final Map<DatanodeStorage, PerStoragePendingIncrementalBR>
  59. pendingIncrementalBRperStorage = Maps.newHashMap();
  60. // IBR = Incremental Block Report. If this flag is set then an IBR will be
  61. // sent immediately by the actor thread without waiting for the IBR timer
  62. // to elapse.
  63. private volatile boolean sendImmediateIBR = false;
  64. private volatile boolean shouldServiceRun = true;
  65. private final DataNode dn;
  66. private final DNConf dnConf;
  67. private DatanodeRegistration bpRegistration;
  68. // 构造方法,BPServiceActor被创建时就已明确知道NameNode地址InetSocketAddress类型的nnAddr,和BPOfferService类型的bpos
  69. BPServiceActor(InetSocketAddress nnAddr, BPOfferService bpos) {
  70. this.bpos = bpos;
  71. this.dn = bpos.getDataNode();
  72. this.nnAddr = nnAddr;
  73. this.dnConf = dn.getDnConf();
  74. }
  75. }

聪明的您,是不是一眼就能看出,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中心跳涉及的数据结构如何初始化,我们下节再讲!

HDFS源码分析心跳汇报之整体结构的更多相关文章

  1. HDFS源码分析心跳汇报之数据结构初始化

    在<HDFS源码分析心跳汇报之整体结构>一文中,我们详细了解了HDFS中关于心跳的整体结构,知道了BlockPoolManager.BPOfferService和BPServiceActo ...

  2. HDFS源码分析心跳汇报之周期性心跳

    HDFS源码分析心跳汇报之周期性心跳,近期推出!

  3. HDFS源码分析心跳汇报之DataNode注册

    HDFS源码分析心跳汇报之DataNode注册,近期推出!

  4. HDFS源码分析心跳汇报之数据块汇报

    在<HDFS源码分析心跳汇报之数据块增量汇报>一文中,我们详细介绍了数据块增量汇报的内容,了解到它是时间间隔更长的正常数据块汇报周期内一个smaller的数据块汇报,它负责将DataNod ...

  5. HDFS源码分析心跳汇报之BPServiceActor工作线程运行流程

    在<HDFS源码分析心跳汇报之数据结构初始化>一文中,我们了解到HDFS心跳相关的BlockPoolManager.BPOfferService.BPServiceActor三者之间的关系 ...

  6. HDFS源码分析心跳汇报之数据块增量汇报

    在<HDFS源码分析心跳汇报之BPServiceActor工作线程运行流程>一文中,我们详细了解了数据节点DataNode周期性发送心跳给名字节点NameNode的BPServiceAct ...

  7. HDFS源码分析数据块汇报之损坏数据块检测checkReplicaCorrupt()

    无论是第一次,还是之后的每次数据块汇报,名字名字节点都会对汇报上来的数据块进行检测,看看其是否为损坏的数据块.那么,损坏数据块是如何被检测的呢?本文,我们将研究下损坏数据块检测的checkReplic ...

  8. HDFS源码分析数据块校验之DataBlockScanner

    DataBlockScanner是运行在数据节点DataNode上的一个后台线程.它为所有的块池管理块扫描.针对每个块池,一个BlockPoolSliceScanner对象将会被创建,其运行在一个单独 ...

  9. HDFS源码分析数据块复制监控线程ReplicationMonitor(一)

    ReplicationMonitor是HDFS中关于数据块复制的监控线程,它的主要作用就是计算DataNode工作,并将复制请求超时的块重新加入到待调度队列.其定义及作为线程核心的run()方法如下: ...

随机推荐

  1. python第三方库安装和卸载

    库的安装与卸载 pip install requests pip uninstall requests 查看安装好的库 pip list   第三方库的各种安装方式如下: 一.包管理器 Python有 ...

  2. Word Pattern - LeetCode

    Given a pattern and a string str, find if str follows the same pattern. Here follow means a full mat ...

  3. char可不可以存汉字

    常见的面试题:char类型的变量可不可以存汉字 答案是可以的,char类型中存储的是Unicode编码,Unicode编码中是存在存在中文的,所以Char自然可以存储汉字,但是!仅限于Unicode中 ...

  4. centos7.5更换docker-ce镜像源

    更换成阿里云 cd /etc/yum.repos.d/ vim docker-ce.repo # 按ecs进行非编辑模式 :%s/https:\/\/download.docker.com/https ...

  5. mariadb 启动方法

    通用启动方法 /etc/init.d/mariadb status #查看状态 /etc/init.d/mariadb start #启动 /etc/init.d/mariadb restart #重 ...

  6. Android Actionbar 添加返回按钮

    setHomeButtonEnabled这个小于4.0版本的默认值为true的.但是在4.0及其以上是false,该方法的作用:决定左上角的图标是否可以点击.没有向左的小图标. true 图标可以点击 ...

  7. andriod GridLayout

    来自:http://blog.csdn.net/jianghuiquan/article/details/8299973 GridLayout网格布局 android4.0以上版本出现的GridLay ...

  8. setOnFocusChangeListener的使用

    类似于文本框里面hint文字在初始化的时候显示或者隐藏的操作,就要用到setOnFocusChangeListener的 首先我认为不是太必要- 毕竟当你输入东西时,默认文字自然会消失 当然假设你执意 ...

  9. 【Salvation】—— 项目策划&市场分析

    写在前面:这个项目是2017年,我们评选校级创新基金项目的参加作品,小组4人,我为负责人,这个项目现在已经基本完成,目前处于后期收尾阶段. 一.项目的目标.内容及创新之处 1.研究目标 体现人类与自然 ...

  10. VC++动态链接库(DLL)编程深入浅出(二)

    好,让我们正式进入动态链接库的世界,先来看看最一般的DLL,即非MFC DLL 上节给大家介绍了静态链接库与库的调试与查看,本节主要介绍非MFC DLL. 4.非MFC DLL 4.1一个简单的DLL ...