文章摘要
飞宇 GPT

引言:从 2.x 到 3.x,这一跳到底值不值

我第一次把生产集群从 Hadoop 2.x 升到 3.x,是在一个接近 2000 节点 的集群上。当时团队最直接的痛点是:冷数据膨胀太快,每年采购的 SATA 盘里有一大半都喂给了三副本。调研了一圈之后,Hadoop 3.x 三个能力进入了我们的视野——纠删码(Erasure Coding)Router-based FederationYARN 的资源模型升级。这三件事几乎覆盖了”省存储、降复杂度、扩调度边界”三个方向,也是我判断 3.x 相对 2.x 最关键的演进。

Hadoop 2.x 把 MapReduce 时代推进到 YARN,奠定了资源管理的底座;3.x 则更多是在”存储经济性”和”联邦可运维性”上做文章,并让 YARN 能更好地服务于非传统计算负载(GPU、长服务)。这一篇我从源码阅读和生产踩坑两条线展开,尽量把每个特性的边界讲清楚,而不是只罗列 release notes。

一、纠删码 Erasure Coding:用 CPU 换磁盘的存储经济学

1.1 为什么要上 EC

HDFS 传统的三副本机制,存储效率只有约 33%——写 1TB 数据要占 3TB 物理空间。对于热数据,副本既保证可靠性又提供读吞吐,是值得的;但对于冷数据 / 温数据(日志归档、历史快照、模型训练的原始数据集),三副本的边际收益极低,纯粹在烧钱。

Hadoop 3.0 引入的纠删码(Erasure Coding,简称 EC)解决了这个问题。它用 RS(Reed-Solomon)码 把数据切成分片,生成校验块,丢失若干块仍可重建。社区默认提供两种策略:

策略 数据块 校验块 容忍丢失 有效存储率(含 1 副本内 EC)
RS-6-3 6 3 3 约 50%
RS-3-2 3 2 2 约 60%
RS-LEGACY-6-3 6 3 3 约 50%(兼容旧版本读取)

说明:RS-6-3 即 6 个数据单元 + 3 个校验单元共 9 单元,存储效率 6/9 ≈ 66.7% ;相比三副本的 33.3%,节省约 50% 的物理空间。RS-3-3、XOR 等也内置但更少用。

我们实际把日志归档目录切成 RS-6-3 之后,单集群存储水位从 78% 降到 52% ,相当于凭空”白嫖”了几百台 DataNode 的容量。这笔账非常划算。

1.2 条带化布局 vs 连续布局

EC 在 HDFS 上有两种 cell 布局:

  • 条带化布局(Striping):把一个文件按 cell size(默认 1MB)切成单元,数据单元与校验单元交错分布在多台 DataNode 上。读一个文件不需要把整个块都读进来,对中小文件友好。Hadoop 3.x 默认采用这种布局。
  • 连续布局(Contiguous):整个数据块连续写在一台 DataNode 上,校验块单独分布。实现简单、顺序写吞吐高,但单个 block 失效就要拉整个块重建,对小文件不友好。

社区最终选择 striping 作为默认,原因很现实:EC 目标场景就是冷数据里的海量中小文件,striping 的随机读放大更小。需要重点理解的一个细节是——EC 写路径上,客户端要先生成校验单元再下发,这意味着每次写都要做一次 RS 编码计算;读路径上,只要有一个 cell 丢失,就要做一次解码。这是 EC 用 CPU 换存储的核心代价。

1.3 代价与适用边界

EC 不是银弹,我在生产上给它划了明确的红线:

  1. 写入吞吐下降:RS 编码本身是 CPU 密集的,默认的 RS-6-3 编码相比三副本,单客户端写吞吐大约下降 20%~30% 。我们专门做过对比测试,小文件场景下这个比例还会更高。
  2. 随机读放大:striping 布局下一个逻辑读可能要 fan-out 到多个 DataNode,随机读尾延迟明显。EC 目录不要放 OLTP 风格的工作负载。
  3. 小文件问题反而更严重:EC 的最小开销是”数据单元数 + 校验单元数”个 block,RS-6-3 至少要占 9 个 block。如果你有一堆 1KB 的小文件,开销比三副本还离谱。
  4. 重建成本:丢块后的重建是 CPU + 网络双吃,集群繁忙时建议限速(dfs.datanode.ec.reconstruction.stripedread.bandwidth,单位字节/秒)。
  5. 与副本互斥:EC policy 一旦作用到一个目录,该目录下新文件就走 EC;老文件不会被自动转换,需要显式 mover 数据迁移。

