Apache Hadoop HDFS 架构
HDFS 概述
HDFS(Hadoop Distributed File System)是 Apache Hadoop 生态的核心存储组件,专为运行在廉价硬件集群上的大规模数据存储而设计。它以高容错、高吞吐量访问和流式数据处理为核心目标,是分布式计算框架(如 MapReduce、Spark)的理想基石。
设计理念
- 超大文件:HDFS 擅长处理 GB 级甚至 TB 级的单个文件,而非海量小文件。
- 一次写入,多次读取:数据写入后很少需要修改,这简化了数据一致性模型并提高了吞吐。
- 流式数据访问:关注高吞吐量而非低延迟,适合批量处理而非交互式查询。
- 硬件故障是常态:通过多副本策略自动处理节点故障,保障数据不丢失。
- 移动计算比移动数据更划算:优先将计算任务调度到数据所在的节点,降低网络开销。
HDFS 核心架构
HDFS 采用主/从(Master/Slave)架构,由一个 NameNode(主节点)和多个 DataNode(从节点)组成。
┌──────────┐
│ Client │
└─────┬────┘
│
┌──────────┴──────────┐
│ │
┌────▼─────┐ ┌────▼─────┐
│ NameNode │ │Secondary │
│ (主节点) │ │NameNode │(检查点节点)
└────┬─────┘ └──────────┘
│
┌──────────┼──────────┐
│ │ │
┌──▼──┐ ┌──▼──┐ ┌──▼──┐
│Data │ │Data │ │Data │
│Node1│ │Node2│ │Node3│
└─────┘ └─────┘ └─────┘
NameNode(名称节点)
NameNode 是 HDFS 的管理者,负责:
- 元数据管理:维护整个文件系统的目录树,记录每个文件的名称、目录结构、文件到数据块的映射关系等。
- 数据块位置信息:掌握每个数据块存储在哪些 DataNode 上(该信息不持久化,由 DataNode 启动时上报)。
- 命名空间操作:处理客户端的文件创建、打开、关闭、重命名等请求。
- 副本策略控制:监控数据块副本数量,当副本数不足或增加时,触发复制或删除。
注意:NameNode 将所有元数据保存在内存中以保证高性能,因此其内存大小决定了集群能存储的文件数量上限。在生产中,NameNode 通常会部署在内存充裕的节点上。
DataNode(数据节点)
DataNode 是实际存储数据的节点,负责:
- 数据块的读写:根据 NameNode 的指令完成数据块的存储或读取。
- 心跳汇报:定期(默认 3 秒)向 NameNode 发送心跳,报告存活状态及持有的数据块列表。
- 副本维护:根据 NameNode 的调度,执行数据块的复制、删除或转移任务。
- 流水线复制:当客户端写入数据时,DataNode 之间以管道方式传输数据,依次写入多个副本。
Secondary NameNode(辅助名称节点)
尽管名字带“Secondary”,但它并不是 NameNode 的热备份,而是帮助 NameNode 合并编辑日志(EditLog)和镜像文件(FsImage)的检查点节点。
- FsImage:某一时刻文件系统命名空间的完整快照。
- EditLog:记录每次元数据变更的事务日志,如创建文件、删除文件等。
- 随着运行,EditLog 会不断增长。Secondary NameNode 定期获取 NameNode 的 FsImage 和 EditLog,合并后生成新的 FsImage 回传给 NameNode,从而限制 EditLog 的大小,加速 NameNode 重启恢复。
在高可用(HA)配置中,该角色被备用 NameNode(Standby NameNode)取代。
数据块与副本机制
数据块化存储
HDFS 将文件拆分为固定大小的块(block)进行存储,默认块大小为 128 MB(Hadoop 2.x 及以后;1.x 默认为 64 MB)。拆分后每个块作为独立单元存储,文件可以大于集群中任意单个磁盘的容量。
- 块大小可配置:可在
hdfs-site.xml中通过dfs.blocksize参数调整,典型值从 128 MB 到 512 MB 不等。 - 按块寻址:NameNode 记录每个文件由哪些块组成,客户端需通过 NameNode 获取块位置再直接与 DataNode 通信。
多副本策略
为提供高容错,每个数据块默认保存 3 份副本。副本放置策略兼顾可靠性和网络带宽:
- 第一个副本:若客户端位于 DataNode 上,则优先写入本地节点;否则随机选择一台 DataNode。
- 第二个副本:放置在与第一个副本 不同机架 的节点上,以应对机架级故障。
- 第三个副本:放置在与第二个副本 相同机架 但不同节点上,以减少跨机架传输开销。
当发现某个块的副本数不足时,NameNode 会指令 DataNode 从其余副本复制,以维持目标副本数。
HDFS 读写流程
写入文件流程
- 客户端调用
DistributedFileSystem.create()并向 NameNode 发起 创建文件请求。 - NameNode 检查命名空间(是否重名、权限等),通过后在元数据中创建文件记录,并返回 块分配信息(DataNode 列表) 给客户端。
- 客户端将数据切分成一个个数据包,通过 FSDataOutputStream 建立与第一个 DataNode 的 数据管道(Pipeline)。
- 第一个 DataNode 连续接收数据包,一边写入本地磁盘,一边转发给第二个 DataNode,依此类推,直到所有副本节点都完成写入。
- 每个数据包写入完毕后,DataNode 沿管道反向发送确认(ACK)。
- 当第一个块写满后,客户端重复步骤 2 获取新块的 DataNode 列表,开始下一个块的写入。
- 文件所有块写入完成后,客户端调用
close(),并向 NameNode 发送完成信号。
读取文件流程
- 客户端通过
DistributedFileSystem.open()打开文件,NameNode 返回文件 块列表及每个块所在的 DataNode 地址(按与客户端的距离排序)。 - 客户端从最近的 DataNode 开始读取第一个块的数据,FSDataInputStream 封装了具体通信。
- 当某个块的读取失败或网络中断,客户端自动尝试从下一个拥有该副本的 DataNode 继续读取。
- 读取完一个块后,切换到下一个块的地址,直到文件全部读取完毕。
整个读写过程中,客户端直接与 DataNode 交互,NameNode 仅提供元数据定位,这避免了单点瓶颈。
高可用性与容错
NameNode 单点故障的解决
早期 HDFS 中 NameNode 单点故障是整个集群的致命弱点。如今通过 HDFS High Availability(HA) 方案实现双 NameNode 热备:
- 配置一对 Active NameNode 和 Standby NameNode。
- 两个 NameNode 通过 共享存储(JournalNode 集群或共享 NAS) 同步编辑日志,Standby 节点实时回放 EditLog 保持内存元数据一致。
- 所有 DataNode 同时向两个 NameNode 发送心跳和块报告。
- 使用 ZooKeeper + FailoverController 自动检测故障并切换 Active 节点。
有了 HA 之后,Secondary NameNode 的职责被 Standby NameNode 替代 — Standby 负责定期合并 FsImage 并上传回 Active 节点,不再需要额外的 Secondary NameNode。
DataNode 容错
- 副本自动恢复:当 DataNode 掉线或磁盘损坏导致块副本不足,NameNode 会安排其他节点补全副本。
- 心跳超时:若默认 10 分钟内未收到心跳,NameNode 将该 DataNode 标记为“死亡”,不再向其分配新请求,并启动块副本补充。
- 校验和检测:DataNode 存储数据时保存校验和,读取时自动校验,发现数据损坏则返回错误,客户端转而读取其他副本。
机架感知(Rack Awareness)
为了提高网络效率和数据可靠性,HDFS 支持将节点划分到逻辑机架。管理员可通过脚本或配置表定义每个 DataNode 所属的机架 ID。
- 副本放置策略会考虑机架信息,确保至少一个副本位于不同机架。
- 读取数据时,NameNode 根据机架距离排序返回的 DataNode 列表,使客户端优先访问同机架节点,降低带宽消耗。
- 通过
net.topology.script.file.name参数指定机架映射脚本,Hadoop 自动感知节点所属机架。
关键配置参数速览
| 参数 | 默认值 | 说明 |
|---|---|---|
dfs.blocksize |
134217728 (128M) | 数据块大小(字节) |
dfs.replication |
3 | 默认副本数 |
dfs.namenode.heartbeat.recheck-interval |
300000 (5分钟) | 判定 DataNode 死亡的时间 |
dfs.ha.automatic-failover.enabled |
false | 是否开启自动故障转移 |
dfs.namenode.name.dir |
file://${hadoop.tmp.dir}/dfs/name | NameNode 元数据存储路径 |
dfs.datanode.data.dir |
file://${hadoop.tmp.dir}/dfs/data | DataNode 数据存储路径 |
dfs.namenode.secondary.http-address |
0.0.0.0:9868 (取决于版本) | Secondary NameNode 地址 |
适用场景与局限
擅长处理的场景
- 超大规模数据集(PB 级)的存储与处理。
- 对吞吐量要求极高的批量数据处理。
- 一次写入、多次读取的日志分析、数据仓库等。
- 需要良好横向扩展能力的廉价硬件集群。
不擅长的场景
- 低延迟数据访问:HDFS 为高吞吐设计,不适合毫秒级访问。
- 大量小文件:每个文件的元数据占用 NameNode 内存,小文件过多会耗尽内存并降低 RPC 性能。
- 任意文件修改:不支持已有文件的随机写入和修改,仅支持追加(Hadoop 3.x 支持追加但性能有限)。
对于要求低延迟或海量小文件的场景,可以考虑 HBase、Apache Kudu 或对象存储(如 S3)等替代方案。
总结
HDFS 通过 NameNode/DataNode 主从架构、分块存储、多副本冗余和机架感知策略,构建了一个高度可靠、可线性扩展的分布式文件系统。掌握其架构原理和读写流程,是深入 Hadoop 生态及大规模数据处理的重要基础。在实际生产中,还需结合高可用部署、块大小调优及合理的命名空间设计,才能充分发挥其效能。