HDFS 集群跑久了,几乎每个运维团队都会遇到同一个问题:当初为了扛住业务高峰堆上去的机器,现在成了机房里最安静的一排——CPU 常年个位数,磁盘写满一次就再没动过,电费和维保账单却一分不少。节点缩容这件事,说起来就是"把机器下线",真动手的时候才发现,HDFS 不是把机器一关就完事的系统:副本怎么迁、数据怎么均衡、NameNode 的元数据怎么更新、下线过程中业务读写会不会抖,每一步都有坑。这篇就把我在几个生产集群上做 DataNode 缩容的完整思路拆开讲,从判断该缩哪些节点,到 decommission 的底层流程,再到缩容后的收尾验证,尽量把能踩的坑都提前标出来。
1. 先想清楚:缩容不是删机器,是一次有状态的数据搬迁
很多人对缩容的第一反应是"挑几台空闲的 DataNode,关机、从配置里删掉、重启集群"。这个做法在小数据量、低副本、无业务的测试环境里可能侥幸跑通,但在生产环境里基本等于主动制造数据丢失事故。原因很直接:HDFS 的每个数据块默认有 3 个副本,分散在不同 DataNode 上,你直接关掉一台机器,它上面承载的那部分副本就瞬间不可用了。如果恰好某个块的另外两个副本所在的节点也有问题(磁盘故障、网络分区、正在重启),这个块就进入副本不足甚至块丢失状态,读请求直接报错。
所以缩容的本质,是一次受控的、可回滚的数据副本迁移过程。HDFS 为此专门设计了 decommission(退役)机制,让 NameNode 主动把待下线节点上的数据块复制到其他节点,等副本数满足要求后,再把这个节点标记为已退役,此时关机才是安全的。理解这一点,后面所有的操作步骤就都有了逻辑主线:我们做的每一件事,都是在保证"任意时刻,每个数据块的可用户副本数不低于副本因子"。
1.1 缩容前必须先回答的三个问题
动手之前,我习惯先把三个问题问清楚,这三个答案直接决定了缩容方案怎么设计。
第一个问题:缩几台、缩哪些?不是所有"看起来闲"的节点都适合下线。要综合看磁盘使用率、节点上的块数量、机架分布。如果某个机架上只有两台 DataNode,你缩掉一台,这个机架就只剩单副本,一旦机架级故障(比如整排机柜断电),数据就有风险。所以缩容选节点时,机架感知的均衡比单机负载更重要。
第二个问题:集群当前的健康水位如何?缩容过程中会有大量数据搬迁,会占用网络带宽和磁盘 IO。如果集群本身磁盘使用率已经到 85% 以上,缩容搬迁很可能把剩余节点写爆。我的经验阈值是:缩容前集群整体磁盘使用率控制在 70% 以下,给搬迁留出足够余量。同时要确认没有块处于 under-replicated(副本不足)状态,否则先修复再缩容。
第三个问题:业务能接受多大的性能抖动?decommission 会触发大量块复制,默认不限速的话能把万兆网卡打满,影响正常读写。所以要么在业务低峰期做,要么配置带宽限速。这个问题没有标准答案,但必须提前和业务方对齐,别等出问题了再解释。
1.2 一个容易被忽略的前置检查:副本因子与机架配置
在真正执行 decommission 之前,有两项配置必须核对,否则缩容过程会卡住或者结果不符合预期。
第一项是副本因子。用hdfs dfsadmin -report或者查看hdfs-site.xml里的dfs.replication,确认当前副本数。如果集群是 3 副本,缩容后剩余节点数必须能支撑每个块放下 3 个副本且尽量跨机架。假设你有 3 个机架、每个机架 4 台 DataNode,缩掉某个机架的 2 台后还剩 2 台,勉强能维持机架内副本,但如果继续缩就会破坏机架级冗余。
第二项是机架感知脚本。检查net.topology.script.file.name是否配置正确,用hdfs dfsadmin -printTopology看输出是否符合实际的机架分布。我见过有集群这个脚本压根没配,所有节点都被当成同一个默认机架,这种情况下 decommission 虽然能跑完,但副本分布完全没有机架冗余可言,缩容等于把风险悄悄放大了。
提示:缩容前把
hdfs dfsadmin -report和hdfs fsck / -files -blocks -locations的输出各存一份,作为缩容前的基线快照。后面验证时对比着看,比凭记忆靠谱得多。
2. decommission 到底在后台做了什么
搞清楚 decommission 的内部流程,你才能在它"卡住"的时候知道卡在哪。这块机制其实不复杂,但很多人只记住了操作命令,没理解状态机,出问题时只能干等。
2.1 从"服役"到"退役"的状态流转
一个 DataNode 在 NameNode 眼里有几个关键状态:In Service(正常服役)、Decommissioning(退役中)、Decommissioned(已退役)、Entering Maintenance(进入维护)等。当你把一个节点加入 exclude 列表(dfs.hosts.exclude)并让 NameNode 重新加载配置后,这个节点就从 In Service 进入 Decommissioning。
进入 Decommissioning 后,NameNode 会做两件事:第一,停止向这个节点分配新的写请求,后续的新块不会再落到它上面;第二,扫描这个节点上所有的块,对每个块检查其他节点上的副本数是否满足副本因子,如果不满足,就触发复制任务,把缺失的副本补到其他健康节点上。这个过程是持续进行的,直到这个节点上所有块的"外部副本数"都达标,节点才会被标记为 Decommissioned。
这里有个关键细节:判断标准是"其他节点的副本数是否足够",而不是"这个节点上的数据是否被搬空"。也就是说,如果一个块在这个节点上有副本,但在其他节点上已经有 3 个副本了,那这个副本根本不需要搬,NameNode 直接认为它可以丢弃。所以 decommission 完成后,节点上往往还残留着一些数据,这是正常的,不代表没搬干净。
2.2 为什么有的节点几小时就退役完,有的要跑好几天
decommission 的耗时取决于两个因素:待搬迁的数据量和复制带宽。数据量好理解,节点上块越多、块越大,需要补的副本就越多。带宽这块,HDFS 默认对复制流量是不限速的,但实际吞吐受限于源节点磁盘读、目标节点磁盘写、网络带宽三者中的短板。
我遇到过最典型的情况是:一个节点 decommission 卡了两天没动静,查下来是目标节点所在机架的网络交换机端口跑满了,复制任务排队。还有一种情况是小文件过多,块数量巨大但单块很小,NameNode 的复制调度成为瓶颈,表现为 decommission 进度条走得很慢。这种时候调大dfs.namenode.replication.work.multiplier.per.iteration(控制每轮心跳触发的复制任务数)能加快调度,但要配合监控,别把 NameNode 压垮。
可以用hdfs dfsadmin -report观察 Decommissioning 节点的 "Blocks" 和 "Under replicated blocks" 变化,也可以用hdfs fsck / -list-corruptfileblocks之外的方式,重点看 NameNode 的DecommissioningNodes指标。生产上我一般会盯 NameNode UI 的 "Datanodes" 页面,退役中的节点会显示进度百分比,比命令行直观。
2.3 带宽限速:别让搬迁把业务拖垮
如果集群和业务共用网络,decommission 期间一定要限速。HDFS 提供了dfs.datanode.balance.bandwidthPerSec这个参数,但它主要作用于 balancer 工具,decommission 的复制流量走的是 DataNode 之间的正常传输通道,受dfs.datanode.max.transfer.threads和网络层限速影响。
更稳妥的做法是在交换机或主机层面做流量整形,或者干脆把 decommission 安排在业务低峰窗口。我个人的习惯是:如果集群规模在几十台以内、业务对延迟敏感,就选凌晨窗口做,不折腾限速;如果集群上百台、缩容周期长,就在 DataNode 所在主机上用 tc 做出口限速,把复制流量控制在总带宽的 30% 左右。这个比例不是拍脑袋,是留足业务峰值余量后的经验值。
3. 一步步执行:从配置 exclude 到节点真正下线
理论讲完,进入实操。下面这套流程我在多个集群上验证过,按顺序走基本不会出问题。需要说明的是,不同发行版的配置文件名和路径略有差异,命令以通用 HDFS 为准。
3.1 第一步:准备 exclude 文件并核对内容
exclude 文件是一个纯文本文件,每行一个主机名(或 IP),列出要退役的节点。这个文件的位置由dfs.hosts.exclude指定,通常在 NameNode 的配置目录下。
# 假设 exclude 文件路径为 /etc/hadoop/conf/dfs.exclude # 先备份原文件 cp /etc/hadoop/conf/dfs.exclude /etc/hadoop/conf/dfs.exclude.bak.$(date +%Y%m%d) # 追加待退役节点,一行一个 cat >> /etc/hadoop/conf/dfs.exclude <<'EOF' datanode-05.example.internal datanode-06.example.internal EOF # 核对内容,确保没有误加仍在服役的节点 cat /etc/hadoop/conf/dfs.exclude这里有个血泪教训:exclude 文件里千万不要留空行或注释行。有些版本的 Hadoop 对空行处理不严格,可能把空行当成一个无效主机名,导致 NameNode 加载配置时报错或者行为异常。另外,主机名必须和hdfs dfsadmin -report里显示的完全一致,大小写、域名后缀都要对上,否则 NameNode 认不出这个节点,decommission 根本不会触发。
3.2 第二步:刷新 NameNode 配置,观察状态变化
配置改完后,不需要重启 NameNode,用 refresh 命令让它重新加载即可:
hdfs dfsadmin -refreshNodes执行后立刻用hdfs dfsadmin -report观察,待退役节点应该从 "Live datanodes" 移到 "Decommissioning datanodes" 段落。如果节点还在 Live 里没动,八成是 exclude 文件路径配错、主机名不匹配,或者dfs.hosts.exclude压根没在hdfs-site.xml里配置。这时候别急着往下走,先把配置问题解决。
确认节点进入 Decommissioning 后,就可以开始盯进度了。我一般会开一个循环脚本,每隔几分钟打印一次状态:
while true; do echo "===== $(date) =====" hdfs dfsadmin -report | grep -A 3 "Decommissioning datanodes" sleep 300 done3.3 第三步:判断退役完成的信号,而不是干等
怎么知道一个节点退役完了?最直接的信号是hdfs dfsadmin -report里它从 "Decommissioning datanodes" 消失,出现在 "Decommissioned datanodes" 段落。但更严谨的做法是结合 fsck 验证:对退役节点上曾经承载的块做抽查,确认副本数达标。
# 查看集群整体健康,重点关注 under-replicated blocks 是否为 0 hdfs fsck / -blocks | tail -20如果输出里 "Under-replicated blocks" 是 0,"Missing blocks" 是 0,说明集群副本健康。这时候退役节点上的数据已经不再承担可用性职责,可以安全关机。
需要提醒的是,退役完成不等于数据搬空。前面说过,NameNode 只保证其他节点副本足够,节点上残留的副本会被逐步清理,但这个过程可能滞后。所以关机前不用纠结节点上还剩多少数据,只要集群 fsck 健康、节点状态是 Decommissioned,就可以动手。
3.4 第四步:关机、清理配置、更新集群清单
节点标记为 Decommissioned 后,操作顺序建议是:先停 DataNode 进程,再关机,最后从 exclude 文件里移除(因为节点已经不在集群里了,留在 exclude 里没意义,反而会让配置越来越乱)。
# 在待下线节点上停止 DataNode hdfs --daemon stop datanode # 确认进程已退出 jps | grep -i datanode关机之后,记得同步更新几样东西:集群的节点清单文档、监控系统的采集目标、CMDB 或资产管理系统里的记录。这些收尾工作看起来琐碎,但漏掉监控目标会导致告警一直响,漏掉资产记录会让下次盘点对不上账。我见过有团队缩容后忘了删监控,结果告警风暴持续了一周才有人发现。
4. 缩容过程中最容易翻车的几个场景
流程本身不复杂,真正让人头疼的是各种意外。下面这几个场景都是我或同行实际遇到过的,提前知道能省不少时间。
4.1 退役卡在 99% 不动:多半是某个块的副本补不上
decommission 进度长时间停在接近完成的位置,是最常见的"假死"现象。根因通常是某个块的副本无法复制到目标节点。可能的原因包括:目标节点磁盘满、目标节点网络不通、副本放置策略找不到合适的机架、某个块本身已经损坏。
排查思路是先用 fsck 找出副本不足的块:
hdfs fsck / -blocks | grep -i "under-replicated"如果输出里有具体的块 ID,可以用hdfs fsck /path/to/file -files -blocks -locations定位到文件和所在节点,然后检查这些节点的磁盘和网络状态。有时候问题出在副本放置策略上:比如集群只剩两个机架,而策略要求跨三个机架放副本,就会一直找不到合适位置。这种时候要么调整副本因子,要么补充机架,硬等是等不出来的。
4.2 缩容后集群磁盘使用率飙升:搬迁的隐性成本
decommission 是把副本从待退役节点复制到其他节点,这意味着集群总数据量在缩容期间是增加的(退役节点上的数据还没删,新副本已经写进去了)。如果集群原本磁盘使用率就高,缩容期间很容易触发磁盘满告警,甚至导致写入失败。
这就是为什么前面强调缩容前使用率要控制在 70% 以下。如果实在没法降下来,可以考虑分批缩容:一次只退役一两台,等副本清理、使用率回落后再退役下一批。分批的代价是周期变长,但安全性高得多。我一般建议单批次缩容不超过集群总节点数的 10%,这个比例在多数场景下能把使用率波动控制在可接受范围。
4.3 误把仍在服役的节点加入 exclude:一次惊险的回滚
这个坑我必须单独说,因为它的后果很严重。有一次操作时复制粘贴出错,把一台核心 DataNode 的主机名加进了 exclude 文件,refresh 之后这台节点立刻进入 Decommissioning,开始往外搬数据。好在发现得早(监控告警提示某节点进入退役状态),及时把它从 exclude 文件移除并再次 refresh,节点才回到 In Service。
这个经历说明两件事:第一,exclude 文件修改后一定要 diff 确认,别嫌麻烦;第二,refreshNodes 之后要立刻看 report,确认只有预期内的节点状态变化。如果误操作发现得晚,节点已经搬走大量数据,回滚虽然也能让它重新服役,但期间造成的副本迁移和网络开销是实打实的损失。
注意:从 exclude 文件移除节点后,同样需要执行
hdfs dfsadmin -refreshNodes才能生效。移除后节点会从 Decommissioning 回到 In Service,但已经迁走的副本不会自动迁回,需要靠后续的 balancer 重新均衡。
4.4 缩容后没有重新均衡:数据分布歪了
decommission 只保证副本数达标,不保证数据分布均匀。退役节点上的数据被复制到其他节点时,NameNode 是按副本放置策略选目标节点的,可能造成某些节点块数偏多、某些偏少。缩容完成后,集群虽然健康,但热点节点可能成为新的瓶颈。
所以缩容收尾时,建议跑一次 balancer:
hdfs balancer -threshold 5 -policy datanode-threshold 5表示磁盘使用率差异在 5% 以内就停止,这个值太小会导致 balancer 反复微调、耗时很长,太大又达不到均衡效果,5% 到 10% 是常见区间。balancer 同样会占用带宽,建议在业务低峰执行,或者配合限速参数。
5. 缩容后的验证清单与长期观察
节点下线不代表事情结束,缩容对集群的影响需要观察一段时间才能确认没有遗留问题。我习惯按下面这个清单逐项过一遍。
5.1 短期验证:副本健康与读写正常
缩容完成后 24 小时内,重点看三件事。第一,hdfs fsck /的输出里 Missing blocks 和 Under-replicated blocks 必须都是 0,Corrupt blocks 也应该是 0。第二,抽查几个核心业务表的读写,确认延迟没有明显上升。第三,看 NameNode 的 RPC 队列和 DataNode 的心跳,确认没有节点因为负载变化出现异常。
可以用一个简单的脚本把关键指标拉出来对比缩容前的基线:
# 集群容量与使用率 hdfs dfsadmin -report | grep -E "Configured Capacity|DFS Used%|DFS Remaining" # 块健康 hdfs fsck / -blocks 2>/dev/null | grep -E "Total blocks|Missing blocks|Under-replicated"把这两组数据和缩容前的快照对比,容量减少是预期的,但使用率的变化幅度要心里有数。如果使用率涨幅远超"退役节点数据量 / 剩余节点总容量"的估算值,说明可能有副本异常膨胀,需要进一步排查。
5.2 中期观察:热点转移与性能基线
缩容后一到两周,重点观察热点是否转移。原来退役节点承担的那部分读请求,现在分散到了其他节点,如果某些节点本身负载就高,可能成为新的热点。看 DataNode 的磁盘 IO、网络吞吐和请求队列,对比缩容前的分布,确认没有节点持续跑满。
另一个容易被忽略的点是机架级带宽。缩容如果集中在某个机架,剩余节点之间的跨机架流量会增加,交换机端口利用率可能上升。这个指标在主机监控里看不到,需要网络侧的监控配合。我一般会在缩容后一周内找网络同事确认一下相关链路的利用率,图个安心。
5.3 长期视角:把缩容纳入容量规划
单次缩容做完,更值得思考的是怎么让下次缩容更从容。我的做法是把节点生命周期纳入容量规划:定期(比如每季度)评估一次集群资源利用率,识别长期低负载的节点,提前规划退役批次,而不是等到机器快过保了才临时抱佛脚。
同时,缩容经验要沉淀成文档和脚本。exclude 文件的修改、refreshNodes、进度监控、fsck 验证、balancer 收尾,这套流程完全可以脚本化,把人工操作降到最低。脚本化不仅提效,更重要的是减少误操作——前面说的误加节点那种事,有了带确认步骤的脚本就能避免大半。
6. 几个参数与策略层面的取舍心得
最后聊几个偏配置层面的经验,这些内容在官方文档里往往一笔带过,但实际用起来差别很大。
6.1 副本因子不是越高越安全,要看集群规模
有些团队为了"更安全",把副本因子调到 4 甚至 5,结果缩容时发现剩余节点根本放不下这么多副本,decommission 直接卡死。副本因子的选择要和集群规模匹配:小集群(10 台以内)3 副本已经足够,大集群可以考虑 3 副本加机架感知,没必要盲目加副本。缩容前如果发现副本因子偏高,可以先评估是否降到 3,再执行退役,能显著降低搬迁压力。
6.2 维护模式(Maintenance)和退役(Decommission)别混用
HDFS 还提供了 maintenance 模式,用于临时下线节点做硬件维护。它和 decommission 的区别在于:maintenance 模式下,NameNode 允许副本数暂时低于副本因子(有一个dfs.namenode.maintenance.replication.min的下限),适合短时间维护;而 decommission 是永久下线,要求副本数严格达标。临时维护用 maintenance,永久缩容用 decommission,用错了要么搬迁量白白增加,要么维护期间数据风险上升。
6.3 缩容窗口的选择比技术细节更影响体验
技术方案再完善,如果选在业务高峰做缩容,照样会被投诉。我的经验是:缩容窗口优先选业务最低谷的连续时间段,确保有足够时间完成搬迁和验证。如果集群大、单批缩容耗时长,就拆成多个小批次,每批都在低峰窗口内完成。宁可多花几个晚上,也别在一个窗口里硬塞太多节点。
这套流程走下来,HDFS 节点缩容其实没有想象中那么可怕,核心就是把"数据搬迁"这件事交给 decommission 机制,人要做的是选对节点、控好节奏、盯紧状态、验证到位。真正容易出问题的从来不是命令本身,而是对机制理解不到位导致的误操作和误判。把前面那些坑提前在脑子里过一遍,动手的时候心里就有底了。