适用边界一句话:EC 适合”一次写、多次顺序读、数据量大、访问频率低”的冷温数据;热数据、小文件、强随机读场景,老老实实三副本。

1.4 策略配置与日常操作

社区内置了五条 EC 策略,开箱即用。开 EC 的标准动作是:先在目录上 setPolicy,然后(可选)把存量数据用 HDFS Mover 迁移过来。下面这段是在生产上为日志归档目录开启 RS-6-3 的完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 查看当前集群已注册的 EC 策略
hdfs ec -listPolicies

# 2.(可选)增加一条自定义策略,比如放宽到 RS-10-4 以容忍更多节点故障
# schema 必须是已加载的,numDataUnits + numParityUnits 不能超过集群可用 DataNode 数
hdfs ec -addPolicy \
-policy RS-10-4 \
-numDataUnits 10 \
-numParityUnits 4

# 3. 给目录绑定策略。注意:策略只对"之后写入"的新文件生效
hdfs ec -setPolicy -path /warehouse/log_archive -policy RS-6-3

# 4. 校验策略是否绑定成功
hdfs ec -getPolicy -path /warehouse/log_archive

# 5. 把目录下已有的三副本文件迁移成 EC 布局(重活,建议限流 + 后台跑)
hdfs mover -p /warehouse/log_archive \
-D dfs.datanode.ec.reconstruction.stripedread.bandwidth=10485760

# 6. 不再需要 EC 时,解除策略(不会回写已 EC 化的文件)
hdfs ec -unsetPolicy -path /warehouse/log_archive

几个我踩过的坑值得提醒:

  • setPolicy 之后写的文件才会是 EC,老文件不动;迁移存量必须显式 mover,而 mover 是 IO 重活,务必挑业务低峰、限速跑。
  • EC 文件的 block group 里只要校验块在,丢数据块能恢复;但同时丢的数据块数 ≥ 校验块数时,整个 block group 直接不可读,所以 RS-6-3 的 9 台机器不要集中在同一个机架。
  • NameNode 内存里 EC 文件的 block group 元数据比普通三副本大,目录里 EC 文件特别多时要关注 NN heap。我们大约每 1 亿个 EC 文件多占 6~8GB NN heap

二、HDFS Router-based Federation:终于不再让客户端背配置地狱

2.1 老 Federation 的痛

HDFS Federation(Hadoop 0.23 引入)允许一个集群挂多个 NameNode,每个 NameNode 管一部分 namespace,用来突破单 NN 的内存上限。但它的代价是——客户端必须自己知道某个路径挂在哪个 NameService 下。在 core-site.xml 里要配 fs.viewfs://clusterX 的一堆 mount link,新增一个 NS 全公司都要推配置,业务方还经常配错。

ViewFS 只是做了一层客户端静态挂载表,本质没解决问题,反而把复杂度推到了客户端侧。每次有新业务接入、新 NS 上线,运维都要走一遍”改配置—发布—重启客户端”的循环。

2.2 Router-based Federation(RBF)的思路

Hadoop 2.9 / 3.0 引入的 Router-based Federation 在 Federation 之上加了一层 Router 角色,把 mount table 从客户端搬到了服务端:

角色 职责
Router 维护一份”全局 mount table”,把路径映射到具体 NameService;同时承担 quota、缓存、监控聚合
State Store 存放 mount table、Router 状态,通常用 ZooKeeper 或本地文件
Namenodes 原有 Federation 下的若干 NameNode,不变

对客户端来说,访问 HDFS 只需要一个虚拟路径,比如 hdfs://cluster/warehouse/log_archive,Router 自己转发到对应的 NS。客户端的 core-site.xml 只需要配 Router 地址,配置复杂度从”NS 数 × 业务方数”降到了”1 × 业务方数”。

2.3 RBF 和 ViewFS 的本质区别

维度 ViewFS Router-based Federation
mount table 位置 客户端配置文件 服务端 Router + State Store
新增 NS 客户端改动 必须更新配置 + 重启 几乎无感(Router 改 mount 即可)
客户端故障域 配置错就访问错 Router 故障可切换
Quota / 监控聚合 Router 层统一聚合

简言之,RBF 把”挂载关系”做成了一个集群级服务,而不是每个客户端各自维护的本地约定。这个抽象提升非常大。

