3、存储层运维(上):HDFS/Hadoop核心配置、NameNode高可用、DataNode扩缩容、数据均衡策略

存储层是Lakehouse的底座。说白了,数据湖能不能跑得稳,全看HDFS扛不扛得住。我见过太多团队在计算层花里胡哨搞优化,结果底层HDFS三天两头出问题——NameNode挂了、磁盘写满了、数据倾斜严重。嗯,今天我们就来聊聊这些硬骨头怎么啃。

核心观点:HDFS运维不是“配完就忘”的事。NameNode的元数据健康、DataNode的磁盘水位、集群的数据分布,这三样东西你得天天盯着。任何一个环节出问题,整个Lakehouse都得停摆。

3.1 HDFS核心配置:那些容易踩坑的参数

Hadoop的配置项成百上千,但真正影响生产稳定性的,其实就那么十几个。我个人习惯把配置分成三类:元数据保护类、IO行为类、资源管控类。

3.1.1 元数据保护类配置

NameNode的元数据是HDFS的命根子。我曾经遇到过一次机房断电,NameNode的edits log损坏,导致整个集群恢复花了整整两天。从那以后,我对这几个参数格外敏感:

配置项 推荐值 说明
dfs.namenode.name.dir 多个目录(不同挂载点) 元数据镜像必须写多份,且放在不同磁盘/路径下
dfs.namenode.edits.dir 与name.dir分离的目录 edits log单独存放,避免与fsimage争IO
dfs.namenode.checkpoint.period 3600(秒) 每小时做一次checkpoint,减少重启恢复时间
dfs.namenode.num.checkpoints.retained 3 保留最近3个checkpoint,防止误操作后无法回滚

注意:dfs.namenode.name.dir和dfs.namenode.edits.dir千万不要指向同一个磁盘。我见过有人图省事配了同一个目录,结果磁盘坏了元数据全丢——这种教训太贵了。

3.1.2 IO行为类配置

HDFS的读写性能,很大程度上取决于你怎么配IO参数。这里有几个关键点:

  • 副本数(dfs.replication):生产环境默认3副本就够了。但如果你用的是纠删码(Erasure Coding),可以降到1.5倍左右的存储开销。我建议冷数据走EC,热数据保留3副本。
  • 块大小(dfs.blocksize):默认128MB,但如果你跑的是大文件批处理作业,256MB甚至512MB效果更好。块越大,NameNode内存压力越小。
  • 短路读(dfs.client.read.shortcircuit):一定要开启。DataNode和客户端在同一台机器时,短路读能绕过网络栈,直接读本地文件。延迟能降一个数量级。
<!-- 短路读配置示例 -->
<property>
  <name>dfs.client.read.shortcircuit</name>
  <value>true</value>
</property>
<property>
  <name>dfs.domain.socket.path</name>
  <value>/var/lib/hadoop-hdfs/dn_socket</value>
</property>

3.1.3 资源管控类配置

DataNode的内存和IO不能无限吃。我习惯给DataNode的堆内存设一个上限,同时限制数据传输线程数:

  • dfs.datanode.handler.count:默认10,生产环境建议30-50。处理读写请求的工作线程数。
  • dfs.datanode.max.transfer.threads:默认4096,建议根据物理核数调整。这个值设太大反而会导致上下文切换开销过高。
  • dfs.datanode.balance.bandwidthPerSec:默认10MB/s,太保守了。我一般设成100MB/s或更高,前提是网络带宽够用。

3.2 NameNode高可用:别让单点毁了你的集群

NameNode挂了,整个HDFS就不可用了。早期Hadoop 1.x时代,NameNode单点故障是运维人员的噩梦。好在现在有HA方案,但配置不当照样出问题。

3.2.1 HA架构的核心组件

一个标准的NameNode HA集群包含:

  • Active NameNode:处理所有客户端请求,写入edits log。
  • Standby NameNode:实时同步edits log,保持内存中元数据与Active一致。随时准备接管。
  • JournalNode(QJM):edits log的共享存储。通常部署3或5个节点,用Quorum机制保证一致性。
  • ZKFailoverController(ZKFC):监控NameNode健康状态,通过ZooKeeper实现自动故障转移。

经验之谈:JournalNode不要和DataNode混部。JournalNode的IO压力很大,混部会影响DataNode的正常读写。我一般用3台独立的机器跑JournalNode,磁盘用SSD。

3.2.2 自动故障转移配置要点

自动故障转移依赖ZooKeeper。配置时要注意几个细节:

  1. ZooKeeper会话超时:ha.zookeeper.session-timeout.ms,默认5秒。如果网络抖动频繁,可以适当调大到10-15秒,避免误切。
  2. fencing机制:确保同一时刻只有一个Active NameNode。常用的fencing方法是SSH + 命令杀进程,或者用shell脚本调用API。
  3. 过渡状态处理:故障转移期间,客户端会收到StandbyException。客户端需要配置重试机制,我一般设3次重试,间隔2秒。
<!-- NameNode HA核心配置 -->
<property>
  <name>dfs.nameservices</name>
  <value>mycluster</value>
</property>
<property>
  <name>dfs.ha.namenodes.mycluster</name>
  <value>nn1,nn2</value>
