HDFS安全模式详解

什么是安全模式

安全模式是HDFS所处的一种特殊状态,在这种状态下,文件系统只接受读数据请求,而不接受删除、修改等变更请求。在NameNode主节点启动时,HDFS首先进入安全模式,DataNode在启动的时候会向namenode汇报可用的block等状态,当整个系统达到安全标准时,HDFS自动离开安全模式。如果HDFS出于安全模式下,则文件block不能进行任何的副本复制操作,因此达到最小的副本数量要求是基于datanode启动时的状态来判定的,启动时不会再做任何复制(从而达到最小副本数量要求)

下面是namenode的一个日志片段:

这里写图片描述

安全模式相关的配置

系统什么时候才离开安全模式,需要满足哪些条件?当收到来自datanode的状态报告后,namenode根据配置,确定 1)可用的block占总数的比例、2)可用的数据节点数量符合要求之后,离开安全模式。如果有必要,也可以通过命令强制离开安全模式。与安全模式相关的主要配置在hdfs-site.xml文件中,主要有下面几个属性

  • dfs.namenode.replication.min: 最小的文件block副本数量,默认为1.

  • dfs.namenode.safemode.threshold-pct: 副本数达到最小要求的block占系统总block数的百分比,当实际比例超过该配置后,才能离开安全模式(但是还需要其他条件也满足)。默认为0.999f,也就是说符合最小副本数要求的block占比超过99.9%时,并且其他条件也满足才能离开安全模式。如果为小于等于0,则不会等待任何副本达到要求即可离开。如果大于1,则永远处于安全模式。

  • dfs.namenode.safemode.min.datanodes: 离开安全模式的最小可用(alive)datanode数量要求,默认为0.也就是即使所有datanode都不可用,仍然可以离开安全模式。

  • dfs.namenode.safemode.extension: 当集群可用block比例,可用datanode都达到要求之后,如果在extension配置的时间段之后依然能满足要求,此时集群才离开安全模式。单位为毫秒,默认为1.也就是当满足条件并且能够维持1毫秒之后,离开安全模式。 这个配置主要是对集群的稳定程度做进一步的确认。避免达到要求后马上又不符合安全标准。

总结一下,要离开安全模式,需要满足以下条件:
1)达到副本数量要求的block比例满足要求;
2)可用的datanode节点数满足配置的数量要求;
3) 1、2 两个条件满足后维持的时间达到配置的要求。

我们可以再次从日志中验证这些:

这里写图片描述

相关的操作命令

Hadoop提供脚本用于对安全模式进行操作,主要命令为:

hadoop dfsadmin -safemode <command>

command的可用取值如下:

command 功能 备注
get 查看当前状态
enter 进入安全模式
leave 强制离开安全模式
wait 一直等待直到安全模式结束

相关源码

安全模式的相关操作封装在SafeMode接口中:

/** 安全模式相关操作 */
@InterfaceAudience.Private
public interface SafeMode {
  /**
   *  检查进入或者退出安全模式的条件是否满足,
   *  如果满足,进入或退出安全模式。
   */
  public void checkSafeMode();

  /** 系统当前是否处于安全模式? */
  public boolean isInSafeMode();

  /**
   * 系统启动时是否自动进入安全模式?
   */
  public boolean isInStartupSafeMode();

  /** Check whether replication queues are being populated. */
  public boolean isPopulatingReplQueues();

  /**
   * 增加达到最小副本数要求的block数
   * @param replication current replication 
   */
  public void incrementSafeBlockCount(int replication);

  /** 降低达到最小副本数要求的block数 */
  public void decrementSafeBlockCount(Block b);
}

HDFS的命名系统需要提供安全模式相关的操作,因此实现了改接口,层次结构如下:

这里写图片描述

Namesystem继承了SafeMode接口,FSNamesystem类实现了Namesystem,间接实现SafeMode定义的操作,也就是我们可以对命名系统进行安全模式相关的操作。

FSNamesystem内部实现了一个内部线程类SafeModeMonitor,周期性地检查是否应该离开安全模式:


  /**
   * 周期性地检测是否可以离开安全模式,逻辑封装在run方法中.
   * This thread starts when the threshold level is reached.
   *
   */
  class SafeModeMonitor implements Runnable {
    /** 两次检测间隔的毫秒数,即1秒 */
    private static final long recheckInterval = 1000;

    /**
     */
    @Override
    public void run() {
      // 系统运行时,循环检测
      while (fsRunning) {
        writeLock();
        try {
          // 没有安全模式相关信息,也就是不在安全模式
          if (safeMode == null) { // Not in safe mode.
            break;//线程退出
          }
          if (safeMode.canLeave()) {
            // Leave safe mode.
            safeMode.leave();
            smmthread = null;
            // 离开安全模式后,线程退出
            break;
          }
        } finally {
          writeUnlock();
        }

        try {
          // 两次检测之间,线程休眠
          Thread.sleep(recheckInterval);
        } catch (InterruptedException ie) {
          // Ignored
        }
      }
      // 当系统不在运行的时候,线程结束退出
      if (!fsRunning) {
        LOG.info("NameNode is being shutdown, exit SafeModeMonitor thread");
      }
    }
  }

