HDFS Maintenance State

前言


初一看文章标题,很多人可能比较奇怪“HDFS Maintenance”是什么意思,“HDFS包含”的意思?首先Maintenance这个形似Maintain的单词可不是什么包含的意思,它的解释是维护,维修。那么HDFS Maintenance具体是什么意思呢,HDFS处于维护状态?说起维护状态,我们不禁可以联想到HDFS RollingUpgrad,没错,RollingUpgrade确实与HDFS的维护有点关联。确切地说,HDFS的RollingUpgrade是HDFS Maintenance功能的一个使用场景。下面给大家介绍一下社区目前在开发的这个feature。

HDFS Maintenance的缘来


HDFS Maintenance的提出源自于HDFS Upgrade Domain(升级域)的提出。HDFS升级域是方便于集群做大规模滚动升级之类的操作而提出的HDFS升级域的相关内容可查阅笔者的另外一篇文章:HDFS升级域:Upgrade Domain。在HDFS升级域的讨论与实现过程中,社区提出了DataNode的Maintenance(维护状态)。Maintenance状态是一类新的状态,HDFS现有的状态有以下几种,看完大家可能就知道这是什么意思了。

  • Live(In Sevice)
  • Decommissioning
  • Decommissioned
  • Dead

这些状态我们在NameNode的WebUI界面上也能直接看到。那么问题来了,上面4种是否是完美的呢?答案显然不是,如果我们仔细进行分析的话,上面状态的划分不够灵活。在这样的情况下,DataNode不是处于服务状态,就是处于Out of Service状态。而一旦处于Out of Service状态,接下来就会导致replication操作,使集群副本数满足副本系数的要求。所以这里会引出一个问题,我们是否能够定义一个新的状态,使得我们对DataNode的操作能尽可能地对集群没有影响,并能够快速的恢复回去呢?这就是DataNode Maintenance状态的缘由。

DataNode Maintenance状态的定义


与Decommission下线状态类似,Maintenance状态同样有2个细分状态,Enter_Maintenance和In_Maintenance状态。Enter_Maintenance表示的意思是正在进入Maintenance状态。而In_Maintenance表示目前已为Maintenance状态。为什么这里还会有进入Maintenance这样的状态呢?其实大家可以拿Decommission过程与此过程做一个比较。我们在执行下线操作时,为了满足集群副本数的要求,会有一个Decommissioning的状态,此时集群会做replication操作。同理,Enter_Maintenance状态也会做这样的事情,但是它与Decommission过程的目的略有不同。Enter_Maintenance是为了保证集群内有至少满足1个副本以上的数据副本,来保证数据的可用性。比如说当进入Maintenance状态的节点拥有集群中唯一的某块副本时,这时就会做一次replication操作。从一定程度上而言,Maintenance情况下需要满足的最低副本数应比Decommission过程要低,理论上只要存在一个即可。所以如果我们集群都是默认3副本的情况时,那么进入Maintenance状态,基本不会有replication操作了。

如果DataNode处于Maintenance状态了,HDFS对DataNode的节点是怎样处理的呢?主要有以下几点主要影响:

  • 集群内不会触发replicate此节点内的块数据。
  • DataNode将不接收来自DFSClient端的读写请求。以往DataNode如果处于Out of Service的状态,就会抛出很多的异常。
  • DataNode将不会被选为block的存储节点。
  • Balancer/Mover操作也将避免移动此节点上的数据。

所以从这里我们可以看到,Maintenance状态的DataNode就是处于一个“维护”的状态,它并不等同于Dead、Decommissioned状态,它能够主动地让NameNode知道自己的状态,并能够随时快速的切换回服务状态。

HDFS Maintenance功能的使用场景


介绍完Maintenance状态的定义之后,我们再来看看它的使用场景。

首先HDFS Maintenance的提出源自于HDFS 升级域的讨论。所以毫无疑问,在滚动升级的过程中,HDFS Maintenance可以起到很大的帮助。这样的话,当每次进行批量rolling upgrade操作的时候,就能够快速进行服务状态的切换,而不用担心操作时间过长导致集群进行replication操作了。

第二个使用场景是DataNode服务的快速切换。在很多情况下,当节点本身出现软件或者硬件层面的问题时,这时我们可能会对其进行临时的停止操作来进行软、硬件的故障恢复操作。这个时候我们就可以使DataNode进入Maintenance状态。在这里,我们可以把DataNode Maintenance理解为DataNode背后的一道开关。

HDFS Maintenance的核心实现


在HDFS-7877上,原作者提交了此功能的patch。笔者对其也进行了学习,给我的感觉是HDFS Maintenance的引入好比在各个操作方法入口上加了一道开关。这里的开关可以具象的理解为if(datanode.inService())的判断。主要在下面几个场景代码中做了处理。

第一处,在调用BlockManager#createLocatedBlock获取块信息的时候做了处理,这步操作会在每次的读请求中被调用。代码改动如下:

