HDFS 集群跑久了,磁盘水位、机架分布、预算账单这三件事迟早会把你逼到同一个操作面前:缩容。我见过太多团队在扩容时轻车熟路,一到缩容就手忙脚乱,最惨的一次是某数据平台的同学直接关机下线两台 DataNode,结果触发了大量块的重新复制,NameNode 的 RPC 队列瞬间打满,整个集群读写卡了将近四十分钟。缩容这件事,本质上不是"删机器",而是一次有计划的数据迁移工程,做得好是平滑瘦身,做得不好就是一次自伤式的故障演练。
这篇内容我想把 HDFS 集群节点缩容这件事从头到尾讲透:为什么缩容比扩容更需要谨慎、Decommission(退役)机制到底在底层做了什么、完整的实操流程怎么走、参数怎么算、坑在哪里。适合已经有一定 HDFS 运维基础、正准备做集群降配或者机架调整的工程师参考,也适合刚接触大数据平台、想搞明白"下线一个节点背后发生了什么"的读者。全文围绕真实可复现的操作路径展开,参数和命令都可以直接拿去改改就用。
1. 缩容这件事的整体设计与思路拆解
1.1 为什么缩容不能"直接关机"
先把最核心的认知摆正:HDFS 是一个强副本一致性的分布式文件系统,默认三副本。你下线一台 DataNode,意味着这台机器上承载的每一个 Block 副本都少了一份。如果直接关机或者停进程,NameNode 会在心跳超时(默认 10.5 分钟,即 2 次 5 分钟心跳间隔加 30 秒检查)后把这些副本标记为失效,然后启动全量复制来补齐副本数。这个过程的可怕之处在于:复制是并发的、不受控的,集群带宽会被瞬间抽干,同时 NameNode 要处理海量的块状态变更,RPC 压力陡增。
而 Decommission 机制的设计初衷,就是把这种"突发性故障恢复"变成"可控的计划性迁移"。你告诉 NameNode"我要退役这台节点",NameNode 会把这台节点上的块标记为"待迁移",然后有条不紊地把副本复制到其他健康节点上,复制完成后才真正把这台节点踢出集群。整个过程你可以控制并发、控制带宽、观察进度,出问题还能随时取消。
这个区别用生活化的类比很好理解:直接关机像是你突然从一栋楼里搬走一户人家,物业发现后紧急调动物业资源去补位,一片混乱;Decommission 则是你提前跟物业报备,物业按流程安排新住户入住、交接完成后再让你搬走,全程有序。
1.2 缩容方案选型:三种路径的取舍
实际运维中,缩容大致有三条路径,选哪条取决于你的集群规模、版本和风险承受能力。
| 方案 | 核心操作 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Decommission 退役 | 配置 exclude 文件 + refreshNodes | 官方机制、可控、可回滚 | 迁移期间占用带宽、耗时较长 | 绝大多数生产环境首选 |
| 直接下线 | 停进程/关机 | 操作简单 | 触发紧急复制、风险极高 | 仅限测试环境或节点已空 |
| 数据先清空再下线 | 先做数据均衡迁移再停节点 | 对 NameNode 压力小 | 需要额外工具、流程长 | 超大规模集群精细操作 |
我的建议很明确:生产环境一律走 Decommission。原因在于它是唯一一个"NameNode 知情且配合"的方案,你能拿到迁移进度、能设置并发上限、能在中途反悔。直接下线只适合一种情况——这台节点上本来就没多少数据,或者你已经在业务低峰期且集群余量充足。至于第三种,通常是超大规模集群(上千节点)为了进一步降低 NameNode 压力才考虑的精细化操作,普通规模没必要上。
1.3 缩容前必须想清楚的三个问题
在动手之前,有三个问题必须先有答案,否则后面一定出乱子。
第一,缩容后剩余容量够不够。这不是简单看"总容量减一台",而是要算上副本因子和预留水位。假设集群现有 10 台节点,每台 10TB 可用,三副本,实际可存数据约 33TB。缩掉 2 台后剩 8 台 80TB 原始容量,可存数据约 26TB。如果你的实际数据量已经接近 30TB,那缩容后就会长期处于高水位,写入会频繁触发副本选择失败。一般建议缩容后集群使用率不超过 70%,留足缓冲。
第二,缩容期间业务能不能承受带宽波动。Decommission 的复制流量和业务读写共享网络,如果集群网络本身吃紧,迁移期间业务延迟会明显上升。所以要么选业务低峰期,要么用dfs.datanode.balance.bandwidthPerSec之类的参数限制迁移带宽。
第三,机架感知配置是否要同步调整。如果你的集群开启了机架感知(topology),下线某机架的节点后,副本的机架分布策略会变化,需要确认剩余机架的副本分布仍然满足容灾要求。这一点经常被忽略,缩容后才发现某个机架只剩一个副本,容灾能力打了折扣。
2. 核心机制解析与关键参数实操要点
2.1 Decommission 在 NameNode 内部到底做了什么
理解机制才能理解参数。当你把节点加入 exclude 文件并执行refreshNodes后,NameNode 内部大致经历这么几个阶段:
节点状态从In Service变为Decommission In Progress。此时 NameNode 会扫描这台 DataNode 上所有的块,为每个块生成一个"复制任务",目标是把这个块复制到其他节点,使得排除该节点后副本数仍然满足配置的 replication factor。这些复制任务进入 NameNode 的复制监控队列(ReplicationMonitor),由它按优先级和并发度调度。
复制任务完成后,该节点上的块被标记为"已迁移",当这台节点上所有块都迁移完毕,节点状态变为Decommissioned,此时它才真正不再参与读写,你可以安全地停进程、关机、下架。
这里有个关键细节:Decommission 的复制和普通的副本补齐走的是同一套复制机制,但优先级不同。NameNode 会优先处理 Decommission 相关的复制,因为节点退役是有时限压力的。这也意味着,如果集群同时存在大量失效副本,Decommission 的进度会被拖慢。
2.2 三个必须调对的参数
第一个是dfs.namenode.replication.work.multiplier.per.iteration(默认 2)。这个参数控制每轮 ReplicationMonitor 迭代中,每个 DataNode 最多能发起多少个复制任务。集群规模大、节点多的时候,适当调大能加快迁移,但调太大又会压垮网络。我的经验是:百节点以内保持默认,几百节点可以调到 4 到 6,再大就要配合带宽限制一起用。
第二个是dfs.datanode.balance.bandwidthPerSec(默认 10MB/s)。注意这个参数名字里带 balance,但它同样影响 Decommission 的复制带宽,因为底层都是 DataNode 之间的块传输。生产环境我一般设成 50MB/s 到 100MB/s,具体看你的网络是万兆还是千兆。千兆网络(理论 125MB/s)建议不超过 50MB/s,万兆可以放开到 200MB/s 以上。算一下:如果你要迁移 5TB 数据,50MB/s 大约需要 510241024/50 ≈ 104857 秒,差不多 29 小时。这个时间预期要提前跟业务方对齐。
第三个是dfs.namenode.decommission.blocks.per.interval和dfs.namenode.decommission.max.concurrent.tracked(不同版本参数名略有差异)。前者控制每次处理的块数量,后者控制并发跟踪的块上限。这两个参数直接决定 NameNode 处理退役块的速度,调大能加速但要盯着 NameNode 的堆和 RPC 队列。
注意:这些参数修改后需要重启对应的服务或者通过滚动重启生效,NameNode 侧的参数改动影响面大,务必在低峰期操作,并提前做好配置备份。
2.3 exclude 文件的正确写法与常见误区
exclude 文件(dfs.hosts.exclude指向的文件)是 Decommission 的入口。写法上,一行一个主机名,必须是 DataNode 注册时使用的主机名(通常是 hostname 或 FQDN),不能写 IP,除非你的集群就是用 IP 注册的。
常见的坑有三个。第一,主机名写错或者大小写不一致,导致refreshNodes后节点根本没进入退役状态,你以为在迁移,其实啥也没发生。第二,exclude 文件里加了注释或者空行格式不对,某些老版本解析会出问题,建议保持纯净的一行一个主机名。第三,多个节点同时加入 exclude,一次性退役太多节点,导致剩余容量不足或者迁移压力过大。正确做法是分批,一次退役一到两台,等状态变成 Decommissioned 再处理下一批。
还有一个容易被忽略的点:exclude 文件是"增量生效"的,你往里面加节点,refreshNodes 后这些节点开始退役;但如果你从文件里删掉某个正在退役的节点再 refreshNodes,它会停止退役并回到 In Service 状态。这个特性可以用来做"反悔"操作,但前提是它还没变成 Decommissioned。
3. 完整实操流程与现场记录
3.1 缩容前的体检清单
动手前先做一轮体检,把下面这些信息摸清楚,后面每一步都用得上。
# 查看集群整体容量和使用率 hdfs dfsadmin -report # 查看所有 DataNode 状态(重点看 In Service / Decommissioning 数量) hdfs dfsadmin -report | grep -E "Name|State|Configured Capacity|DFS Used%" # 查看待退役节点上的块数量(粗略评估迁移量) hdfs fsck / -blocks | grep -c "blk_" # 确认副本因子配置 hdfs getconf -confKey dfs.replicationdfsadmin -report的输出里,重点看DFS Remaining%和DFS Used%。如果当前使用率已经超过 75%,缩容要格外谨慎,最好先清理一些冷数据或者临时扩容过渡。同时记录下待退役节点的DFS Used数值,这就是你要迁移的数据量,用它除以带宽就能估算迁移时长。
体检时还要确认一件事:集群是否有正在进行的块恢复或者均衡操作。如果有,等它们结束再开始缩容,否则多个任务抢带宽,谁都跑不快。
3.2 配置 exclude 文件并触发退役
假设我们要退役两台节点,主机名分别是datanode-09和datanode-10(虚构示例)。先确认hdfs-site.xml里 exclude 文件的路径配置:
<property> <name>dfs.hosts.exclude</name> <value>/etc/hadoop/conf/dfs.exclude</value> </property>然后编辑这个文件,加入待退役节点:
# 编辑 exclude 文件,一行一个主机名 cat >> /etc/hadoop/conf/dfs.exclude << 'EOF' datanode-09 datanode-10 EOF # 确认内容 cat /etc/hadoop/conf/dfs.exclude接着在 NameNode 上执行刷新:
hdfs dfsadmin -refreshNodes执行完立刻查看状态:
hdfs dfsadmin -report | grep -A 2 "datanode-09"正常情况下,你会看到datanode-09的状态变成Decommission In Progress。如果还是In Service,八成是主机名不匹配或者 exclude 文件路径没配对,回去检查。
3.3 监控迁移进度与关键指标
退役开始后,监控是重头戏。最直接的命令还是dfsadmin -report,但要看对字段:
# 持续观察退役进度,每 30 秒刷新一次 watch -n 30 "hdfs dfsadmin -report | grep -E 'Decommission|Under replicated|Blocks'"重点盯三个指标:Decommissioning Nodes的数量(应该等于你加入 exclude 的节点数)、Under-replicated Blocks(迁移期间会先升后降,最终归零)、以及待退役节点的Blocks数量(应该持续下降)。
除了命令行,NameNode 的 Web UI(默认 9870 端口)上有个Datanodes页面,能直观看到每个节点的状态和块数变化。我习惯同时开着 UI 和命令行,UI 看趋势,命令行看精确数值。
迁移期间还要关注 NameNode 的负载:
# 查看 NameNode 的 RPC 队列和堆使用(jstat 需要 JDK 环境) jstat -gcutil <namenode_pid> 5000 # 查看集群网络流量(在 DataNode 上) sar -n DEV 5 10如果发现 NameNode 的 RPC 队列持续堆积,或者 GC 频繁,说明复制压力过大,应该调小dfs.namenode.replication.work.multiplier.per.iteration或者降低带宽上限,让迁移慢下来。宁可多花几个小时,也别把 NameNode 搞挂。
3.4 退役完成后的收尾动作
当dfsadmin -report里待退役节点状态变成Decommissioned,且Under-replicated Blocks为 0,说明迁移完成。这时候才能做收尾:
# 确认节点状态 hdfs dfsadmin -report | grep -B 1 -A 3 "Decommissioned" # 确认没有欠副本块 hdfs fsck / -blocks | grep "Under-replicated" # 停止该节点上的 DataNode 进程 hdfs --daemon stop datanode # 从 exclude 文件移除(可选,取决于你是否还要保留记录) # 如果节点要彻底下架,建议保留在 exclude 文件里防止误启动这里有个实操细节:节点变成 Decommissioned 后,它的 DataNode 进程其实还在跑,只是不再接收新数据、不再参与副本放置。你可以安全地停掉它。但如果你打算把这台机器重新利用(比如改作其他用途),记得把它从 exclude 文件里移除,否则将来重新加入集群时会被拒绝。
收尾时还要做一次全量 fsck,确认没有因为迁移产生损坏块:
hdfs fsck / -files -blocks -locations > fsck_after_decommission.log这个日志留着,万一后续业务反馈数据异常,可以拿来对比排查。
4. 常见问题排查与避坑经验实录
4.1 退役卡住不动怎么办
这是最常见的问题。表现是节点状态一直是Decommission In Progress,块数长时间不下降。排查思路按顺序来:
先看是不是有块无法复制。某些块的副本可能因为机架策略、磁盘故障等原因找不到合适的放置目标。用hdfs fsck / -blocks找到长期 under-replicated 的块,看它们的副本分布。如果是机架策略导致(比如要求跨机架但剩余机架不够),需要临时调整副本放置策略或者补充节点。
再看 NameNode 的复制队列是否被其他任务占满。如果集群同时有大量失效副本在恢复,Decommission 的复制会被挤在后面。这时候要么等,要么临时调大复制并发。
还有一种情况是待退役节点的磁盘有问题,导致块读取失败,复制任务反复重试。去该节点的 DataNode 日志里搜IOException或者BlockNotFoundException,基本能定位。
4.2 迁移期间业务变慢的应对
业务侧反馈变慢,通常是网络带宽被迁移流量挤占。应急手段是动态调低带宽上限:
# 在 DataNode 上动态调整(部分版本支持,需确认) hdfs dfsadmin -setBalancerBandwidth 20971520 # 20MB/s注意这个命令主要作用于 balance,对 Decommission 的效果因版本而异。更稳妥的做法是直接改配置并滚动重启 DataNode,虽然麻烦但确定有效。另外可以错峰操作,把缩容安排在凌晨业务低谷。
如果业务对延迟极其敏感,还有一个思路:先做一次手动 balance,把待退役节点的数据主动均衡到其他节点,等它数据量降下来再走 Decommission,这样退役阶段的迁移量小,对业务影响也小。
4.3 缩容后副本分布不均的修复
缩容完成后,剩余节点的数据分布往往不均匀,尤其是被退役节点原来的数据集中复制到了少数几个节点上。这时候需要跑一次 Balancer:
# 启动 balancer,限制带宽 50MB/s hdfs balancer -threshold 5 -bandwidth 50-threshold 5表示节点间使用率差异超过 5% 才触发迁移。这个值不要设太小,否则 balancer 会反复微调,浪费资源。跑 balancer 期间同样要监控 NameNode 负载,必要时用hdfs balancer -stop中断。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方向 |
|---|---|---|---|
| 节点状态不变 | 主机名不匹配 / exclude 路径错 | cat dfs.exclude、对比 hostname | 修正主机名或配置路径 |
| 退役长时间不完成 | 块无法复制 / 复制队列拥堵 | hdfs fsck / -blocks | 调整机架策略或复制并发 |
| 业务延迟上升 | 迁移带宽挤占 | sar -n DEV、NameNode RPC 队列 | 调低带宽上限、错峰 |
| 缩容后写入报错 | 剩余容量不足 / 高水位 | dfsadmin -report看 Used% | 清理冷数据或暂缓缩容 |
| 副本分布不均 | 迁移集中落点 | hdfs balancer -threshold | 跑 balancer 均衡 |
| 节点无法重新加入 | 仍在 exclude 文件 | cat dfs.exclude | 移除后 refreshNodes |
4.5 几条踩坑换来的经验
第一条,永远分批退役。我见过一次性把 5 台节点加入 exclude 的操作,结果剩余容量瞬间吃紧,迁移还没完成写入就开始报错。一次一到两台,稳。
第二条,退役前先确认没有单副本的块。有些历史遗留数据可能因为早期配置问题只有单副本,这种块在退役时如果所在节点正好是唯一副本,迁移会失败甚至丢数据。用hdfs fsck / -blocks提前扫一遍,发现单副本块先补副本再退役。
第三条,exclude 文件要纳入版本管理。这个文件是集群状态的一部分,谁改了、什么时候改的,都要有记录。我习惯在文件头部加一行注释记录变更时间和操作人(用代号),出问题时能快速回溯。
第四条,退役期间别做其他大动作。不要同时跑 balancer、不要同时做 NameNode 重启、不要同时升级版本。多个操作叠加,出了问题根本分不清是谁的锅。
第五条,留好回滚路径。退役开始后如果发现容量不够或者业务扛不住,把节点从 exclude 文件移除再refreshNodes,节点会停止退役回到 In Service。但要注意,已经迁移走的块不会自动迁回来,所以回滚后集群的数据分布是"退役了一半"的状态,需要再跑 balancer 收拾。这个回滚不是无损的,所以前期评估一定要做足。
我个人在实际操作中的体会是,HDFS 缩容最考验的不是命令敲得对不对,而是前期评估做得够不够细。容量算清楚、带宽估准确、业务沟通到位,后面就是等和看;反过来,前期图省事,后面就是救火和背锅。每次缩容我都当成一次小型项目来管,列清单、定时间窗、留监控、备回滚,这套流程走下来,至今没出过需要半夜爬起来处理的事故。