这个内部类中出现的safeMode,其类型为SafeModeInfo,用于保存安全模式下的相关信息:

 private volatile SafeModeInfo safeMode;  // safe mode information

注意到这个变量为volatile,也就是说线程对该变量的任何修改完成后,其他线程立刻可以看到变化。OK,那么什么时候会创建这个对象呢,我们用safeMode= new SafeModeInfo搜索源代码,发现2个地方会实例化这个对象:一个实在FSNamesystem的构造函数中,另一处在FSNamesystem类的enterSafeMode方法中。我们先来看构造函数,其方法声明如下:

FSNamesystem(Configuration conf, FSImage fsImage, boolean ignoreRetryCache)
      throws IOException 

这个构造函数使用给定的配置及文件镜像,创建一个文件命名系统。其中有一句如下:

this.safeMode = new SafeModeInfo(conf);

我们去看看SafeModeInfo这个构造函数:

 /**
     * Creates SafeModeInfo when the name node enters
     * automatic safe mode at startup.
     *  
     * @param conf configuration
     */
    private SafeModeInfo(Configuration conf) {
      // 这个配置就是我们前面提到的百分比配置
      this.threshold = conf.getFloat(DFS_NAMENODE_SAFEMODE_THRESHOLD_PCT_KEY,
          DFS_NAMENODE_SAFEMODE_THRESHOLD_PCT_DEFAULT);
      if(threshold > 1.0) {
        LOG.warn("The threshold value should't be greater than 1, threshold: " + threshold);
      }
    // 这个也是我们前面提到的最小可用datanode数量配置
      this.datanodeThreshold = conf.getInt(
        DFS_NAMENODE_SAFEMODE_MIN_DATANODES_KEY,
        DFS_NAMENODE_SAFEMODE_MIN_DATANODES_DEFAULT);
      this.extension = conf.getInt(DFS_NAMENODE_SAFEMODE_EXTENSION_KEY, 0);
      this.safeReplication = conf.getInt(DFS_NAMENODE_REPLICATION_MIN_KEY, 
                                         DFS_NAMENODE_REPLICATION_MIN_DEFAULT);

      LOG.info(DFS_NAMENODE_SAFEMODE_THRESHOLD_PCT_KEY + " = " + threshold);
      LOG.info(DFS_NAMENODE_SAFEMODE_MIN_DATANODES_KEY + " = " + datanodeThreshold);
      LOG.info(DFS_NAMENODE_SAFEMODE_EXTENSION_KEY + "     = " + extension);

      // default to safe mode threshold (i.e., don't populate queues before leaving safe mode)
      this.replQueueThreshold = 
        conf.getFloat(DFS_NAMENODE_REPL_QUEUE_THRESHOLD_PCT_KEY,
                      (float) threshold);
      this.blockTotal = 0; 
      this.blockSafe = 0;
    }

核心逻辑就是获取相关的配置,创建初始的SafeModeInfo。

看看另一个地方:

 /**
   * Enter safe mode. If resourcesLow is false, then we assume it is manual
   * 参数用于区分是因为资源不满足要求还是手动进入安全模式
   * @throws IOException
   */
  void enterSafeMode(boolean resourcesLow) throws IOException {
    writeLock();
    try {
      // Stop the secret manager, since rolling the master key would
      // try to write to the edit log
      stopSecretManager();

      // Ensure that any concurrent operations have been fully synced
      // before entering safe mode. This ensures that the FSImage
      // is entirely stable on disk as soon as we're in safe mode.
      boolean isEditlogOpenForWrite = getEditLog().isOpenForWrite();
      // Before Editlog is in OpenForWrite mode, editLogStream will be null. So,
      // logSyncAll call can be called only when Edlitlog is in OpenForWrite mode
      if (isEditlogOpenForWrite) {
        getEditLog().logSyncAll();
      }
      if (!isInSafeMode()) {
        safeMode = new SafeModeInfo(resourcesLow);
        return;
      }
      if (resourcesLow) {
        safeMode.setResourcesLow();
      } else {
        safeMode.setManual();
      }
      if (isEditlogOpenForWrite) {
        getEditLog().logSyncAll();
      }
      NameNode.stateChangeLog.info("STATE* Safe mode is ON"
          + safeMode.getTurnOffTip());
    } finally {
      writeUnlock();
    }
  }

总结一下,SafeModeMonitor作为守护线程,在收到来自datanode的BlockReport状态报告之后,周期性地检测是否达到离开安全模式的条件,如果符合条件,则离开安全模式。

(完)

你可能感兴趣的:(Hadoop,hdfs,namenode)