</property>
<property>
  <name>dfs.namenode.rpc-address.mycluster.nn1</name>
  <value>host1:8020</value>
</property>
<property>
  <name>dfs.namenode.rpc-address.mycluster.nn2</name>
  <value>host2:8020</value>
</property>

避坑指南:我曾经遇到过ZKFC误判NameNode“假死”导致频繁切换的情况。原因是NameNode在做Full GC时暂停了十几秒,ZKFC以为它挂了。解决方案是调大dfs.namenode.handler.count减少GC压力,同时把ZKFC的健康检查间隔从1秒改成3秒。

3.3 DataNode扩缩容:平滑操作的关键细节

DataNode的扩缩容,听起来简单——加机器、删机器嘛。但实际操作中,缩容比扩容容易出问题。我见过有人直接kill DataNode进程,结果导致副本数不足,整个集群进入安全模式。

3.3.1 扩容DataNode

扩容相对简单,核心步骤就三步:

  1. 在新机器上安装Hadoop,配置指向同一个NameNode。
  2. 启动DataNode进程:hdfs --daemon start datanode。
  3. NameNode会自动识别新节点,开始向它分发数据。

但要注意:新节点不会立刻存满数据。NameNode的块分配策略会优先选择磁盘空间充足的节点。如果你想让新节点快速参与数据写入,可以手动触发均衡:

hdfs balancer -threshold 10

3.3.2 缩容DataNode

缩容要谨慎。正确的做法是:

  1. 先标记退役:在dfs.hosts.exclude文件中添加要退役的DataNode主机名。
  2. 刷新配置:hdfs dfsadmin -refreshNodes。
  3. 等待数据迁移完成:NameNode会把退役节点上的块复制到其他节点。这个过程可能持续几小时甚至几天,取决于数据量。
  4. 确认退役完成:用hdfs dfsadmin -report查看节点状态,确认Decommissioning变为Decommissioned。
  5. 停止DataNode:hdfs --daemon stop datanode。

关键指标:退役期间要关注Under-Replicated Blocks的数量。如果这个值持续增长,说明集群带宽不够,需要调大dfs.namenode.replication.max-streams(默认2,建议调大到10-20)。

3.4 数据均衡策略:别让热点拖垮集群

数据倾斜是HDFS的常见问题。有的DataNode磁盘用了90%,有的才30%。原因可能是:

  • 写入热点:某些目录写入量特别大,块都分配到了同一批节点。
  • 退役/扩容后未均衡:新节点数据少,老节点数据多。
  • 磁盘容量不一致:集群里混用了不同大小的磁盘。

3.4.1 使用Balancer进行均衡

HDFS自带的Balancer工具是最常用的均衡手段。它的工作原理是:

  • 计算每个DataNode的磁盘使用率。
  • 找出使用率高于平均值的节点(源节点)和低于平均值的节点(目标节点)。
  • 从源节点移动块到目标节点,直到所有节点的使用率差距在阈值以内。
# 启动Balancer,阈值设为10%
hdfs balancer -threshold 10

# 限制带宽,避免影响线上业务
hdfs balancer -threshold 10 -bandwidth 104857600

我的习惯:Balancer不要在业务高峰期跑。我一般凌晨2点到6点执行,带宽限制在100MB/s。另外,Balancer是单线程的,大集群跑得很慢。HDFS 3.x引入了hdfs diskbalancer,可以针对单个DataNode内部的不同磁盘做均衡,效率高很多。

3.4.2 手动干预:处理极端倾斜

Balancer不是万能的。有些极端情况需要手动处理:

  • 单个目录文件过多:NameNode的目录结构是树形的,一个目录下文件太多会导致查询性能下降。我建议每个目录下的文件数不要超过100万。
  • 小文件问题:大量小文件会撑爆NameNode内存。解决方案是合并小文件,或者用HBase、Iceberg等表格式来管理。
  • 磁盘故障:某块磁盘坏了,数据自动迁移到其他磁盘,导致其他磁盘负载飙升。这时候需要手动调整副本分布。

3.4.3 均衡策略的自动化

我建议把均衡操作做成定时任务。比如每天凌晨跑一次Balancer,配合监控告警:

# crontab示例:每天凌晨3点执行均衡
0 3 * * * /usr/bin/hdfs balancer -threshold 10 -bandwidth 104857600 >> /var/log/hdfs/balancer.log 2>&1

同时,监控系统要盯住两个指标:

  • 节点磁盘使用率标准差:超过15%就触发告警。
  • Under-Replicated Blocks数量:持续大于0说明副本有问题。
HDFS存储层运维核心逻辑 HDFS核心配置 元数据保护类 IO行为类 资源管控类 NameNode高可用 DataNode扩缩容 数据均衡策略 Active/Standby JournalNode ZKFC自动切换 Balancer/DiskBalancer 目标:高可用 + 高吞吐 + 数据均匀分布

这张图把HDFS存储层运维的核心逻辑串起来了。从配置基础出发,到HA保障可用性,再到扩缩容和均衡保证性能,最终目标就是让集群稳定、高效地运行。你想想看,如果每个环节都做到位了,Lakehouse的底座就稳了。