2.4 生产实践要点

  • Router 至少 3 个,挂 HA,客户端通过 RPC 重试切换,避免 Router 单点。
  • State Store 强烈建议用 ZooKeeper,而不是本地文件,否则 Router 之间状态不一致。
  • 配额在 Router 层做:可以跨 NS 聚合 quota,老 Federation 想都别想。
  • 冷启动顺序:先起 ZK → 再起 State Store 初始化 → 再起 Router → 最后起各 NS。

我们升级 RBF 之后,新业务接入 NS 的工作量从”半天运维 + 协调业务方重启”降到了”一条 dfsrouteradmin -addMount 命令” ,运维满意度肉眼可见地上升。

三、YARN 的新能力:从 MapReduce 调度器走向通用资源平台

3.1 Timeline Service v2(ATSv2)

Hadoop 2.x 的 Timeline Service v1 把所有 application / task 信息塞进一个内存结构,扩展性极差,集群规模一上来就崩。ATSv2(3.0 引入)的解决思路是:

  • 写路径上引入 Collector(每个 NM 一个,AM 也可以嵌入式),用流式方式写;
  • 存储后端换成 HBase,按 application / flow / user 多维度分片;
  • 引入 flow / flow run / flow version 概念,把一次作业流水线(如每天跑一次的 ETL)作为一等公民。

收益很明显:支撑 Spark / Flink 长跑作业、能承载几十万并发 application 的指标写入。代价是要多维护一套 HBase 集群,且 ATSv2 在 3.0 早期 bug 不少,建议至少用 3.2 以上再上生产。

3.2 通用资源类型:终于能调度 GPU

Hadoop 2.x 的调度器只认 CPU(vCore)和内存两种资源,对 GPU、FPGA 谦卑地无能为力。3.1 开始 YARN 引入通用资源模型(resource types)

  • 默认除了 memoryvcores,新增了可选的 resource 字段;
  • 通过 yarn-site.xml 配置 yarn.resource-types,注册如 yarn.io/gpuyarn.io/fpga
  • NodeManager 通过 plugin 上报 GPU 数量,调度器按需分配;
  • 容器启动时可以隔离 GPU(通过 cgroups / NVIDIA Docker)。

这意味着深度学习训练任务可以直接跑在 YARN 上,由 YARN 统一调度 GPU。我们把训练平台从独立 K8s 集群迁到 YARN 之后,GPU 利用率从不到 50% 提升到 75% ,因为同一批卡可以被训练 / 推理 / 离线推理任务错峰复用。

3.3 Node Attributes 与 Placement Constraints

  • Node Attributes:给节点打键值对标签(如 rack=r5gpu.model=A100),调度器可以基于 attribute 而不仅是 label 做匹配。
  • Placement Constraints:让应用主动声明”我这个 container 想和哪些 container 同机架 / 同节点 / 反亲和”,调度器尽力满足。

这两个能力加在一起,让 YARN 接近 Mesos / K8s 的调度表达力。比如做一个分布式训练任务,可以要求:parameter server 反亲和(不要堆在一起),worker 尽量同机架(减少跨机架带宽),这种”以前要靠 hack 写代码”的需求,现在 scheduler 原生支持。

3.4 YARN Service:把长服务当一等公民

3.x 引入了 YARN Service 概念,可以用一份 YAML 描述一个长服务(如 Web 服务、Redis 集群),YARN 自动拉起、监控、重启、扩缩容。这把 YARN 从”批作业调度器”拓宽成”通用服务编排平台”。

老实说,YARN Service 在生产用得不算多——大家更愿意用 K8s 做长服务。但它的价值在于:那些已经把所有负载都压在 YARN 上的公司,可以不再额外维护 K8s 来跑长服务,统一在一个调度器下。

四、其他容易被忽略但很要命的变更

这一节我专门讲那些 release notes 一句话带过、但升级时坑到生产的事:

4.1 默认端口变更

Hadoop 3 把一批默认端口从特权端口(< 1024)改到了高位端口,避免用 root 启动。最容易踩的是 NameNode 的 8020 / 9000 仍在,但不少服务的默认端口变了,老配置直接拷过来跑会起不来。升级前一定要 diff 一遍 hdfs-default.xmlyarn-default.xmlmapred-default.xml

4.2 Java 版本要求

Hadoop 3.0 起最低要求 JDK 8;3.3 起开始实验性支持 JDK 11;3.4 进一步完善 11/17 支持。如果还在跑 JDK 7,必须先升 JDK。

4.3 Shell 脚本重写

