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