Hadoop 3.x 核心新特性深度解读
引言:从 2.x 到 3.x,这一跳到底值不值
我第一次把生产集群从 Hadoop 2.x 升到 3.x,是在一个接近 2000 节点 的集群上。当时团队最直接的痛点是:冷数据膨胀太快,每年采购的 SATA 盘里有一大半都喂给了三副本。调研了一圈之后,Hadoop 3.x 三个能力进入了我们的视野——纠删码(Erasure Coding)、Router-based Federation、YARN 的资源模型升级。这三件事几乎覆盖了”省存储、降复杂度、扩调度边界”三个方向,也是我判断 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 不是银弹,我在生产上给它划了明确的红线:
- 写入吞吐下降:RS 编码本身是 CPU 密集的,默认的
RS-6-3编码相比三副本,单客户端写吞吐大约下降 20%~30% 。我们专门做过对比测试,小文件场景下这个比例还会更高。 - 随机读放大:striping 布局下一个逻辑读可能要 fan-out 到多个 DataNode,随机读尾延迟明显。EC 目录不要放 OLTP 风格的工作负载。
- 小文件问题反而更严重:EC 的最小开销是”数据单元数 + 校验单元数”个 block,RS-6-3 至少要占 9 个 block。如果你有一堆 1KB 的小文件,开销比三副本还离谱。
- 重建成本:丢块后的重建是 CPU + 网络双吃,集群繁忙时建议限速(
dfs.datanode.ec.reconstruction.stripedread.bandwidth,单位字节/秒)。 - 与副本互斥:EC policy 一旦作用到一个目录,该目录下新文件就走 EC;老文件不会被自动转换,需要显式
mover数据迁移。
适用边界一句话:EC 适合”一次写、多次顺序读、数据量大、访问频率低”的冷温数据;热数据、小文件、强随机读场景,老老实实三副本。
1.4 策略配置与日常操作
社区内置了五条 EC 策略,开箱即用。开 EC 的标准动作是:先在目录上 setPolicy,然后(可选)把存量数据用 HDFS Mover 迁移过来。下面这段是在生产上为日志归档目录开启 RS-6-3 的完整示例:
1 | # 1. 查看当前集群已注册的 EC 策略 |
几个我踩过的坑值得提醒:
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):
- 默认除了
memory、vcores,新增了可选的resource字段; - 通过
yarn-site.xml配置yarn.resource-types,注册如yarn.io/gpu、yarn.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=r5、gpu.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.xml、yarn-default.xml、mapred-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 把启动脚本(hdfs、yarn、hadoop)用 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 镜像。具体流程:
- 备份 fsimage + editlog;
- NN 进入 safe mode,部署 3.x 二进制;
- 启动 3.x NN,跑
dfsadmin -rollingUpgrade finalize; - 滚动重启 DN,逐台升级;
- 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 不是要不要升的问题,而是什么时候升、按什么节奏升的问题。希望这篇复盘能给在观望的你提供一份足够细的参考。
