安全模式是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状态报告之后,周期性地检测是否达到离开安全模式的条件,如果符合条件,则离开安全模式。
(完)