深入 Hadoop 06 - HDFS 3.x 演进与纠删码
上一篇讲了 HA 怎么解决 NameNode 单点问题。本篇讲 HDFS 在 Hadoop 3.x 时代的三个核心演进:纠删码(Erasure Coding)、路由联邦(Router-Based Federation)、Observer NameNode。这三个特性分别解决三个不同的瓶颈:存储成本、NameNode 元数据上限、读吞吐上限。
HDFS 3.x 常被描述成"HDFS 的现代化版本"。这个描述对应了发布节奏但没解释演进逻辑。准确的说法是:HDFS 在 2.x 解决了 HA 和 YARN 之后,3.x 时代面对的核心问题是 PB 级集群的存储成本和元数据瓶颈,所以引入纠删码降存储、引入路由联邦解决元数据上限、引入 Observer NameNode 拆分读写流量。三个特性的设计动机都是"假设集群规模已经超过单 NameNode 上限"。
本篇只抓一个问题:HDFS 3.x 引入的三个主要特性各自解决什么瓶颈、用什么代价、适合什么场景。
纠删码:用 CPU 换存储
HDFS 默认三副本意味着每存 1TB 用户数据要占 3TB 物理存储。这个开销在 PB 级集群里是巨大的成本——一个 100PB 用户数据的集群需要 300PB 物理存储。Hadoop 3.0 引入纠删码把存储开销从 3x 降到约 1.5x,节省 50% 物理存储。
HDFS 用的纠删码算法是 Reed-Solomon(RS),默认配置 RS-6-3:
1 | |
对比三副本:
1 | |
注意 RS-6-3 与三副本不是同一维度的容错模型。RS-6-3 在一个 stripe 内可容忍任意 3 个 internal block 丢失,但要求至少 9 个 DataNode,机架分布也要足够;三副本的恢复路径更简单,读延迟和恢复 CPU 成本更低。
代价是写入和重建时的 CPU 开销。写入时纠删码编码器要计算 3 个校验单元(基于 6 个数据单元做矩阵运算),读取完整数据时如果某个单元丢失,解码器要用剩余单元重建丢失部分。三副本没有这种计算开销。
HDFS 默认仍是三副本复制;在内置 EC 策略里,默认启用的是 RS-6-3-1024k。自 Hadoop 3.0.0 起,内置策略还包括 RS-10-4(更省存储但写入开销更大)和 XOR-2-1-1024k(轻量级,容忍 1 个单元丢失且 CPU 开销较小)。选择哪种取决于集群的存储成本敏感度与 CPU 资源充裕度。
纠删码适用于"冷数据"——访问频率低、对延迟不敏感的数据。HDFS 文件本身仍是写一次、追加导向的模型;EC 文件可以正常读取,包括按位置读取,但追加、truncate、hflush/hsync 和降级读都有额外限制或成本,不适合频繁小写和低延迟随机访问。热数据(HBase、实时分析)仍然用三副本,因为纠删码的解码延迟和恢复开销会拖慢访问路径。
Hadoop 3.4.1 面向用户的 HDFS 纠删码使用条带布局:
条带布局(Striped Layout):每个文件被切成 cell(默认 1MB),6 个 cell 组成一个 stripe,分散写到 9 个 DataNode。这是默认布局,存储效率最高。
启用纠删码:
1 | |
启用后该目录下新写入的文件会自动按 RS-6-3 编码存储。已存在的旧文件不自动迁移,需要复制到新目录才能应用。
路由联邦:解决 NameNode 元数据上限
HDFS 单 NameNode 元数据上限受 JVM 堆内存约束。一个有 100GB 堆的 NameNode 大概能管 6-7 亿个文件(每个文件 + block 大约 150 字节元数据)。超过这个规模的集群——典型场景是大型互联网公司的日志归档、电商图片库、视频原始素材——单 NameNode 装不下。
Hadoop 2.x 引入了 HDFS Federation,把命名空间切成多个独立 NameNode,每个 NameNode 管一个 Block Pool。客户端通过 ViewFS 配置路径映射,例如:
1 | |
ViewFS 的缺陷是路径映射配置在客户端——每个客户端的 core-site.xml 都要维护这份映射,集群调整路径时所有客户端配置都要更新。这在大规模多租户集群里运维成本很高。
Hadoop 3.x 引入 Router-Based Federation(RBF)解决客户端配置问题。RBF 引入一个无状态的 Router 进程,客户端只配置 Router 地址,Router 内部维护路径 → NameNode 映射,把请求路由到对应 NameNode:
1 | |
Router 是无状态的——可以部署多个 Router 实例做负载均衡,任何一个 Router 挂掉客户端自动切换到另一个。映射表存在 State Store,所有 Router 共享这份映射。
RBF 的核心收益是路径映射集中管理。集群调整路径时,运维只更新 State Store,所有 Router 立刻看到新映射,客户端配置不需要变化。
RBF 还提供 Mount Table 管理 API,可以动态挂载/卸载子树到不同 NameNode。这种动态挂载让 HDFS 集群可以按需扩展——某个 NameNode 元数据满了,把它的一部分子树迁移到新 NameNode,更新 Mount Table 即可,客户端无感。
RBF 在 Hadoop 2.9.0 / 3.0.0 引入,Hadoop 3.4.1 文档仍把它描述为 Router、State Store 与 Mount Table 组合的联邦访问层。它是否适合生产集群,要看 Router 高可用、State Store 后端、Mount Table 变更流程和跨命名空间操作边界是否已经被运维流程覆盖。
Observer NameNode:拆分读写流量
HDFS HA 部署里 Standby NameNode 不接受客户端 RPC,只做 tail 和 Checkpoint。这让 Standby 的资源(CPU、内存)大部分时间闲置。
观察生产集群的 NameNode 流量分布:读 RPC(listStatus、getBlockLocations、getFileInfo 等元数据查询)通常占 80%+ 流量,写 RPC(create、delete、append 等元数据修改)占 20% 以下。Active NameNode 同时承担读和写,瓶颈往往在读侧——特别是 MapReduce 作业启动时大量 Task 同时 getBlockLocations,把 Active NameNode CPU 打满。
Hadoop 3.3.0 引入 Observer NameNode(ONN);该能力也回移到 3.2.2、3.1.4 和 2.10.0。Observer 让只读 NameNode 承担读 RPC,典型部署形态是一个 Active、一个 Standby 和至少一个 Observer。
1 | |
Observer NameNode 不是随意返回"最终一致"的旧视图。客户端会携带从 Active 看到的最新状态 ID,Observer 如果还没 tail 到对应 edit,就等待追上或让客户端回到 Active,从而维持读到自己刚写入结果的语义。代价是 Observer 读会受 EditLog tail 延迟影响;在追赶时,读请求可能等待而不是直接返回。
客户端通过 dfs.client.failover.observer.reads 配置启用 Observer 读。开启后客户端的读 RPC 会路由到 Observer,写 RPC 仍然路由到 Active。客户端通过 DFS Observer Read Provider 选择最近的 Observer(基于距离排序)。
Observer NameNode 的收益是读吞吐线性扩展——可以部署多个 Observer(Hadoop 3.x 支持任意数量),读吞吐随 Observer 数量线性增长。这对元数据读密集型负载(HBase RegionServer 频繁 getBlockLocations、Spark Driver 大量 listStatus)效果显著。
Observer NameNode 不替代 HA——它依赖 JournalNode 和 Active NameNode,不能在 Active 宕机时接管写服务。HA 切换仍然在 Active 和 Standby 之间进行,Observer 不参与切换。
三个特性的应用场景
这三个特性各自解决不同瓶颈。生产集群是否启用、怎么组合,取决于集群的具体瓶颈:
1 | |
大型集群的典型部署:纠删码 + RBF + Observer 同时启用,按子树(subtree)分配不同 NameNode,每个 NameNode 配 1-2 个 Observer,冷数据走纠删码目录、热数据走三副本目录。
小型集群(< 1 亿文件、< 100TB 数据)通常不需要任何这些特性。三副本 + 单 NameNode HA 足够。
实验:观察 HDFS 3.x 特性
UNVERIFIED_RUNTIME:下面命令和输出形态用于在真实 Hadoop 3.x 集群上核对现象,本轮未连接 live Hadoop 集群运行。
观察纠删码(如果集群启用):
1 | |
输出当前集群支持的纠删码策略:
1 | |
为某个目录启用纠删码:
1 | |
上传一个文件到这个目录,用 hdfs fsck 观察块分布——会看到 9 个 block 分布到 9 个 DataNode(6 data + 3 parity),而不是 3 副本。
观察 RBF(如果集群启用):
1 | |
输出 Mount Table:
1 | |
每条记录展示一个路径子树对应的 NameNode 集群。
观察 Observer NameNode(如果集群启用):
1 | |
输出所有 NameNode 的角色:
1 | |
如果集群没有启用这些特性,输出会显示 HA 部署的 active + standby 两个,没有 observer。
模式提炼
HDFS 3.x 的三个特性可以提炼成一组分布式系统扩展性模式:
1 | |
这三个模式不只是 HDFS。
纠删码模式出现在 Ceph 的 erasure-coded pool、很多对象存储系统的底层冗余实现,以及冷数据归档系统。具体是否默认使用 EC、使用哪种 RS 参数、对用户暴露什么价格模型,取决于产品和存储类别,不能直接等同于 HDFS 的 RS-6-3。
无状态路由层模式出现在 Kubernetes Ingress、Service Mesh(Istio Pilot 无状态 + etcd 状态)、数据库代理(Vitess、Sharding-Sphere)。任何一个"单控制节点瓶颈"场景都会演化出这种结构。
读写流量分离模式出现在 MySQL 读写分离、PostgreSQL 流复制 + 读副本、Redis 主从、Kafka follower fetch。任何读多写少的场景都可以用这种结构扩展读吞吐。
工程迁移表
| HDFS 3.x 特性 | Ceph | 对象存储 | Kubernetes | 数据库 |
|---|---|---|---|---|
| 纠删码(EC) | erasure-coded pool | 对象存储底层冗余 | - | 冷数据归档系统 |
| 路由联邦(RBF) | CRUSH 算法 | bucket 路由 | Ingress controller | sharding proxy |
| Observer NameNode | OSD read replica | 多 AZ 端点 | 多副本 API Server | MySQL read replica |
| Mount Table 动态挂载 | crush map | bucket | service + endpoints | sharding config |
注意对象存储这一列。S3、OSS 这类对象存储通常把多副本、纠删码、跨可用区复制等机制封装在服务端,用户看到的是存储类别、可用性和价格,而不是直接选择 HDFS 风格的 EC policy。大数据栈从 HDFS 迁移到对象存储时,成本收益来自服务端冗余、弹性和运维模型的组合;代价是对象存储的对象级接口延迟和语义不同于 HDFS 的本地元数据 RPC,所以热数据场景 HDFS 仍然占优。第二十一篇会展开这个对比。
常见误解
误解一:“纠删码总是比三副本好”。纠删码节省存储但增加 CPU 开销和写入延迟。冷数据(归档、备份)适合纠删码,热数据(数据库、实时分析)适合三副本。把 HBase RegionServer 的数据放到纠删码目录会让随机读延迟显著上升,因为每次读都要解码。
误解二:“Federation 之后集群规模无上限”。Federation 让集群可以横向扩展 NameNode,但每个 NameNode 仍然是单点瓶颈——单个 NameNode 的元数据上限仍然是 JVM 堆内存决定的。集群规模无上限只意味着"可以加 NameNode",不意味着"一个 NameNode 可以管任意多文件"。RBF 也引入了新的运维复杂度——Mount Table 管理、跨 NameNode 操作不支持、客户端配置兼容性。
误解三:“Observer NameNode 可以替代 Active”。Observer 只接受读 RPC,不能接受写。Active 宕机时 Observer 不能接管写服务(即使配置成"Observer 也可以转 Active",仍然需要 Standby 在场)。Observer 的价值是读吞吐扩展,不是 HA 替代品。
误解四:“启用纠删码会立刻节省存储”。已存在的旧文件不会自动迁移到纠删码布局,需要复制到启用纠删码策略的目录才生效。这一步通常是离线进行的——先把冷数据迁移到纠删码目录,原三副本文件删除,物理存储才会真正下降。
误解五:“RBF 让 HDFS 像对象存储一样易扩展”。RBF 解决的是 NameNode 元数据上限,但 HDFS 仍然是"文件系统"语义——每个文件有路径、有元数据、有 block 切分。对象存储的"object + key"语义比 HDFS 的"file + path"更简洁,扩展性也更好。RBF 让 HDFS 接近对象存储的扩展能力,但没有完全达到。这是为什么云原生时代对象存储逐步替代 HDFS(第二十一篇展开)。
练习
-
在 Hadoop 3.x 集群上运行
hdfs ec -listPolicies,查看支持的纠删码策略。为某个测试目录启用 RS-6-3,上传一个 100MB 文件,用hdfs fsck观察块分布(应该是 9 个 block 而不是 3 副本)。 -
在
apache/hadoop源码里找到ErasureCodingPolicy.java、RBFRouter.java、ObserverNameNode.java(hadoop-hdfs-project 模块),观察三个特性的核心实现类。 -
思考题:如果一个集群同时启用纠删码 + RBF + Observer NameNode,单个子树(subtree)的工作流程是怎样的?读、写、元数据查询分别走哪些组件?
-
思考题:为什么 HDFS 选择 RS-6-3 作为默认纠删码策略而不是 RS-3-2 或 RS-10-4?存储效率、容错能力、CPU 开销三者是怎么权衡的?
系列导航
参考资料
- Apache Hadoop 官方文档:HDFS Erasure Coding. https://hadoop.apache.org/docs/r3.4.1/hadoop-project-dist/hadoop-hdfs/HDFSErasureCoding.html
- Apache Hadoop 官方文档:Router-Based Federation. https://hadoop.apache.org/docs/r3.4.1/hadoop-project-dist/hadoop-hdfs-rbf/HDFSRouterFederation.html
- Apache Hadoop 官方文档:Observer NameNode. https://hadoop.apache.org/docs/r3.4.1/hadoop-project-dist/hadoop-hdfs/HdfsUserGuide.html#Observer_NameNode
- Jingcheng Du et al. Erasure Coding for HDFS (HDFS-EC).(Hadoop Summit 2015,EC 设计与生产部署报告)
- Tsz-Wo Nicholas Sze. HDFS Routing Federation at Yahoo Japan.(生产部署案例,RBF 在大规模集群的效果)
- Apache Hadoop 源码:
ErasureCodingPolicy.java、Router.java、ObserverNameNodeRpcServer.java. https://github.com/apache/hadoop
