网站美化教程下载,网站建设工作动态,电商推广方法有哪些,青岛浩瀚网络技术有限公司文章目录 前言Decommission过慢的分析过程NameNode页面并不显示Decommission的进度和剩余块数量增加每次调度的块数量增加Stream Limit以避免节点被Skip节点被Skip时应该在DEBUG时打印原因在大量节点被Skip的时候加快有效调度其他可能改进 基本流程解析用户通过节点刷新触发Dec… 文章目录 前言Decommission过慢的分析过程NameNode页面并不显示Decommission的进度和剩余块数量增加每次调度的块数量增加Stream Limit以避免节点被Skip节点被Skip时应该在DEBUG时打印原因在大量节点被Skip的时候加快有效调度其他可能改进 基本流程解析用户通过节点刷新触发DecommissionPendingNode的处理DatanodeAdminDefaultMonitor的基本框架对Node上的块进行全扫描以确认副本不足的块基于全量扫描的结果进行Prune扫描是否需要进行Reconstruction的判断过程DECOMMISSIONING状态下是否需要进行ReconstructionMAINTENANCE状态下是否需要进行Reconstruction RedundancyMonitor生成和调度ReplicationWork 相关参数调整增加Decommission过程中每次调度的Block数量 日志分析和代码印证 前言
我们在一个HDFS集群中进行部分节点的Decommission操作。在Decommission过程中我们发现了如下问题:
Decommission的整个过程进展非常缓慢一个星期以后依然有一部分节点无法完成Decommission操作。我们尝试通过-refreshNodes重新Decommission系统没有任何response这让我们无法确认到底当前的Decommission和我们重新trigger的尝试是成功了还是失败了。当我们发现Decommission进展缓慢以后我们在INFO日志中无法找到任何与Decommission相关的操作日志。即使我们打开与与Decommission相关的DEBUG日志也无法从中获取直接跟Decommissiion相关的日志信息整个过程的诊断非常困难。后来我们发现这是由于Decommission操作的触发和对Decommission的Block的调度是完全两个相互剥离的异步操作。但是即使设计本身无法改变我们的想法是HDFS是否能在日志上有所优化在对Block进行调度的时候能否有所提示
对于 Decommission缓慢的情况我们采取的措施是:
在不修改代码不变的情况下调整了部分与Decommission相关的配置以加快Decommission的进行;我们详细检查了Decommission过程的代码发现了一个优化点并且将对应的优化提交给了HDFS社区 TODO。
对于日志问题我们也向HDFS社区提交了PatchTODO这个Patch想解决的问题是:
当用户反复执行-refreshNodes操作其实只要一个节点正在Decommission那么后面针对这个节点的所有的-refreshNodes操作都是无效的会被skip掉。这种关键的日志能否在日志中体现毕竟又有多少用户能在代码层面去详细研究Decommission的详细触发和执行逻辑呢当Decommission执行缓慢的时候能否在DEBUG日志打开的情况下提供给管理员充足的信息以诊断对应原因
Decommission过慢的分析过程
NameNode页面并不显示Decommission的进度和剩余块数量
首先在我们发现我们的Decommission操作在过了一周以后依然没有结束待Decommission的节点依然处于DECOMMISSSIOIN_IN_PROGRESS的状态。我们首先想到的是在NameNode的页面查看这些DataNode上有多少Block已经移走了有多少Block的Replica还没有移走。 然后我们从NameNode上看到的是下面的页面其中最后一行的DataNode正处在DECOMMISSIONING_IN_PROGRESS状态中 我们发现这个DECOMMISSIONING_IN_PROGRESS的Block数量是一直不变的。所以我们的问题是这个数字一直不变这是否意味着这个节点的Decommission已经没有任何进展了 后来我们从代码分析看到我们从这个页面看到的一个DECOMMISSIONING_IN_PROGRESS节点的块数量代表的并不是这个节点还需要transfer出去的块的数量而是这个机器上本身存放的副本数量与Decommission无关。Decommission操作不会删除这个节点上的任何块数据而是Decommission操作的发生导致这个节点上的Replica的状态不再是Live状态因此如果不采取任何操作那么这个待Decommission节点上的Replica对应的Block的副本状态可能就无法满足Redundancy。基于这个考虑Decommission的过程其实就是轮询这个机器上的所有块保证机器在Decommission并且停机以后这些块的副本数依然满足要求。 这个过程就是Decommission的workload即块的复制。
增加每次调度的块数量
我们从日志中发现每一轮调度只有四个节点被尝试。比如下面的日志显示06:18的时候调度4个然后似乎sleep 了 3s下一轮调度又是4个节点
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795480_54656
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795483_54659
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795486_54662
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795487_54663
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795538_54714
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795547_54723
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795548_54724
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795549_54725我们查看对应代码这是由于RedundancyMonitor根据neededReconstruction中的low-redundancy block构造对应的BlockReconstruct的时候会根据当前集群的Live节点的数量确定这一轮需要考量多少个low-redundancy Block。 RedundancyMonitor会根据集群中当前Live节点的数量乘以一个倍数(dfs.namenode.replication.work.multiplier.per.iteration)得到每轮循环取出的、尝试进行构造BlockReconstructionWork的Block数量。 相邻两轮循环之间RedundancyMonitor会sleep 3s。 在我们的小集群中我们正在Decommission 5个节点保留了2个节点然后有一个节点正在加入到集群中因此在我们的场景下每轮(3s钟一轮)仅仅取出4个节点尝试构造BlockReconstructionWork这个速度是非常低的。 我们将对应的参数调整为10以增加RedundancyMonitor每轮处理的节点数量 propertynamedfs.namenode.replication.work.multiplier.per.iteration/namevalue10/value/property具体原理和代码可以参考我的另一篇文章HDFS块重构和RedundancyMonitor详解中讲解RedundancyMonitor的章节。
增加Stream Limit以避免节点被Skip
在构造BlockReconstructionWork的时候RedundancyMonitor会首先挑选Source节点。 对于一个Block的所有的source节点如果一个节点负载过高那么这个节点就不应该被选择成为Source。显然如果一个Block的所有Replica所在的节点的负载都很高因此没有被选作Source那么本轮就不会为这个节点调度BlockReconstructionWork。
NameNode端评价一个DataNode负载是否过高使用的标准是已经调度到这个节点上(待调度的(重构任务还没有被DataNode的心跳认领)和已经调度出去(重构任务已经被DataNode的心跳认领)的)的Task的数量是否大于配置的阈值。这两个阈值分别是dfs.namenode.replication.max-streams默认值是2和dfs.namenode.replication.max-streams-hard-limit默认值是4。其中dfs.namenode.replication.max-streams其实是一个Soft Limit即只约束普通优先级的Block但是不约束QUEUE_HIGHEST_PRIORITY的块的重构。而 dfs.namenode.replication.max-streams-hard-limit属于Hard Limit也会约束QUEUE_HIGHEST_PRIORITY的块的重构。
在一个大型的HDFS集群中我们可以使用默认值因为块分布均匀每一台机器上所承载的Replica所属的Block都只是集群所有Block中的一小部分。但是在我们只有几台节点的机器上这些机器上所承载的块很多并且几乎每一台DataNode机器上的Replica很可能Cover了集群中所有的Block因此任何一台机器由于负载过高而被排除在source node以外都会导致所有Block的BlockReconstructionWork都无法被选作source nodes。 因此我们需要将它们设置成一个较大的值 propertynamedfs.namenode.replication.max-streams/namevalue10/value/propertypropertynamedfs.namenode.replication.max-streams-hard-limit/namevalue20/value/property具体原理和代码可以参考我的另一篇文章HDFS块重构和RedundancyMonitor详解中讲解RedundancyMonitor的章节。
节点被Skip时应该在DEBUG时打印原因
为了找到Decommission缓慢的原因我们打开了DEBUG日志发现RedundancyMonitor在尝试为neededReconstructionBlock构造BlockReconstructionWork的时候打印如下日志:
2024-07-02 03:20:54,311 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Block blk_1073810374_69550 cannot be reconstructed from any node我们分析代码看到RedundancyMonitor为一个Block调度对应的BlockReconstructionWork发生在方法BlockManager.computeReconstructionWorkForBlocks()中其基本流程分为3步:
source node的选择构造对应的BlockReconstructionWork的实现。 对于基于Erasure Coding的和基于Replication的redundancyBlockReconstructionWork的具体实现类分别是ErasureCodingWork和ReplicationWork。在选择Source node的过程中涉及到很多不同的选择标准的总和考虑。对于基于Replication 的redundancy如果我们无法为一个Block选择任何一个符合要求的source node那么显然这个Reconstruction是无法工作的。 target node的选择target nodes的选择和我们写文件的过程中为一个Block选择target节点时一模一样的都是使用的BlockPlacementPolicy接口的具体实现类。其中对于基于Replication和基于Erasure Coding的BlockPlacementPolicy接口实现各不相同分别是BlockPlacementPolicyDefault和BlockPlacementPolicyRackFaultTolerant这里不再赘述。 我们打开DEBUG以后同时从代码中也看到在选择目标节点过程中是有比较好的DEBUG日志的详细打印在选择target节点的过程中一个Node因为哪些具体原因被排除。这些原因被定义在了NodeNotChosenReason中: private enum NodeNotChosenReason {NOT_IN_SERVICE(the node is not in service),NODE_STALE(the node is stale),NODE_TOO_BUSY(the node is too busy),TOO_MANY_NODES_ON_RACK(the rack has too many chosen nodes),NOT_ENOUGH_STORAGE_SPACE(not enough storage space to place the block),NO_REQUIRED_STORAGE_TYPE(required storage types are unavailable),NODE_SLOW(the node is too slow);当source node和target node都选择成功还需要对构建的BlockReconstructionWork进行校验这发生在方法validateReconstructionWork()中。如果校验不通过同样这个Block的BlockReconstructionWork不会被调度。
所以一个Low Redundancy Block最后却没有被成功调度BlockReconstructionWork大致原因有上面的三种情况。但是很可惜即使打开DEBUG日志除了target node的选择失败会打印DEBUG日志其他原因都不会打印日志。 因此我们向HDFS社区提了对应的Issue HDFS-17568在DEBUG环境下打印无法为某一个LowRedundancyBlock调度BlockReconstructionWork的具体原因。
在大量节点被Skip的时候加快有效调度
上面讲过RedundancyMonitor的每一轮调度都尝试从neededReconstruction中取出固定数量的Block尝试进行调度每运行一轮就会sleep 3s才进入下一轮。这个逻辑发生在方法computeBlockReconstructionWork()中 int computeBlockReconstructionWork(int blocksToProcess) {ListListBlockInfo blocksToReconstruct null;blocksToReconstruct neededReconstruction.chooseLowRedundancyBlocks(blocksToProcess, reset); // 选择最多blocksToProcess个需要进行重构的Blockreturn computeReconstructionWorkForBlocks(blocksToReconstruct); // 对这些Block进行重构} 具体流程为 选择最多blocksToProcess个需要进行重构的Block这些Block存放在一个二维数组中数组的第一维表示这些Blocks调度的优先级第二维度是对应优先级的Block的列表 blocksToReconstruct neededReconstruction.chooseLowRedundancyBlocks(blocksToProcess, reset); // 选择最多blocksToProcess个需要进行重构的Block对这些选定的需要重构的Block进行重构任务的构造和调度 return computeReconstructionWorkForBlocks(blocksToReconstruct); // 对这些Block进行重构这里存在一个巨大的问题通过chooseLowRedundancyBlocks()选定的重构的Block可能大部分由于各种原因(没有合适的source没有合适的targetvalidate不通过等)并没有实际调度出BlockReconstructionWork。但是即使这样这一轮RedundancyMonitor结束了要想到下一轮必须等待3s。这极大拖累了BlockReconstructionWork的调度效率即可能候选Block在每一轮都是固定的但是其中有效的、成功的调度可能非常少。
在我们的集群中我们的Decommission产生的Block在neededReconstruction中的优先级较低如果有其他高优先级的Block但是这些高优先级的Block总是无法成功调度(比如source节点负载过高而被skip)那么为这些由Decommission带来的Low-Redundancy Block调度BlockReconstructionWork由于优先级很低会进行无谓的空等过程。这非常像资源预留机制中的饿死现象可用资源被预留给大应用小应用却长时间无法获取资源去运行。 我们的优化逻辑是当遇到无法为其构建BlockReconstructionWork的low-redundancy block时在当前这一轮调度内快进检查其他低冗余块并为其安排重建从而在RedundancyMonitor的一轮运行中尽量多地进行有效调度。当然有效调度的块总数将受到参数blocksToProcess的限制。
具体issue和PR请查看HDFS-17569
其他可能改进 JMX中暴露一个节点的under replicated 的Block的数量 对于一个正在Decommission的节点是否可以暴露对应的JMX metrics显示这个节点的under replicate的块数量这个数量的减少趋势直接反印了这个节点的Decommission的进展。 能够通过关键词将Decommission的几个大的异步的Stage串联起来 当Decommission看起来过慢的时候我们直觉性地通过关键字Decommission搜索NameNode日志但是很显然基于我们在下文将会降到的一个节点Decommission的整个过程由于其基于各种队列的异步特性尤其是后面的Reconstruction等过程其实与Decommission并无直接管理。 尽管这样我们的问题是在日志层面我们能否有所优化比如虽然由于往neededReconstruioint中添加Block的原因很多Decommission只是其中一个我们是否可以添加辅助DEBUG日志给管理员一些提示比如这些Block添加进neededReconstruction 的原因因为Decommission因为Maintenance因为用户的手动升副本等等。这样我们通过比如Decommission搜索能够搜索到Decommission的全流程的一些进展数据而不是仅仅是下文所讲的第一个Stage的DEBUG日志。
基本流程解析
下图总结了我们Decommission一个节点的整个过程 在忽略具体细节的情况下我们可以看到一个节点的Decommissiion是由多个队列、多个线程完成的而不是一个职责单一、阻塞式等待的过程。这种基于队列和多线程的异步设计往往对于一个长时间运行的任务的调度是必须的但是这种设计方式给我们对整个过程的跟踪、调试、日志的分析和理解带来了非常大的麻烦因为逻辑上的因果关系不再和时间上的因果关系一一对应事件发生的有序性不再存在而是以流水线的方式互相重叠。
第一阶段 当我们通过-refreshNodes触发一个Decommission或者进入Maintenance的操作的时候将节点的AdminState置为ENTERING_MAINTENANCE或者DECOMMISSION_IN_PROGRESS的状态并将节点加入到pendingNodes以后触发结束第一阶段的任务完成。此后如果用户再次通过-refreshNodes重新出发NameNode仅仅检查发现当前已经处于AdminState的ENTERING_MAINTENANCE或者DECOMMISSION_IN_PROGRESS就什么也不做就直接退出。第二阶段独立线程DatanodeAdminDefaultMonitor从pendingNodes中取出待decommission或者maintenance的节点进行块的扫描和调度。 对节点上的所有Block进行一轮全扫描全扫描过程中对于确定由于decommission或者maintenance操作会导致low-redundancy的Block放入neededReconstruction中待构造ReconstructionWork同时生成一个low-redundancy的Block的List(insufficientList)。随后基于insufficientList中的每一个Block进行剪枝扫描同样检查将需要加入到neededReconstruction中的Block加入进去并且由于重构的发生所以副本已经充足因此不会阻塞Decommission的Block从insufficientList中删除。 第三阶段独立线程RedundancyMonitor从neededReconstruction中取出块进行Reconstruction的任务调度。任务调度主要需要确认可用的source节点和target节点。这个过程中可能因为各种原因无法调度成功第四阶段DataNode端在收到调度任务以后进行数据的读写和拷贝并将重构完成的块汇报给NameNode第五阶段收到块汇报的NameNode会重新考量块的副本情况。对于由于重构的发生副本数已经充足的节点就从neededReconstruction中删除。同时DatanodeAdminDefaultMonitor在不断地扫描和判定过程中也会发现这个块的副本数已经充足因此从insufficientList中将该块删除。当一个Node对应的insufficientList被清空这个Node就可以最终进入Decommission或者Maintenance状态。
用户通过节点刷新触发Decommission
我们通过自定义的 exludes 节点来告诉HDFS我们需要Decommision的节点列表: propertynamedfs.hosts.exclude/namevalue/hadoop/decommissioned-datanodes/value/property然后我们通过刷新节点命令触发对这些节点的decommission操作(参考 Hadoop Official Doc)
$HADOOP_HOME/bin/hdfs dfsdmin -refreshNodes实际上是出发了DatanodeManager的refreshDatanodes()方法 private void refreshDatanodes() {.....for (DatanodeDescriptor node : copy.values()) {// Check if not include.if (!hostConfigManager.isIncluded(node)) {node.setDisallowed(true); // 这个Node是显式包含在include中的因此不允许对这个节点进行操作} else { // 只有不在includes中的节点才有可能进入maintenance或者进入decommissionlong maintenanceExpireTimeInMS hostConfigManager.getMaintenanceExpirationTimeInMS(node);if (node.maintenanceNotExpired(maintenanceExpireTimeInMS)) { // maintenance时间还没有到期datanodeAdminManager.startMaintenance( // 开始maintenancenode, maintenanceExpireTimeInMS);} else if (hostConfigManager.isExcluded(node)) { //datanodeAdminManager.startDecommission(node); // 对于在exclude中的节点执行decommission操作} else { //离开maintenance离开decommissiondatanodeAdminManager.stopMaintenance(node);datanodeAdminManager.stopDecommission(node);}}node.setUpgradeDomain(hostConfigManager.getUpgradeDomain(node));}}这里涉及到对于节点maintenance和decommision的处理逻辑最终会交付给DatanodeAdminManager其基本逻辑为: 2. 在include文件(dfs.hosts.exclude配置文件中的节点列表)中的节点是绝对不会进入maintenance中或者decommission中的 3. 如果节点不在includes中 4. 如果节点的确进入了maintenance并且maintenance还没有到期那么就继续进行maintenance 5. 如果节点在excludes中那么就通过调用startDecommision()状态 6. 如果都不是意味着节点不应该处于maintenance状态也不应该处于decommissiion状态那么这时候会通过调用stopMaintenance()和stopDecommission()结束这两种状态。
这里不具体讲述maintenance的具体细节主要关注在decommission的过程 VisibleForTestingpublic void startDecommission(DatanodeDescriptor node) {if (!node.isDecommissionInProgress() !node.isDecommissioned()) {// Update DN stats maintained by HeartbeatManagerhbManager.startDecommission(node);// Update clusters emptyRackblockManager.getDatanodeManager().getNetworkTopology().decommissionNode(node);// hbManager.startDecommission will set dead node to decommissioned.if (node.isDecommissionInProgress()) {for (DatanodeStorageInfo storage : node.getStorageInfos()) {LOG.info(Starting decommission of {} {} with {} blocks,node, storage, storage.numBlocks());}node.getLeavingServiceStatus().setStartTime(monotonicNow());monitor.startTrackingNode(node);}} else {LOG.trace(startDecommission: Node {} in {}, nothing to do.,node, node.getAdminState());}}从上面代码我们看到: 如果节点既不是正在decommission也不是已经完成了decommission那么就执行decommission的启动工作: if (!node.isDecommissionInProgress() !node.isDecommissioned()) {// Update DN stats maintained by HeartbeatManager......启动Decommission的具体过程为: 通过HeartbeatManager将DataNode的AdminStates.DECOMMISSION_INPROGRESS hbManager.startDecommission(node);其实质上是将这个DatanodeDescriptor中adminState置为DECOMMISSION_INPROGRESS状态------------------------------------------------ HeartbeatManager -------------------------------------------synchronized void startDecommission(final DatanodeDescriptor node) {....node.startDecommission(); // 将DatanodeDescriptor的状态设置为DECOMMISSION_INPROGRESS}-------------------------------------------------- DatanodeInfo ---------------------------------------------------public void startDecommission() {adminState AdminStates.DECOMMISSION_INPROGRESS;}维护NetworkTopology中的节点和rack信息包括将节点从rack的节点列表中删除(如果这是这个rack的最后一个节点那么会将rack删除)通过将节点加入到decommissionNodes中: blockManager.getDatanodeManager().getNetworkTopology().decommissionNode(node); // 在NetworkTopology中卸载节点NetworkTopology中卸载节点的代码如下 --------------------------------------------- NetworkTopology ---------------------------------------------public void decommissionNode(Node node) {.....decommissionNodes.add(node.getName()); // 将节点加入到decommissionNodes中interRemoveNodeWithEmptyRack(node); // 更新对应的节点和rack映射关系}将这个节点添加到DatanodeAdminManager的pendingNodes中。后面会看到添加到这里的节点会有另外一个独立的线程(DataNodeAdminDefaultMonitor)来单独取出并处理 monitor.startTrackingNode(node);public void startTrackingNode(DatanodeDescriptor dn) {pendingNodes.add(dn);} 如果节点已经处在DECOMMISSION_INPROGRESS或者DECOMMISSIONED状态只是打印日志不做任何事情 else {LOG.trace(startDecommission: Node {} in {}, nothing to do.,node, node.getAdminState());}这里涉及到一个关键问题就是当我们发现我们的Decommission进展速度缓慢然后企图再次执行hdfs dfsdmin -refreshNodes命令重新出发decommission的时候实际上HDFS是不会做任何事情的。而且这是一条TRACE日志在默认的INFO日志级别下不打印因此对于不了解代码的HDFS管理员来说其实是感到特别迷惑的。我认为这是代码的问题。
PendingNode的处理
DatanodeAdminDefaultMonitor的基本框架
上面讲到用户通过hdfs dfsadmin -refreshNodes的admin操作启动了对某些节点的decommission或者enter maintenance操作这些节点进入到DatanodeAdminDefaultMonitor的pendingNodes中。 DatanodeAdminDefaultMonitor是专门用来处理DECOMMISSION_INPROGRESS 和 ENTERING_MAINTENANCE 状态的节点的它是一个Runnable会按照指定频率被调度。 在DatanodeAdminManager启动的时候会加载DatanodeAdminDefaultMonitor实例然后以固定30s的频率调度该线程: Class cls null;cls conf.getClass(DFSConfigKeys.DFS_NAMENODE_DECOMMISSION_MONITOR_CLASS,DatanodeAdminDefaultMonitor.class);executor.scheduleWithFixedDelay(monitor, intervalSecs, intervalSecs,TimeUnit.SECONDS);DatanodeAdminManager中的pendingNodes只是记录了节点信息显然一个节点是否能够进入DECOMMISSIONED状态或者IN_MAINTENANCE状态必定需要检查这个节点上的所有副本信息只有无一遗漏的所有副本都满足了某种副本要求这个节点才会进入DECOMMISSIONED状态或者IN_MAINTENANCE状态。 这是通过
Override
---------------------------------------- DatanodeDefaultAdminMonitor -------------------------------------public void run() {.....processPendingNodes(); // 处理pendingNodes放入outOfServiceNodeBlocks中check();}
DatanodeAdminDefaultMonitor中有一个独立线程负责做以下两件事情: 将新加入到pendingNodes中的节点取出放入到outOfServiceNodeBlocks中。代码很简单只有一个简单的限流逻辑即如果outOfServiceNodeBlocks的大小(代表着正在进行DECOMMISSION或者Enter Maintenance的节点数量大于maxConcurrentTrackedNodes那么暂时不会将pendingNodes中的节点加入到outOfServiceNodeBlocks中 private void processPendingNodes() {while (!pendingNodes.isEmpty() (maxConcurrentTrackedNodes 0 ||outOfServiceNodeBlocks.size() maxConcurrentTrackedNodes)) {outOfServiceNodeBlocks.put(pendingNodes.poll(), null);}}pendingNodes本身是一个PriorityQueue即从pendingNodes中取出节点时基于比较器有序的即最近有心跳的节点放在前面这样一些最近没有心跳的unhealthy节点的优先级就会较低即比较晚地放入到outOfServiceNodeBlocks中 private final PriorityQueueDatanodeDescriptor pendingNodes new PriorityQueue(PENDING_NODES_QUEUE_COMPARATOR);static final ComparatorDatanodeDescriptor PENDING_NODES_QUEUE_COMPARATOR (dn1, dn2) - Long.compare(dn2.getLastUpdate(), dn1.getLastUpdate()); 基于outOfServiceNodeBlocks的数据结构为这些节点的Block构建对应的ReconstructionWork并检查Reconstruction的结果以确定节点是否可以进入最终的DECOMMISSIONED状态或者IN_MAINTENANCE状态。这是在check()方法中完成的 ----------------------------------------- DatanodeAdminDefaultMonitor ----------------------------------------private void check() {final IteratorMap.EntryDatanodeDescriptor, AbstractListBlockInfoit new CyclicIteration(outOfServiceNodeBlocks, iterkey).iterator();final ListDatanodeDescriptor toRemove new ArrayList();// 每次会检查完outOfServiceNodeBlocks中的所有的DataNode但是每一轮对于每个节点最多检查的replica数量有上限约束while (it.hasNext() !exceededNumBlocksPerCheck() namesystem.isRunning()) {numNodesChecked;final Map.EntryDatanodeDescriptor, AbstractListBlockInfoentry it.next();final DatanodeDescriptor dn entry.getKey();// 获取挂载在这个节点上的所有BlockAbstractListBlockInfo blocks entry.getValue();boolean fullScan false;if (dn.isMaintenance() dn.maintenanceExpired()) { // 已经进入maintenance或者maintenance已经到期// If maintenance expires, stop tracking it.dnAdmin.stopMaintenance(dn);toRemove.add(dn);continue;}if (dn.isInMaintenance()) { // 已经进入了maintenance但是期限还没有到期处理下一个dncontinue;}if (blocks null) { // 第一次扫描的时候blocks是空的blocks handleInsufficientlyStored(dn); // 获取这个节点的insufficient的blockoutOfServiceNodeBlocks.put(dn, blocks); // 扫描出来的节点放在outOfServiceNodeBlocks中fullScan true; // 已经完成了full scan后面不会再进行full scan而是基于第一次scan的结果进行进一步scan} else {pruneReliableBlocks(dn, blocks); // 增量处理当前剩余的Blocks}if (blocks.size() 0) { // blocks ! null 并且已经insufficient的replica已经全部清空if (!fullScan) {blocks handleInsufficientlyStored(dn);outOfServiceNodeBlocks.put(dn, blocks);}// If the full scan is clean AND the node liveness is okay,// we can finally mark as DECOMMISSIONED or IN_MAINTENANCE.final boolean isHealthy blockManager.isNodeHealthyForDecommissionOrMaintenance(dn); // 节点是健康存活的if (blocks.size() 0 isHealthy) { // 这个节点的所有replica都已经清空完毕if (dn.isDecommissionInProgress()) { // 从DECOMMISSIONING_IN_PROGRESS进入到DECOMMISSIONED状态dnAdmin.setDecommissioned(dn);toRemove.add(dn);} else if (dn.isEnteringMaintenance()) { // 从ENTERING_MAINTENANCE进入到IN_MAINTENANCE状态// IN_MAINTENANCE node remains in the outOfServiceNodeBlocks to// to track maintenance expiration.dnAdmin.setInMaintenance(dn);}} } // Remove the datanodes that are DECOMMISSIONED or in service after// maintenance expiration.for (DatanodeDescriptor dn : toRemove) {outOfServiceNodeBlocks.remove(dn); // decommission 或者 enter maintenance成功DatanodeAdminDefaultMonitor不再处理}}
check()方法的基本流程为:
第一次处理某个Node的时候check()方法会对这个DataNode进行一轮Block的全扫描以获取节点上的所有的low-redundancy的replica。在扫描的过程中如果发现这个Replica对应的Block有进行Block Reconstruction的必要就添加到neededReconstruction中。全量扫描以后会进行一轮剪枝扫描。即以全扫描获取的low-redundancy的replica的List为基础进行减量扫描。即对这些low-redundancy的replica再次判定是否需要进行Block Reconstruction如果需要并且当前不在neededReconstruction中则加入到neededReconstruction中。同时如果我们发现low-redundancy的replica对应的Block已经有了sufficient redundancy那么就从list中删除。通过不断的增量扫描对于那些已经有了sufficient redundancy的Replica不断从outOfServiceNodeBlocks中删除掉。当outOfServiceNodeBlocks中一个Node的所有的Replica都已经被删掉说明所有的Replica都已经满足了Node进行decommission的要求这时候节点就可以动DECOMMISSION_IN_PROGRESS状态进入最后的DECOMMISSIONED状态。
为了避免每一次调度处理时间过长通过参数dfs.namenode.decommission.blocks.per.interval限制每一轮check()所应该处理的最大的块数量默认是500000。
注意neededReconstruction中存放了需要进行重构的那些Block其中由于Maintenance或者Decommission而带来的这些LowRedundancy的Block并放入到neededReconstruction中是由DatanodeDefaultAdminMonitor来操作的。但是neededReconstruction中并不仅仅存放了来自于Decommission或者Maintenance带来的low-redundancy block而是在所有情况下所有需要进行Reconstruction的Block都会放入到neededReconstruction中随后由RedundancyMonitor进行统一检查并构造对应的ReconstructionWork。具体细节请参考我的另一篇文章HDFS块重构和RedundancyMonitor详解 构建对outOfServiceNodeBlocks中的nodes的迭代器。其中iterkey是一个全局变量每次check()方法运行完(因为check()方法完全可能中途结束不一定每次都能遍历完所有的outOfServiceNodeBlocks中的所有的Datanodes)都会将当前的遍历位置(即当前的节点)更新到iterkey中下次可以从当前的位置继续而不是重新从头遍历 final IteratorMap.EntryDatanodeDescriptor, AbstractListBlockInfoit new CyclicIteration(outOfServiceNodeBlocks,iterkey).iterator();只要当前这一轮的调用的Blocks数量没有超过threadshold就基于outOfServiceNodeBlocks的迭代器逐个迭代其中的每一个节点 while (it.hasNext() !exceededNumBlocksPerCheck() namesystem.isRunning()) {对于已经进入Maintenance的节点(AdminStates.IN_MAINTENANCE)并且Maintenance时间已经到期则进行到期的清理处理比如将节点从pendingNodes中删除: if (dn.isMaintenance() dn.maintenanceExpired()) { // 已经进入maintenance或者maintenance已经到期// If maintenance expires, stop tracking it.dnAdmin.stopMaintenance(dn);toRemove.add(dn); // toRemove中的节点会从outOfServiceNodeBlocks中删除continue;}对于已经进入Maintenance(AdminStates.IN_MAINTENANCE不是AdminStates.ENTERING_MAINTENANCE_STATUS)并且还没有到期不做任何处理: if (dn.isInMaintenance()) { // 已经进入了maintenance但是期限还没有到期处理下一个dncontinue;}如果还没有进行full scan那么先需要进行这个节点的full scan并置fullScan true表示full scan已经进行。full scan会构造这个DN的所有的block的一个迭代器获取insufficient 的block并放到outOfServiceNodeBlocks中 if (blocks null) { // 第一次扫描的时候blocks是null注意和下面第7步的blocks.size 0区分开blocks handleInsufficientlyStored(dn); // 获取这个节点的insufficient的blockoutOfServiceNodeBlocks.put(dn, blocks); // 扫描出来的节点放在outOfServiceNodeBlocks中fullScan true; // 已经完成了full scan后面不会再进行full scan而是基于第一次scan的结果进行进一步scan}如果已经进行了full scan(意思是在前面的某一轮线程调度中已经进行了full scan)那么其实现在要做的其实是一个剪枝(prune)操作即对当前outOfServiceNodeBlocks中这个DataNode剩余的还没有处理的block进行迭代然后挨个进行副本状态的检查检查过程中发现这个Block的副本状态已经满足了要求那么会在迭代过程中把这个block从outOfServiceNodeBlocks中删除 else {pruneReliableBlocks(dn, blocks); // 对这个节点上挂载的这些low-redundancy blocks进行剪枝}再次检查blocks中块的数量如果发现为0(注意和步骤5中的blocksnull)说明这个DataNode的Blocks已经全部检查并且满足了副本要求因此都在pruneReliableBlocks(…)中删除了。这时候会在标记节点为DECOMMISSIONED或者IN_MAINTENANCE之前再次来一次全扫描以防万一 if (blocks.size() 0) {if (!fullScan) { // blocks handleInsufficientlyStored(dn);outOfServiceNodeBlocks.put(dn, blocks);}如果经过二次全扫描blocks.size依然为0那么可以尝试让节点进入最终的DECOMMISSIONED或者IN_MAINTENANCE状态 如果节点当前处于 AdminState.DECOMMISSION_IN_PROGRESS状态那么可以进入AdminState.DECOMMISSIONEDif (blocks.size() 0 isHealthy) {if (dn.isDecommissionInProgress()) {dnAdmin.setDecommissioned(dn);toRemove.add(dn); // 放入到toRemove中将从outOfServiceNodeBlocks中删除}如果节点当前处于AdminState.ENTERING_MAINTENANCE状态那么可以进入AdminState.IN_MAINTENANCE状态else if (dn.isEnteringMaintenance()) {// IN_MAINTENANCE node remains in the outOfServiceNodeBlocks to// to track maintenance expiration.dnAdmin.setInMaintenance(dn);任何情况下都将当前的iterkey更新为当前的Datanode这样check()方法的下一次调度就可以从上一次调度的节点开始了 finally {iterkey dn;}对于unhealthy nodes比如在DECOMMISSION_INPROGRESS中挂掉的节点并且现在需要处理的节点(outOfServiceNodeBlocks中除去已经DECOMMISSION或者IN_MAINTENANCE的节点以及pendingNodes中的节点)已经超过了maxConcurrentTrackedNodes(dfs.namenode.decommission.max.concurrent.tracked.nodes)那么这时候unhealthy nodes最好暂时不要再占用资源了(但是不能放弃track它们还需要在pendingNodes中或者outOfServiceNodeBlocks中)因此需要将unhealthy nodes中的节点从outOfServiceNodeBlocks中取出来然后放回到pendingNodes中(上面说过pendingNodes是一个根据最近的心跳时间的优先级队列因此没有心跳的unhealthy nodes放入到pendingNodes中以后相对于健康有心跳的节点肯定是最后才会取出来)。放回到pendingNodes中是因为这些节点可能在未来的某个时间重新恢复healthy状态。 int numTrackedNodes outOfServiceNodeBlocks.size() - toRemove.size(); // outOfServiceNodeBlocks中剩余的节点int numQueuedNodes getPendingNodes().size(); // pendingNodes中的节点这些节点即将被取出来放入到outOfServiceNodeBlocksint numDecommissioningNodes numTrackedNodes numQueuedNodes; // 还需要进行check的节点的总数if (numDecommissioningNodes maxConcurrentTrackedNodes) { // 当前待处理的节点已经超过了阈值因此需要将这些unhealthy节点从outOfServiceNodeBlocks中取出来避免占用资源numDecommissioningNodes, maxConcurrentTrackedNodes, numQueuedNodes);// Re-queue unhealthy nodes to make space for decommissioning healthy nodesgetUnhealthyNodesToRequeue(unhealthyDns, numDecommissioningNodes).forEach(dn - {getPendingNodes().add(dn);// 把unhealthyDns节点重新放回到pendingNodes中outOfServiceNodeBlocks.remove(dn); // 把unhealthyDns节点从outOfServiceNodeBlocks中删除让出资源给正常healthy节点的检查上});} 对于toRemove中的节点(即不再需要在outOfServiceNodeBlocks中进行track的节点比如已经进入MAINTENANCE并且MAINTENANCE已经到期的节点已经进入DECOMMISSIONED的节点在处理过程中已经发生了异常因此没必要再处理的节点)从outOfServiceNodeBlocks中删除: for (DatanodeDescriptor dn : toRemove) {Preconditions.checkState(dn.isDecommissioned() || dn.isInService(),Removing node %s that is not yet decommissioned or in service!,dn);outOfServiceNodeBlocks.remove(dn);}对Node上的块进行全扫描以确认副本不足的块
上面讲过check()方法会首先对outOfServiceNodeBlocks中的新的节点进行全扫描以确定low-redundancy block。然后基于全扫描获取的low-redundancy block逐一进行Reconstruction的调度。一轮全扫描完成返回了insufficient redundancy的block list。这个insufficient redundancy block list随后会作为减量Prune扫描的基础。 全扫描是在方法handleInsufficientlyStored()中进行的该方法输入Datanode返回这个Datanode的insufficiently replicated的Block列表: private AbstractListBlockInfo handleInsufficientlyStored(final DatanodeDescriptor datanode) {AbstractListBlockInfo insufficient new ChunkedArrayList();processBlocksInternal(datanode, datanode.getBlockIterator(),insufficient, false); // 第四个参数是pruneReliableBlocks即发现副本数是足够的是否将这个Block从Datanode中删除。全扫描的时候是false即只扫描副本数量不足的节点不负责删除return insufficient;}核心方法是processBlocksInternal()该方法通过传入的一个Block的迭代器遍历所有的Block根据Block的副本状态将replication insufficient的副本放入到insufficientList中 private void processBlocksInternal(final DatanodeDescriptor datanode,final IteratorBlockInfo it,final ListBlockInfo insufficientList, // fullscan的时候这个list没有用到boolean pruneReliableBlocks) { // fullscan的时候pruneReliableBlocksfalse.....while (it.hasNext()) { // 循环遍历每一个Block.....numBlocksChecked;numBlocksCheckedPerLock;final BlockInfo block it.next();.....final BlockCollection bc blockManager.getBlockCollection(block);final NumberReplicas num blockManager.countNodes(block);final int liveReplicas num.liveReplicas();// Schedule low redundancy blocks for reconstruction// if not already pending.boolean isDecommission datanode.isDecommissionInProgress();boolean isMaintenance datanode.isEnteringMaintenance();boolean neededReconstruction isDecommission ?blockManager.isNeededReconstruction(block, num) : // 有效存活副本(live)并不充足小于requiredblockManager.isNeededReconstructionForMaintenance(block, num);if (neededReconstruction) {if (!blockManager.neededReconstruction.contains(block) blockManager.pendingReconstruction.getNumReplicas(block) 0 blockManager.isPopulatingReplQueues()) {// Process these blocks only when active NN is out of safe mode.blockManager.neededReconstruction.add(block,liveReplicas, num.readOnlyReplicas(),num.outOfServiceReplicas(),blockManager.getExpectedRedundancyNum(block));}}if (dnAdmin.isSufficient(block, bc, num, isDecommission, isMaintenance)) {if (pruneReliableBlocks) { // full scan的时候pruneReliableBlocks false。it.remove(); // 当一个节点的it都删除完了这个节点就可以进行decommmission了}continue;}// isSufficient()返回false// Weve found a block without sufficient redundancy.if (insufficientList ! null) {insufficientList.add(block); // 加到insufficientList中的block是必须要进行复制否则就会阻止节点下线的那些block}......}该方法的基本流程是: 通过传入的Iterator对Block进行遍历: while (it.hasNext()) { // 循环遍历每一个Block....对于当前正在处理的Block对这个Block的所有的Replica状态进行统计以便随后判断这个Block的状态以决定是否是replica sufficient: final NumberReplicas num blockManager.countNodes(block);一个Block的Replica的状态其实是依赖于这个Replica所在的Datanode的状态决定的用StoredReplicaState表示。 需要区分这个Replica的状态StoredReplicaState和节点的状态(AdminState)这里不再详述。 所有的副本状态被StoredReplicaState表述顾名思义StoredReplicaState所表述的副本状态是已经存储下来的副本的整个状态这些层面的状态一方面是为了给用户的诸如fsck的命令返回副本的统计信息更重要的这些状态的统计信息将用来决定下一步对副本是否需要进行重构的策略比如副本数是否太低太低的副本需要进行重构副本数是否太高副本数太高的副本需要进行部分的删除。 public enum StoredReplicaState {// live replicas. for a striped block, this value excludes redundant// replicas for the same internal blockLIVE,READONLY,// decommissioning replicas. for a striped block ,this value excludes// redundant and live replicas for the same internal block.DECOMMISSIONING,DECOMMISSIONED,// We need live ENTERING_MAINTENANCE nodes to continue// to serve read request while it is being transitioned to live// IN_MAINTENANCE if these are the only replicas left.// MAINTENANCE_NOT_FOR_READ maintenanceReplicas -// Live ENTERING_MAINTENANCE.// 当 一个节点处于ENTERING_MAINTENANCE中还没到达最终的IN_MAINTENANCE), 这个节点上的internal block如果没有其它副本// 那么这个node还是会接着serve 这个replica 的读请求。显然当节点进入到IN_MAINTENANCE中的时候读请求就不会过来了// 因此进入MAINTENANCE_NOT_FOR_READMAINTENANCE_NOT_FOR_READ,// Live ENTERING_MAINTENANCE nodes to serve read requests.MAINTENANCE_FOR_READ,CORRUPT,// excess replicas already tracked by blockmanagers excess mapEXCESS, // 副本数量超过了要求这些超过要求的replica 最终会被删除STALESTORAGE,// for striped blocks only. number of redundant internal block replicas// that have not been tracked by blockmanager yet (i.e., not in excess)REDUNDANT} 副本的状态StoredReplicaState是对连续布局和条带布局统一而言的并不是专指某种布局方式。但是某些特定状态在两种布局模式下的含义稍有不同。副本每一个状态的具体含义我们可以从BlockManager构造NumberReplicas对象的方法checkReplicaOnStorage()清楚地看到 private StoredReplicaState checkReplicaOnStorage(NumberReplicas counters,BlockInfo b, DatanodeStorageInfo storage,CollectionDatanodeDescriptor nodesCorrupt, boolean inStartupSafeMode) {final StoredReplicaState s;if (storage.getState() State.NORMAL) {final DatanodeDescriptor node storage.getDatanodeDescriptor();if (nodesCorrupt ! null nodesCorrupt.contains(node)) {s StoredReplicaState.CORRUPT;} else if (inStartupSafeMode) {s StoredReplicaState.LIVE;counters.add(s, 1);return s;} else if (node.isDecommissionInProgress()) {s StoredReplicaState.DECOMMISSIONING;} else if (node.isDecommissioned()) {s StoredReplicaState.DECOMMISSIONED;} else if (node.isMaintenance()) { // 当节点处于ENTERING_MAINTENANCE或者IN_MAINTENANCE的状态时if (node.isInMaintenance() || !node.isAlive()) { // 如果节点已经处于IN_MAINTENANCE或者尽管还处于ENTERING_MAINTENANCE但是节点节点已经死掉了因此数据不可读因此副本状态标记为MAINTENANCE_NOT_FOR_READs StoredReplicaState.MAINTENANCE_NOT_FOR_READ;} else { //其他情况比如节点存活并且处于ENTERING_MAINTENANCEs StoredReplicaState.MAINTENANCE_FOR_READ;}} else if (isExcess(node, b)) {s StoredReplicaState.EXCESS; // 超出正常要求的副本数} else {s StoredReplicaState.LIVE; // 没有超出正常要求的副本数}counters.add(s, 1);// //如果这个Storage是stale storage那么认为这个replica是stale状态直到收到对应DN的heartbeatif (storage.areBlockContentsStale()) {counters.add(StoredReplicaState.STALESTORAGE, 1);}} else if (!inStartupSafeMode storage.getState() State.READ_ONLY_SHARED) {s StoredReplicaState.READONLY;counters.add(s, 1);副本的大部分状态是由这个副本所在的存储状态决定的: LIVE: 就是我们最常见的正常的存活状态。 对于条带布局模式虽然预期是每一个internal block只有一个副本但是有可能存在某个internal block同时存在LIVE状态和其它状态在这种情况下LIVE状态是排他的即这个Internal Block只要有一个副本是LIVE那么我们就认为这个Internal Block的状态是LIVE。关于节点状态的一些去重操作参考BlockManager.countLiveAndDecommissioningReplicas()。从代码可以看到对于与DECOMMISSION或者MAINTENANCE相关的状态即使此时机器还在线副本可读副本状态并不会是LIVE而是对应的DECOMMISSION或者MAINTENANCE的相关状态。 READONLY 如果replica所在的Storage的类型是StorageType.State.READ_ONLY_SHARED 那么这个副本的状态就是READONLY。这个READ_ONLY_SHARED是一个特殊的HDFS特性我看了一下对应的HDFS issue HDFS-5318issue里面有对应的design doc 感兴趣的读者可以看看其大致意思就是将我们传统的通过物理磁盘存储block的方式转移到通过共享存储(NAS, S3等)存储块信息由于不再存放在某台DataNode上因此客户端可以通过任意DataNode读取到这个Block。这个READONLY状态和本文的关系不大不做详细解释。DECOMMISSIONING 一个机器decommision的过程就意味着上面的replica需要全部转移(copy)到其它机器上在全部转移完成以前这个机器上的block都是DECOMMISSIONING的状态。显然对于一个条带布局的 internal block如果这个块已经成功转移到其它的Live的机器上那么这同一个internal block就会在NameNode端同时存在LIVE和DECOMMISSIONING的状态这时候NameNode的判定状态是Live状态。关于节点状态的一些去重操作参考BlockManager.countLiveAndDecommissioningReplicas()。DECOMMISSIONED 副本所在的机器已经decommission结束。MAINTENANCE_FOR_READ 关于机器的maintenance状态感兴趣的读者可以自行学习它发生在我们需要暂时将某个节点进行下线或者升级同时又不希望这个节点的短暂下线引起集群大量的副本拷贝的场景。与maintenance相关的状态与读写的关系是: 凡是与Maintenance相关的状态(ENTERING_MAINTENANCE或者IN_MAINTENANCE状态)机器都不可能再服务写请求处于ENTERING_MAINTENANCE状态并且依然存活的机器是可以服务读请求的一个机器可能在ENTERING_MAINTENANCE的过程中死亡这时候显然上面的所有副本也是不可读的一个机器一旦真正进入了IN_MAINTENANCE状态无论是否存活都不会再server任何请求包括读请求。因为我们将一个机器进入MAINTENANCE的目的大部分都是希望机器短暂停机维修等。所以只有当一个机器处于ENTERING_MAINTENANCE并且存活它上面的副本状态才会是MAINTENANCE_FOR_READ MAINTENANCE_NOT_FOR_READ从上面的分析可以看到如果一个机器已经进入到IN_MAINTENANCE状态或者这个机器在ENTERING_MAINTENANCE的状态中死亡这时候这个机器将拒绝任何请求包括读请求。CORRUPT 这个replica所在的机器存储已经CORRUPT。请注意区分Block corrupt和Replica corrupt的区别当且仅当一个Block的所有的replica都已经corrupt了那么这个Block会被认为是corrupt。NameNode端corrupt的replica都被一个叫做CorruptReplicasMap的对象管理存放了所有的corrupted的replica以及对应的Corrupt的原因public class CorruptReplicasMap{/** The corruption reason code */public enum Reason {NONE, // not specified.ANY, // wildcard reasonGENSTAMP_MISMATCH, // mismatch in generation stampsSIZE_MISMATCH, // mismatch in sizesINVALID_STATE, // invalid stateCORRUPTION_REPORTED // client or datanode reported the corruption}// 存放了所有corrupt的replicaprivate final MapBlock, MapDatanodeDescriptor, Reason corruptReplicasMap new HashMapBlock, MapDatanodeDescriptor, Reason();在BlockManager中维护了CorruptReplicasMap的引用所有corrupt replica都是通过BlockManager.markReplicaAsCorrupt()来添加到corruptReplicas中的主要有以下情况 来自DataNode的增量块汇报(DatanodeProtocol.blockReceivedAndDelete接口)或者全量块汇报(DatanodeProtocol.blockReport)接口。在收到这些块汇报以后NameNode会对所有汇报上来的块进行时间戳和size的检测如果发现汇报上来的块和自己在blockMap中存储的块的时间戳或者size不一致则认定该块是corrupt块来自DataNode直接汇报的badBlock(通过DatanodeProtocol.reportBadBlocks())NameNode收到这些会报上来的badBlock会直接通过调用markBlockAsCorrupt()将其标记为corrupt block。DataNode在什么情况下会直接上报badBlocks呢这主要发生在比如DataNode被分配了reconstruct的任务时会从远程读取一些replica进行重构这时候如果读取发生问题就会认为是corrupted replica然后通过DatanodeProtocol.reportBadBlocks()接口上报NameNode。客户端在读取块的过程中发现块的校验失败会通过(ClientProtocol.reportBadBlocks()接口)告知NameNodeDataNode在完成了某个Recovery以后通过DataNodeProtocol.commitBlockSynchronization()接口告知NameNodeNameNode会在适当情况下将replica标记为corrupt STALESTORAGE这种状态其实是一种Corner Case。当NameNode发生重启或者Failover(从standby到达active状态)发生NameNode会将所有的DataNode 的所有的Storage标记为Stale(陈旧)状态这时候这些storage上的replica也全部为stale状态直到收到了对应的DataNode发送过来的关于这个Storage的心跳信息才会解除Stale状态。Stale状态是为了处理在NameNode发生状态转移的时候DataNode和新的NameNode发生的一些不一致状态。在Stale状态的副本的大多数处理都会延迟因为这是一个中间状态比如我们发现一个replica的副本数过多但是其中有一个副本是STALESTORAGE状态那么这时候我们不可以贸然去删除多余副本因为这时候有可能对应的 DataNode已经将副本删除等汇报上来的时候NameNode也将副本在另外机器上删除造成副本丢失。REDUNDANT 只适用于条带布局某一个internal block的副本数量多余一个。EXCESS: 含义其实于REDUNDANT只不过EXCESS指的是NameNode已经发现了该replica有多余副本(比如通过fsck或者通过RedundancyMonitor的定时扫描线程)同时将这个replica的信息放到excessRedundancyMap中去所有放到excessRedundancyMap中的internal block都会采用响应策略删除一个replica的多余副本。 从上面的状态分析可以看到副本的状态和一些存储的状态有一部分是用户操作的比如Decommission和Maintenance有些是机器自己汇报的比如CORRUPT, 有些是集群本身的状态发展而成的比如LIVE, STALESTORAGE, REDUNDANT, EXCESS 在统计了这个Block的所有Replica的状态并存放在对象NumberReplicas中以后就开始判断节点是否需要进行Reconstruction。如果需要则添加到neededReconstruction中: boolean isDecommission datanode.isDecommissionInProgress();boolean isMaintenance datanode.isEnteringMaintenance();boolean neededReconstruction isDecommission ?blockManager.isNeededReconstruction(block, num) : // 有效存活副本(live)并不充足小于requiredblockManager.isNeededReconstructionForMaintenance(block, num);if (neededReconstruction) {if (!blockManager.neededReconstruction.contains(block) blockManager.pendingReconstruction.getNumReplicas(block) 0 blockManager.isPopulatingReplQueues()) {// 需要进行Reconstruction添加到neededReconstruction中blockManager.neededReconstruction.add(block,liveReplicas, num.readOnlyReplicas(),num.outOfServiceReplicas(),blockManager.getExpectedRedundancyNum(block));}}我们后面将详细讲解isNeededReconstruction()方法和isNeededReconstructionForMaintenance()方法的判断过程。 如果我们发现对应的Block的副本数量足够了并且当前处于prune的过程就将对应的block从iterator中删除即从iterator对应的列表中删除 if (dnAdmin.isSufficient(block, bc, num, isDecommission, isMaintenance)) {if (pruneReliableBlocks) { // full scan的时候pruneReliableBlocks false。it.remove(); // 当一个节点的it都删除完了这个节点就可以进行decommmission了}continue;}我们从调用者check()方法可以看到一个Node最终能够进入IN_MAINTENANCE或者最终进入DECOMMISSIONED状态的标准是 outOfServiceNodeBlocks中对应的Node下面挂载的所有的Block的副本状态满足了指定要求。这里的指定要求就是dnAdmin.isSufficient()返回true这里的it.remove()操作就是将这个Block从outOfServiceNodeBlocks中对应的Node下面删除。 从上面的逻辑可是看出来将一个块加入到neededReconstruction和将这个块从Iterator中移除是两个独立的逻辑。我们的疑问是难道不应该是只要一个块加入到了neededReconstruction中就不应该从Iterator中移除吗并不是这样的。仔细分析dnAdmin.isSufficient()的代码和比如isNeededReconstruction()以及isNeededReconstructionForMaintenance的代码可以看到下面的两种情况这些Block需要进行Reconstruction但是已经不再会阻碍Datanode的decommission和maintenance了 虽然没有满足当前块期望的存活的冗余的要求但是已经满足整个系统的默认的冗余要求比如当前块设置的副本数是5系统默认为3那么如果当前块的副本数是4显然会进行reconstruction但是Reconstruction不会阻碍DataNode的decommission/maintenance虽然满足了当前块期望的存活的冗余的要求但是整个放置不满足要求。这种情况下显然会调度对应的Reconstruction但是同样的Reconstruction不会阻碍DataNode的decommission/maintenance。 否则则将对应的Block添加到insufficientList中。这显然发生在full scan的时候因为full scan的时候insufficientList不为空因此将full scan获取的副本不足的Block放入insufficientList中。 if (insufficientList ! null) {insufficientList.add(block); // 加到insufficientList中的block是必须要进行复制否则就会阻止节点下线的那些block}这就是processBlocksInternal()的基本流程。通过传入到迭代器对Block进行迭代迭代过程中获取这个Block的所有的Replica的状态统计信息根据副本状态的统计信息对于需要进行Reconstruct的Block将节点加入到neededReconstruction中。
基于全量扫描的结果进行Prune扫描
对于某个DataNode的全量扫描结束以后生成了一个Insufficient Redundancy Block List这个list中的Block都是副本数量不足(isSufficient() False)的Block只要DataNode上还有任何一个Block的副本数不足DataNode就不可以进入Maintenance或者Decommissioned状态。增量扫描就是
扫描这个Insufficient Redundancy Block List如果这个List中依然有Block需要进行Reconstruction但是还没有进行Reconstruction就对其调度ReconstructionWork。见着这里的Block的状态是否已经从Insufficient Redundancy 中改出如果改出就从List中删除
增量剪枝扫描和上一节的全量扫描都调用的是processBlocksInternal()方法只不过参数不同 全量扫描handleInsufficientlyStored()的时候Block的迭代器IteratorBlockInfo it是这个DataNode上所有的Block的List的迭代器而增量剪枝的时候这个迭代器是全量扫描所获取的low-redundancy的block的list或者上一次增量剪枝扫描所剩下的Block List所形成的迭代器即outOfServiceNodeBlocks中这个Datanode的Block List的迭代器代表了需要进行检查和处理的low-redundancy的block。 // 全量prune扫描private AbstractListBlockInfo handleInsufficientlyStored(final DatanodeDescriptor datanode) {AbstractListBlockInfo insufficient new ChunkedArrayList();processBlocksInternal(datanode, datanode.getBlockIterator(),insufficient, false); // 第四个参数是pruneReliableBlocks即发现副本数是足够的是否将这个Block从Datanode中删除。全扫描的时候是false即只扫描副本数量不足的节点不负责删除return insufficient;}// 增量prune扫描private void pruneReliableBlocks(final DatanodeDescriptor datanode,AbstractListBlockInfo blocks) {processBlocksInternal(datanode, blocks.iterator(), null, true);}全扫描的时候第三个参数insufficientList传入的是一个初始化的List这样全扫描以后的结果会放入到insufficientList中即全扫描所获取的low-redundancy的结果。而增量剪枝扫描的事后insufficientList是空的因为是直接传入IteratorBlockInfo it的扫描过程中对于已经replication sufficient的block会直接通过这个迭代器指针从List中删除。这个从上面的代码能够看出来。 全量扫描的时候pruneReliableBlocks为false即如果发现一个Block是 replication sufficient不执行删除因此此时扫描的迭代器是来自于Datanode的所有block组成的list。而增量剪枝扫描时pruneReliableBlocks为true即如果发现一个Block是 replication sufficient则进行prune即认为这个Block已经通过Reconstruction进入到了副本充足的状态因此从outOfServiceNodeBlocks中删除。 if (dnAdmin.isSufficient(block, bc, num, isDecommission, isMaintenance)) {if (pruneReliableBlocks) { // full scan的时候pruneReliableBlocks false。it.remove(); // 当一个节点的it都删除完了这个节点就可以进行decommmission了}continue;}是否需要进行Reconstruction的判断过程
上面说过如果我们发现一个Replica所在的机器处于DECOMMISSIONING或者ENTERING_MAINTENANCE状态那么就需要判断这个Replica对应的Block是否需要进行Reconstruction以扩充副本 boolean isDecommission datanode.isDecommissionInProgress();boolean isMaintenance datanode.isEnteringMaintenance();boolean neededReconstruction isDecommission ?blockManager.isNeededReconstruction(block, num) : // 有效存活副本(live)并不充足小于requiredblockManager.isNeededReconstructionForMaintenance(block, num);DECOMMISSIONING状态下是否需要进行Reconstruction
对于一个正在DECOMMISSIONING的节点其实是调用isNeededReconstruction()方法 boolean isNeededReconstruction(BlockInfo storedBlock,NumberReplicas numberReplicas) {// 不考虑pending的replica这个block的存活replica的数量大于等于期望的副本数量(块的副本数)return isNeededReconstruction(storedBlock, numberReplicas, 0);}可以看到当且仅当这个块是一个COMPLETE的块(不是一个正在写入的块或者损坏的块)并且没有足够的有效Replica那么这个Block就是一个需要Reconstruction的Block: boolean isNeededReconstruction(BlockInfo storedBlock,NumberReplicas numberReplicas, int pending) {return storedBlock.isComplete() !hasEnoughEffectiveReplicas(storedBlock, numberReplicas, pending);}所以核心方法在于hasEnoughEffectiveReplicas() // 如果 live pending replicas 的数量不小于所需要的replica数量并且还有pending的replica或者没有pending的并且已经满足放置策略// 这里的含义是如果有pending的那么我们先不用考虑是否满足placement policyboolean hasEnoughEffectiveReplicas(BlockInfo block,NumberReplicas numReplicas, int pendingReplicaNum) {int required getExpectedLiveRedundancyNum(block, numReplicas); // 先看看需要多少个live的副本int numEffectiveReplicas numReplicas.liveReplicas() pendingReplicaNum;return (numEffectiveReplicas required) (pendingReplicaNum 0 || isPlacementPolicySatisfied(block));}从hasEnoughEffectiveReplicas()方法可以看到如果一个块的有效replica(Effective Replica)的数量不小于所期望的存活Replica的数量并且这个块的放置状态也满足要求或者尽管放置状态不满足要求但是还有pending的replica(没有来得及汇报的replica也许这个汇报上来的replica会让放置状态从不满足要求变为满足要求)那么我们认为这个Block有足够的有效replica(Effective Replica)。反之则认为没有足够的有效replica(Effective Replica)。 那么一个块的所期望的存活Replica是怎么计算的呢这是在方法getExpectedLiveRedundancyNum()中计算的: // 期望的最少的live replica的数量public short getExpectedLiveRedundancyNum(BlockInfo block,NumberReplicas numberReplicas) {// 对于striped blockexpectedRedundancy 数量指的是data block parity block的数量,// 对于replicationexpectedRedundancy指的是replication factorfinal short expectedRedundancy getExpectedRedundancyNum(block);// 假如当前我的block配置的副本是5 有2个副本是处于maintenance那么我期待的live 副本数量是5-23return (short)Math.max(expectedRedundancy -numberReplicas.maintenanceReplicas(), // 处于maintenance中的replica也算是live这本身就是maintenance的目的让replica短时间可以容忍丢失但是处于decommission的不能算live// 最小的maintenance 副本数量。对于striped block最小的maintenance副本数量就是data block 的数量说明对于// stripe block, 不允许data block丢失getMinMaintenanceStorageNum(block));}从上面的getExpectedLiveRedundancyNum()方法可以看到 如果一个Block的所有Replica没有任何一个有诸如IN_MAINTENANCE或者DECOMMISSIONED等特殊状态那么期望的存活副本数就是系统配置的Replication Factor(Erasure Coding又是另外的计算方式具体可以参考 TODO这篇文章)。 如果有节点处于IN_MAINTENANCE状态那么会进行一些特殊处理。即如果有的Replica所在的节点处于MAINTENANCE并且节点正常存活那么期望的节点需要减去处于MAINTENANCE状态的副本数量或者等于最小的MAINTENANCE副本数量(dfs.namenode.maintenance.replication.min所配置默认是1)两者的较大值。 这里我们梳理了一下上述方法所涉及到的副本的一些状态和数量定义 什么是待定副本数(Pending Replica): 就是我们讲的PendingReconstructionBlocks对象维护的replica - targets信息即一个block写完(COMMITTED或者COMPLETED)但是NameNode还没收到足够的DataNode汇报那么预期还需要收到多少个DataNode的汇报的数量。比如3副本的BlockNameNode目前只收到1个DataNode的汇报那么这个Block的pendingReplicaNum是2。什么叫有效副本数量(Effective Replicas):从方法hasEnoughEffectiveReplicas()的代码 int numEffectiveReplicas numReplicas.liveReplicas() pendingReplicaNum;即存活状态的副本数量再加上待定状态的副本数量。存活状态的副本是有效副本很容易理解但是为什么待定的副本也算作有效副本呢因为待定的副本是正常状态下还缺失的待汇报的数量这种缺失是很正常的正常情况下只需要再等等就可以因此待定的副本也算作有效。什么叫允许进入Maintenance状态的最小存活副本数即要想将某一个replica所载的DataNode进入maintenance状态那么这个DataNode上的每一个Block需要多少个存活的副本这是为了避免某个节点进入maintenance状态造成了某些数据不可读的状态。从方法getMinMaintenanceStorageNum()可以看到对于纠删码如果进入ENTERING_MAINTENANCE状态以后依然最少有dataBlockNum个副本存活那么这个replica就可以进入Maintenance状态。对于3副本这个默认值是1即ENTERING_MAINTENANCE的节点中如果所有的Block都至少有一个存活的副本那么这个Datanode就可以正式进入MAINTENANCE状态。这就是为什么StoredReplicaState在进入正式的MAINTENANCE状态以前有一个ENTERING_MAINTENANCE状态在ENTERING_MAINTENANCE状态时DataNode会有对应的Monitor确认所有的Block的live replica数量都大于getMinMaintenanceStorageNum()才会从ENTERING_MAINTENANCE进入到IN_MAINTENANCE状态避免进入到IN_MAINTENANCE状态(进入IN_MAINTENANCE状态以后的节点很可能出现短时不可服务比如短暂的关机维修升级等)以后导致数据副本缺失。当前期望的存活副本数量(Expected Live Redundancy): 从getExpectedLiveRedundancyNum()方法的return (short)Math.max(expectedRedundancy - numberReplicas.maintenanceReplicas(),getMinMaintenanceStorageNum(block));代码可以看到不考虑特殊情况对于连续布局期望的副本数量就是配置的副本数对于纠删码期待的副本数量就是dataBlocksNum parityBlocksNum 比如RS(6,2)中等于8。但是由于有些Block的部分副本有可能处于maintenance状态这些副本虽然暂时不可用但是并没有丢失因此期待的副本数量应该减去这些处于Maintenance状态的副本。比如RS(6,2)中有3个副本都处于maintenance状态那么我期待的存活副本数量是8-35。同时期望的存活副本数量应该不小于允许进入maintenance的最小存活副本数量。什么叫足够多的有效Replica: 即当前有效的副本数量不小于期望的副本数量并且当前还有pending的副本或者虽然没有pending的副本但是整个Block的replica分布处于满足PlacementPolicy要求的状态。 这意味着如果有效的副本数量不小于期望的副本数量并且还有pending的副本这时候即使整个Block的副本不满足 PlacementPolicy要求的状态也认为有足够多的有效Replica这样判定是因为存在pending的副本所以认为极有可能当pending的副本的DataNode汇报上来以后PlacementPolicy就会被满足。
MAINTENANCE状态下是否需要进行Reconstruction
对于一个处于ENTERING_MAINTENANCE的节点通过方法isNeededReconstructionForMaintenance()来判定是否需要进行Reconstruction boolean isNeededReconstructionForMaintenance(BlockInfo storedBlock,NumberReplicas numberReplicas) {return storedBlock.isComplete() (numberReplicas.liveReplicas() getMinMaintenanceStorageNum(storedBlock) || // 默认是1!isPlacementPolicySatisfied(storedBlock));}和DECOMMISSIONING一样Reconstruction也仅仅是针对已经COMPLETED的Block对于还处在构造中或者仅仅是COMMITTED或者CORUPTTED等等其他状态的Block不在考虑范畴。
从isNeededReconstructionForMaintenance()方法得知一个Block需要进行Reconstruction的条件是:
这个节点是COMPLETED状态 并且满足以下要求 这个节点的Live Replica的数量已经小于所配置的允许进入MAINTENANCE的最小Live副本数量(dfs.namenode.maintenance.replication.min所配置默认是1) 或者虽然Live Replica的数量已经满足了允许进入MAINTENANCE的最小Live副本数量但是不满足副本放置策略
综上所述processBlocksInternal()方法如果发现一个Datanode处于IN_MAINTENANCE或者DECOMMISSION_IN_PROGRESS并且发现对应的块(这个块有副本在这台机器上)需要进行Reconstruction那么就会将这个块添加到neededReconstruction中。后面会有另外一个独立线程RedundancyMonitor对应生成Reconstruction Task。
对于isPlacementPolicySatisfied()方法本文不再详细讲解有兴趣的读者自行阅读代码。
RedundancyMonitor生成和调度ReplicationWork
在我的另外一篇文章 HDFS 块重构和RedundancyMonitor详解 中详细介绍了RedundancyMonitor对neededReconstruction的处理过程即将neededReconstruction中的重构任务进行基于优先级的派发以及DataNode对BlockReconstructionWork的任务的处理过程。但是请注意:
由节点的decommission或者maintenance所带来的重构任务放在neededReconstruction中。但是由于decommission/maintenance而带来的low-redundancy block只是neededReconstruction中Block的来源的一部分。顾名思义RedundancyMonitor负责的是基于各种原因产生的Low-Redundancy Block的调度。这些原因可能包括(在我的文章HDFS 块重构和RedundancyMonitor详解 中有详细讲解) 本文所讲的由于节点的Decommission或者Maintenance的发生DatanodeAdminDefaultMonitor会针对目标节点进行块扫描从而发现对应的Low-Redundancy Block在BlockManager中的Daemon Thread中会不断对BlocksMap进行全扫描会发现Low-Redundancy Block客户端在写完文件以后会检查文件的所有Block的Redundancy从而发现对应的Low-Redundancy Block来自pendingReconstruction中的Block的转换。 在HDFS 块重构和RedundancyMonitor详解 中介绍BlockReconstructionWork的任务在DataNode端的处理的时候我比较偏向于介绍基于Erasure Coding(纠删码)的处理逻辑因为Erasure Coding的处理方式涉及到多个data 和 parity的block的管理和重算比基于Replication的块冗余策略更加复杂。有兴趣的读者读了我的这篇文章完全可以轻松地再去研究基于Replication的块冗余策略的在DataNode端的重构流程。
相关参数调整
在了解了整个Decommission的过程以后
增加Decommission过程中每次调度的Block数量
Decommission完成的标志以及节点状态的修改参数调优
propertynamedfs.namenode.replication.work.multiplier.per.iteration/namevalue10/value/property在满足每次最大的需要进行replication的数量的情况下有些节点会因为没有任何一个source node而抛出异常 final DatanodeDescriptor[] srcNodes chooseSourceDatanodes(block,containingNodes, liveReplicaNodes, numReplicas,liveBlockIndices, priority);short requiredRedundancy getExpectedLiveRedundancyNum(block,numReplicas);if(srcNodes null || srcNodes.length 0) {// block can not be reconstructed from any nodeLOG.debug(Block {} cannot be reconstructed from any node, block);NameNode.getNameNodeMetrics().incNumTimesReReplicationNotScheduled();return null;}从上面的代码可以看到BlockManager 会通过chooseSourceDatanodes()方法来选择进行replication的源节点。如果没有选择出任何一个源节点那么显然这个replication是无法工作的。
代码改进: 如果我们无法为一个block选择出任何一个可用的源节点那么需要打印对应的原因以方便HDFS管理员进行针对性的调整。 if (priority ! LowRedundancyBlocks.QUEUE_HIGHEST_PRIORITY (!node.isDecommissionInProgress() !node.isEnteringMaintenance()) node.getNumberOfBlocksToBeReplicated() maxReplicationStreams) {continue; // already reached replication limit}if (priority ! LowRedundancyBlocks.QUEUE_HIGHEST_PRIORITY (!node.isDecommissionInProgress() !node.isEnteringMaintenance()) node.getNumberOfBlocksToBeReplicated() maxReplicationStreams) {continue; // 这里是否可以有TRACE日志打印无法选择该节点的原因}// for EC here need to make sure the numReplicas replicates state correct// because in the scheduleReconstruction it need the numReplicas to check// whether need to reconstruct the ec internal blockbyte blockIndex -1;if (isStriped) {blockIndex ((BlockInfoStriped) block).getStorageBlockIndex(storage);countLiveAndDecommissioningReplicas(numReplicas, state,liveBitSet, decommissioningBitSet, blockIndex);}if (node.getNumberOfBlocksToBeReplicated() replicationStreamsHardLimit) {continue; // 这里是否可以有TRACE日志打印无法选择该节点的原因}同时我们可以看到即使一个Block没有为它选出一个合适的源节点而放弃为它构建ReplicationWork这个节点依然被计数到blocksToProcess中。所以如果即使我们在一轮调度中设置了一个合理的blocksToProcess值但是实际上这一轮的大部分调度名额都被那些没有选出合适source节点的Block占用了实际进行的有效调度进展非常缓慢。 所以我们最好修改代码让blocksToProcess指的是有效ReplicationWork的调度的数量而不是处理的blocks的数量。
代码改进 在还有如果并没有为当前的Block选出一个合适的源节点即并没有为这个Block调度对应的ReplicationWork那么就递进往后选择Block保证假如priorityQueues中有足够多的需要进行Replicate的Block的情况下这些可以被调度的Block在一轮调度中能够被调度出去。
怎么避免低优先级的Block一直被饿死 这也是我们在decommission一个节点的时候遇到的问题。节点decommission导致的ReplicationWork其优先级是很低的而用户在写入数据所带来的调度的优先级是很高的。因此假如系统很繁忙这个decommission会一直没有任何进展。
日志分析和代码印证
2024-07-02 05:27:42,672 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073743420_2596
2024-07-02 05:27:47,482 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Removing pending reconstruction for blk_1073743420_2596
2024-07-02 05:27:47,482 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Reported block blk_1073743420_2596 on 10.30.0.172:9866 size 2990310 replicaState FINALIZED
2024-07-02 05:27:47,482 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Reported block blk_1073743420_2596 on 10.30.0.172:9866 size 2990310 replicaState FINALIZED
2024-07-02 05:27:47,855 TRACE org.apache.hadoop.hdfs.server.blockmanagement.DatanodeAdminManager: Block blk_1073743420_2596 does not need replication.
2024-07-02 05:27:47,856 TRACE org.apache.hadoop.hdfs.server.blockmanagement.DatanodeAdminManager: Block blk_1073743420_2596 does not need replication.
2024-07-02 05:27:47,856 TRACE org.apache.hadoop.hdfs.server.blockmanagement.DatanodeAdminManager: Block blk_1073743420_2596 does not need replication.
2024-07-02 05:28:17,865 TRACE org.apache.hadoop.hdfs.server.blockmanagement.DatanodeAdminManager: Block blk_1073743420_2596 does not need replication.2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795480_54656
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795483_54659
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795486_54662
2024-07-02 06:18:49,908 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795487_54663
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795538_54714
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795547_54723
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795548_54724
2024-07-02 06:18:52,910 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795549_54725
2024-07-02 06:18:55,911 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795604_54780
2024-07-02 06:18:55,911 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795605_54781
2024-07-02 06:18:55,911 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795608_54784
2024-07-02 06:18:55,911 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795613_54789
2024-07-02 06:18:58,912 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795651_54827
2024-07-02 06:18:58,912 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795654_54830
2024-07-02 06:18:58,912 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795655_54831
2024-07-02 06:18:58,912 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073795664_54840
2024-07-02 03:20:54,311 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Block blk_1073810374_69550 cannot be reconstructed from any node
2024-07-02 04:56:11,852 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Creating a ReplicationWork to reconstruct blk_1073810374_69550
2024-07-02 04:56:13,920 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Removing pending reconstruction for blk_1073810374_69550
2024-07-02 04:56:13,920 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Reported block blk_1073810374_69550 on 10.30.0.172:9866 size 9158636 replicaState FINALIZED
2024-07-02 04:56:13,920 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Reported block blk_1073810374_69550 on 10.30.0.172:9866 size 9158636 replicaState FINALIZED
2024-07-02 06:14:38,431 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Reported block blk_1073810374_69550 on 10.30.0.172:9866 size 9158636 replicaState FINALIZED
2024-07-02 06:14:38,431 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockManager: Reported block blk_1073810374_69550 on 10.30.0.172:9866 size 9158636 replicaState FINALIZED在HDFS的节点选择过程中如果单个节点的负载过高(xCeiver数量很高)那么这个节点就无法被选作Replica的DataNode。显然 这种设计适合于DataNode节点数量大于节点的副本数量的情况。但是假如我们是一个很小的HDFS集群DataNode节点数量就等于默认副本数量在这种情况下显然节点负载不应该是考虑因素我们就应该无条件将一个文件的所有replica分配到所有节点而不是因为负载过高导致写入文件写入失败即这种情况下这种设计变成了过度设计将问题恶化。
避免单个节点负载过高导致这个节点被排除在待分配节点中。下面的DEBUG日志显示了我们的一个节点由于负载过高而被排除
2024-07-02 08:41:40,343 DEBUG org.apache.hadoop.hdfs.server.blockmanagement.BlockPlacementPolicy: [
Node /rccp205-3a/10.30.0.172:9866 [Datanode 10.30.0.172:9866 is not chosen since the node is too busy (load: 11 10.0).Datanode None is not chosen since required storage types are unavailable for storage type DISK.