3.x 把启动脚本(hdfsyarnhadoop)用 hadoop-functions.sh 完整重写过,老的 *.cmd 行为也调整了。很多公司自己写的运维脚本会失效,比如依赖某些环境变量名(HADOOP_*_USER 等)的脚本。升级前专门留 1~2 天调脚本。

4.4 Classpath 变化

3.x 重新整理了 client classpath,老的 hadoop classpath 输出与 2.x 不一致。Spark / Hive / Flink 等上层组件如果用的是老的 Hadoop 2 客户端 JAR,可能 ClassNotFoundException

4.5 HDFS Disk Balancer

2.x 只有 DataNode 内部的 volume balancer,3.x 引入了 Disk Balancer,可以在 DataNode 的不同磁盘之间均衡数据。这个在替换坏盘、新加盘后非常实用,避免单盘被打爆。

五、升级注意事项:用血泪换来的检查清单

我经历过一次”以为平滑、结果凌晨两点回滚”的升级,把教训整理成下面这份清单:

5.1 兼容性

  • API 兼容:HDFS / YARN 的 Java 客户端 API 总体向下兼容,但部分 @Private / @Unstable 接口会变。重度依赖 internal API 的组件(比如某些自研 YARN scheduler)要重点测。
  • Wire 兼容:3.x 与 2.x 的 RPC 协议基本兼容,但 EC 文件在 2.x 客户端不可读(不支持 EC),这点对下游(Hive、Spark)影响巨大。

5.2 上层组件版本对齐

组件 对 Hadoop 3 的支持起步版本
Hive 3.0
Spark 2.3+(建议 3.0+)
HBase 2.0
Flink 1.4+(建议 1.8+)

升级 Hadoop 之前,先把上面这套生态一起升上去。否则会出现”Hadoop 3 起来了,Hive 还跑在 2.x 客户端上读不了 EC 文件”的尴尬局面。

5.3 EC 对作业的影响

  • MapReduce / Spark 读 EC 文件时,因为 striping 布局,split 计算会变:默认一个 split 不再是 1 个 block,而是一个 block group(9 个 cell)。这会让 map 数减少,并行度下降。建议显式调 mapreduce.input.fileinputformat.split.maxsize
  • 写 EC 文件时 CPU 上升,把计算和存储混部的集群要注意。

5.4 滚动升级(Rolling Upgrade)

3.x 支持从 2.x 滚动升级,但必须先升 NameNode,再升 DataNode;升级期间要保留 downgrade 镜像。具体流程:

  1. 备份 fsimage + editlog;
  2. NN 进入 safe mode,部署 3.x 二进制;
  3. 启动 3.x NN,跑 dfsadmin -rollingUpgrade finalize
  4. 滚动重启 DN,逐台升级;
  5. RM / NM 同样滚动重启。

整个过程在 2000 节点集群上,我们花了约 6 小时 ,其中一半时间花在等 DN 重启后 block report 回灌。

5.5 监控埋点

EC 重建、Router 转发延迟、ATSv2 写延迟,这三个是新引入的高频问题点,必须先埋监控再升级。我们的方案是把 Router 的 JMX 指标接 Prometheus,EC 重建事件用 WebHDFS 订阅。

小结:到底要不要升级

最后给一份我自己的决策框架:

应该升级 3.x 的信号

  • 冷温数据 > 30%,存储采购压力大 → 强升级,EC 一年就能把升级成本省回来。
  • 集群挂 3 个以上 NameNode,配置管理痛苦 → 升级,RBF 是质变。
  • 有 GPU / FPGA 调度需求 → 升级,通用资源模型是前提。
  • 生态(Hive / Spark / HBase)已经升到对应版本 → 顺手升级,没有额外成本。

可以暂缓的信号

  • 集群小(< 200 节点),单 NN 顶得住,存储不紧张 → 升级收益小、风险大,可以缓。
  • 上层生态锁死在老版本,升级上层代价大 → 先把生态升上来再考虑 Hadoop。
  • 业务以低延迟小文件为主,EC 用不上,RBF 也用不上 → 暂缓。

回头看,Hadoop 3.x 不是一次革命性的更新,而是一次”把 2.x 没做完的功课补完”的演进。EC 把存储经济性拉到了一个新水位,RBF 让 Federation 终于可运维,YARN 的资源模型升级为 AI 工作负载开了门。对一个已经在跑大数据底座的公司来说,3.x 不是要不要升的问题,而是什么时候升、按什么节奏升的问题。希望这篇复盘能给在观望的你提供一份足够细的参考。