前言
我们知道,在HDFS里,有2个与文件块写入紧密相连的策略选择类:副本放置策略(BlockPlacementPolicy)和磁盘选择策略(VolumeChoosingPolicy)。这两者有什么作用上的区别呢?前者决定了文件块的目标写入节点,后者决定了目标节点上哪个磁盘目录被写入。之前笔者已经写过相关策略的介绍文章了,感兴趣的同学可以点击此链接:HDFS副本放置策略和DataNode引用计数磁盘选择策略。如果这2个策略你都已经或多或少了解了,那么下面就开始进入本文的主题。
副本放置策略和磁盘选择策略之间的“矛盾”
这里为什么笔者会用了“矛盾”这个词呢?“矛盾”的地方在哪里呢?这个得从BlockPlacementPolicy策略类中的目标节点选择逻辑说起,请看下面一段执行代码:
//在候选节点内为块选出一个合适的存储目录位置
DatanodeStorageInfo chooseStorage4Block(DatanodeDescriptor dnd,
long blockSize,
List<DatanodeStorageInfo> results,
StorageType storageType) {
DatanodeStorageInfo storage =
dnd.chooseStorage4Block(storageType, blockSize);
if (storage != null) {
results.add(storage);
} else {
logNodeIsNotChosen(dnd, "no good storage to place the block ");
}
return storage;
}
然后是接下来的选择逻辑了,下面的代码很关键,请仔细看,
public DatanodeStorageInfo chooseStorage4Block(StorageType t,
long blockSize) {
final long requiredSize =
blockSize * HdfsServerConstants.MIN_BLOCKS_FOR_WRITE;
final long scheduledSize = blockSize * getBlocksScheduled(t);
long remaining = 0;
DatanodeStorageInfo storage = null;
// 遍历当前节点内所拥有的storage存储信息
for (DatanodeStorageInfo s : getStorageInfos()) {
// 只有当存储目录状态是正常的并且与期待的存储类型想匹配时,才进行下一步的选择
if (s.getState() == State.NORMAL && s.getStorageType() == t) {
// 然后赋值给到目标存储信息变量中
if (storage == null) {
storage = s;
}
long r = s.getRemaining();
if (r >= requiredSize) {
remaining += r;
}
}
}
// 判断存储空间大小是否能够满足写入的要求
if (requiredSize > remaining - scheduledSize) {
LOG.debug(
"The node {} does not have enough {} space (required={},"
+ " scheduled={}, remaining={}).",
this, t, requiredSize, scheduledSize, remaining);
return null;
}
// 返回选择结果
return storage;
}
上面这段代码是目前HDFS用来选择节点内目标存储位置的信息。从上面的代码中,我们可以得出下面2点重要信息:
1).副本放置策略中同样有对于目标节点内的写入目录位置进行选择的逻辑。
2).上述方法逻辑每次总是选择第一个有效的目录位置为目标写入位置。
到这里,笔者想总结出这样一条信息:BlockPlacementPolicy策略类同样会对目标写入目录进行选择,不过在目前的实现逻辑中,服务端的这种选择会被DataNode端的VolumeChoosingPolicy策略类的选择所覆盖。换句话说,BlockPlacementPolicy选择的目标写入目录位置只是代表此节点是一个符合要求的节点,至于最后写到哪个目录,还是由VolumeChoosingPolicy说了算。
现在,我们再回头来看这个问题,这个“矛盾”点就在于目标storage位置的选择。写到这里,笔者不禁心生这样一个疑问:DataNode完全无视NameNode提供的一个目标位置选择,是否是一个合理的做法呢?可能一种更好的做法是将服务端的这种选择结果和VolumeChoosingPolicy的本地选择策略进行一种结合,DataNode在一定程度上也应该尊重NameNode选择的这种结果。
OK,基于上面这个想法,我们就需要在相关代码上下一点功夫改进了。
副本放置策略和磁盘选择策略的选择一致化改造
我们姑且命名此优化为“选择一致化”改造。这里主要有3件事情需要去完善。
1.更新数据写入上下游方法,将服务端选择的目录位置标识(这里具体指storageID)传到DataNode端,也就是VolumeChoosingPolicy中。此改造将会涉及许多类文件的更新。不过令人高兴的是,社区JIRA:HDFS-9807(Add an optional StorageID to writes)已经帮我们完成了这部分的工作。在这部分工作之后,VolumeChoosingPolicy的接口中将有了新 的输入参数:服务端选择的storage选择标识,变为如下形式:
/**
* Choose a volume to place a replica,
* given a list of volumes and the replica size sought for storage.
*
* The implementations of this interface must be thread-safe.
*
* @param volumes - a list of available volumes.
* @param replicaSize - the size of the replica for which a volume is sought.
* @param storageId - the storage id of the Volume nominated by the namenode.
* This can usually be ignored by the VolumeChoosingPolicy.
* @return the chosen volume.
* @throws IOException when disks are unavailable or are full.
*/
V chooseVolume(List<V> volumes, long replicaSize, String storageId)
throws IOException;
2.BlockPlacementPolicy中的DatanodeStorage选择策略的改造,因为服务端的选择结果将会对DataNode本地选择有所影响,我们当然希望服务端的这种选择方式也应该进行策略化的改造,而不是目前的总是选择第一个有效的存储位置的方式。我们应该要定义一个类名为DatanodeStorageInfoChoosingPolicy的策略类,它的作用就是服务端的磁盘目录选择。这部分的工作是笔者目前在社区JIRA:HDFS-11464(Improve the selection in choosing storage for blocks)所在做的事情。DatanodeStorageInfoChoosingPolicy中的接口定义如下:
/**
* This interface specifies the policy for choosing storages between datanodes
* and namenode.
*/
@InterfaceAudience.Private
public interface DatanodeStorageInfoChoosingPolicy {
/**
* Choose a storage to place a replica, given a list of datanode storage info,
* the required storage type, block size and the number of scheduled blocks.
*
* This policy interface can be also implemented by a VolumeChoosingPolicy to
* make a consistent storage-chosen decision between datanodes and the
* namenode.
*
* @param candidateStorages
* candidate storages
* @param requiredType
* required storage type
* @param blockSize
* required block size
* @param blocksScheduled
* the number of blocks scheduled
* @return DatanodeStorageInfo chosen storage returned
*/
DatanodeStorageInfo chooseStorage(DatanodeStorageInfo[] candidateStorages,
StorageType requiredType, long blockSize, long blocksScheduled);
}
3.第三点需要去做的事情是定义一个新的VolumeChoosingPolicyt策略类,同时实现VolumeChoosingPolicyt和DatanodeStorageInfoChoosingPolicy接口,这样的话,新的策略类就可以把服务端的这种选择结果给利用上了,它也同样能知道服务端的目录位置选择策略了。不过这一部分工作尚未开始,这要依赖于笔者HDFS-11464工作的完成。副本块相关的策略选择与HDFS内读写文件块的效率密切相关,感兴趣的读者朋友可以试着将此代码apply到自己的内部版本中,进行进一步的分析,优化。
参考资料
[1].Add an optional StorageID to writes, https://issues.apache.org/jira/browse/HDFS-9807
[2].Improve the selection in choosing storage for blocks, https://issues.apache.org/jira/browse/HDFS-11464.