private LocatedBlock createLocatedBlock(final BlockInfoContiguous blk, final lon

     final int numNodes = blocksMap.numNodes(blk);
     final boolean isCorrupt = numCorruptNodes == numNodes;
-    final int numMachines = isCorrupt ? numNodes: numNodes - numCorruptNodes;
+    int numMachines = isCorrupt ? numNodes: numNodes - numCorruptNodes;
+    numMachines -= numberReplicas.inMaintenanceReplicas() +
+        numberReplicas.deadEnteringMaintenanceReplicas();
     final DatanodeStorageInfo[] machines = new DatanodeStorageInfo[numMachines];
     int j = 0;
     if (numMachines > 0) {
       for(DatanodeStorageInfo storage : blocksMap.getStorages(blk)) {
         final DatanodeDescriptor d = storage.getDatanodeDescriptor();
         final boolean replicaCorrupt = corruptReplicas.isReplicaCorrupt(blk, d);
+        // Don't pick IN_MAINTENANCE or dead ENTERING_MAINTENANCE states.
+        if (d.isInMaintenance() || (d.isEnteringMaintenance() && !d.isAlive)) {
+          continue;
+        }

第二处,在Rereplication过程和普通写请求中的选出目标放置节点的方法过程中,进行了过滤处理,主要集中在下面2个方法。

DatanodeDescriptor chooseSourceDatanode(Block block,
         corrupt += countableReplica;
       else if (node.isDecommissionInProgress() || node.isDecommissioned())
         decommissioned += countableReplica;
+      else if (node.isEnteringMaintenance()) {
+        enteringMaintenance += countableReplica;
+        if (!node.isAlive) {
+          deadEnteringMaintenance += countableReplica;
+        }
+      } else if (node.isInMaintenance())
+        inMaintenance += countableReplica;
       else if (excessBlocks != null && excessBlocks.contains(block)) {
         excess += countableReplica;
       } else {
@@ -1638,7 +1682,7 @@ else if (excessBlocks != null && excessBlocks.contains(block)) {
       if ((nodesCorrupt != null) && nodesCorrupt.contains(node))
         continue;
       if(priority != UnderReplicatedBlocks.QUEUE_HIGHEST_PRIORITY 
-          && !node.isDecommissionInProgress() 
+          && !node.isDecommissionInProgress() && !node.isEnteringMaintenance()
           && node.getNumberOfBlocksToBeReplicated() >= maxReplicationStreams)
       {

下面是选择副本放置位置的时候,做了目标存储节点类别的判断。

@@ -764,9 +764,9 @@ private boolean isGoodTarget(DatanodeStorageInfo storage,
     }

     DatanodeDescriptor node = storage.getDatanodeDescriptor();
-    // check if the node is (being) decommissioned
-    if (node.isDecommissionInProgress() || node.isDecommissioned()) {
-      logNodeIsNotChosen(storage, "the node is (being) decommissioned ");
+    // check if the node is in service
+    if (!node.isInService()) {
+      logNodeIsNotChosen(storage, "the node isn't in service");
       return false;
     }

第三处,Balancer/Mover操作的时候,需要过滤掉处于Maintenance状态的节点。

由于Balancer/Mover工具在运行的时候,都会调用Dispatcher init方法来执行初始化节点的操作,来过滤掉exclude,decommissioned这类的节点。所以我们在这里只需多加一类判断即可。

首先是Dispatcher的init初始化方法。

  /** Get live datanode storage reports and then build the network topology. */
  public List<DatanodeStorageReport> init() throws IOException {
    final DatanodeStorageReport[] reports = nnc.getLiveDatanodeStorageReport();
    final List<DatanodeStorageReport> trimmed = new ArrayList<DatanodeStorageReport>(); 
    // create network topology and classify utilization collections:
    // over-utilized, above-average, below-average and under-utilized.
    for (DatanodeStorageReport r : DFSUtil.shuffle(reports)) {
      final DatanodeInfo datanode = r.getDatanodeInfo();
      // 此处判断节点是否需要过滤
      if (shouldIgnore(datanode)) {
        continue;
      }
      trimmed.add(r);
      cluster.add(datanode);
    }
    return trimmed;
  }

然后我们进入shouldIgnore方法。

private boolean shouldIgnore(DatanodeInfo dn) {
     final boolean decommissioned = dn.isDecommissioned();
     // ignore decommissioning nodes
     final boolean decommissioning = dn.isDecommissionInProgress();
+    // ignore nodes in maintenance
+    final boolean inMaintenance = dn.isInMaintenance();
+    // ignore nodes entering maintenance
+    final boolean enteringMaintenance = dn.isEnteringMaintenance();
     // ignore nodes in exclude list
     final boolean excluded = Util.isExcluded(excludedNodes, dn);
     // ignore nodes not in the include list (if include list is not empty)
     final boolean notIncluded = !Util.isIncluded(includedNodes, dn);

-    if (decommissioned || decommissioning || excluded || notIncluded) {
+    if (decommissioned || decommissioning || excluded || notIncluded ||
+        inMaintenance || enteringMaintenance) {
       if (LOG.isTraceEnabled()) {
         LOG.trace("Excluding datanode " + dn + ": " + decommissioned + ", "
-            + decommissioning + ", " + excluded + ", " + notIncluded);
+            + decommissioning + ", " + excluded + ", " + notIncluded + ", "
+            + inMaintenance + ", " + enteringMaintenance);
       }
       return true;
     }

从以上3处的处理我们可以看到,Maintenance状态的处理与原有的Decommission状态的处理极为相似。当然上述只是笔者简单分析了几处,更多的实现还是建议读者自行阅读原patch代码,上面还有Enter_Maintenance状态时进行最小副本数的判断逻辑。

HDFS Maintenance状态转化图


DataNode Maintenance状态同样支持多种状态之间的转换,下面是原设计中的状态转化图。




HDFS Maintenance状态转换图

本文的内容就是如此,希望大家读完后有所收获。HDFS Maintenance功能预计发布在Hadoop 2.9.0的版本中,目前还在开发中。笔者目前也在关注这个Feature,希望也能贡献自己的一份力吧。

参考资料


[1].https://issues.apache.org/jira/browse/HDFS-7877
[2].https://issues.apache.org/jira/secure/attachment/12709388/Supportmaintenancestatefordatanodes-2.pdf
[3].https://issues.apache.org/jira/secure/attachment/12709387/HDFS-7877-2.patch

原文地址:https://www.cnblogs.com/bianqi/p/12183694.html