HDFS 深度解析:大数据存储的"硬盘不够、单盘坏了怎么办"
做大数据的人,绕不开 HDFS。
你的数据量上了 PB 级,单机硬盘装不下;硬盘总会坏,数据丢了怎么办?HDFS 就是 Google GFS 的开源实现,专门解决这两个问题:存储不够,加机器;硬盘坏了,多副本。
HDFS 解决的两个核心问题
问题1:硬盘不够用
单机硬盘容量有限(比如一块 20TB),但数据量可能是 PB 级。解决方案:分布式存储——多台机器组成集群,数据分散存到各个节点上。机器不够就加机器,理论上无限扩展。
问题2:硬盘坏了数据丢
单机硬盘会坏,坏了数据就没了。解决方案:多副本——每个数据块默认存 3 份,分别放到不同的机器上。一台机器挂了,数据还在。
这就是 HDFS 的核心理念:用廉价硬件的集群,解决海量数据的可靠存储问题。
HDFS 核心架构:主从协同的三层模型
HDFS 是典型的 Master-Slave 架构,四个角色各司其职:
| 角色 | 管什么 | 特点 |
|---|---|---|
| NameNode | 管"账本"(元数据) | 一个集群只有一个,存内存里 |
| DataNode | 管"货"(实际数据) | 每个节点一个,存磁盘上 |
| Secondary NameNode | 帮 NameNode 合并账本 | 不是备份,是助手 |
| Client | 用户入口 | 读写文件的接口 |
一句话总结:NameNode 管"什么东西存在哪",DataNode 管"东西本身",Secondary NameNode 帮 NameNode 整理账本。
NameNode:存的是"账本"不是"货"
NameNode 是 HDFS 的大脑,但它不存实际数据,只存元数据——文件在哪个目录、权限是什么、分成了几个块、每个块在哪些 DataNode 上。
元数据存在哪?两个文件:
- FsImage:某一时刻的元数据快照(静态账本)
- EditLog:操作日志(动态流水账),记录每次文件操作
启动的时候,NameNode 把 FsImage 读到内存,然后把 EditLog 里的操作重新执行一遍,生成最新的元数据。
NameNode 的瓶颈在哪?所有元数据都在内存里。所以 HDFS 能存多少个文件,取决于 NameNode 的内存大小。每个文件/目录的元数据大约占 150 字节,如果内存是 128GB,理论上限大约是 8 亿个文件——但实际远到不了这个数。
小文件问题是真实存在的:每个小文件都占一条元数据条目,还意味着大量的 Map 任务开销。能合并就合并。
DataNode:存的是"货",货在磁盘上
DataNode 是真正存数据的节点,实际数据以 Block 为单位存在磁盘上。
Block 大小:默认 128MB
- 大 Block 适合大文件,减少寻址开销,提升吞吐
- 小于 Block 的文件不会占满整个 Block(比如 1MB 文件只占 1MB)
DataNode 定期向 NameNode 汇报:
- 心跳(Heartbeat):默认每 3 秒一次,告诉 NameNode “我还活着”
- 块报告(Block Report):默认每 6 小时一次,告诉 NameNode “我手里有哪些 Block”
心跳停了,NameNode 就把这个节点标记为"死了",然后把上面的 Block 在其他节点上补副本。
Secondary NameNode:不是备份,是助手
这个命名容易误导人。Secondary NameNode不是 NameNode 的热备,不能直接接管 NameNode 的工作。
它的职责是:定期帮 NameNode 合并 FsImage 和 EditLog,避免 EditLog 无限膨胀。
合并流程:
- Secondary NameNode 从 NameNode 拉取 FsImage 和 EditLog
- 在本地加载 FsImage,重放 EditLog 的操作,生成新的 FsImage
- 把新的 FsImage 传回 NameNode,替换旧的
如果 NameNode 挂了怎么办?先用 Secondary NameNode 上的 FsImage 恢复元数据,但会丢失一部分最新的操作(EditLog 中尚未合并的部分)。这只能算"部分恢复",不能完全替代 NameNode。
客户端读写流程
读文件:
- Client 向 NameNode 请求文件位置 → NameNode 返回 Block 列表及所在 DataNode
- Client 直接连 DataNode 读数据(不经过 NameNode)
写文件:
- Client 把文件拆成 Block,向 NameNode 请求第一个 Block 的写入位置
- NameNode 返回一组 DataNode(用于存放副本)
- Client 以 pipeline 方式把数据依次发给第一个 DataNode,第一个再发给第二个,依次类推
- 写完确认后,向 NameNode 汇报元数据更新
关键点:NameNode 不参与数据传输,只负责协调位置。数据直接走 Client ↔ DataNode 通道。
HDFS 关键机制:确保高可用与高效读写
副本策略:3 份副本怎么放
默认 3 份副本,分布策略如下:
- 第一份:放在客户端所在节点(如果客户端不在集群内,随机选一个)
- 第二份:放在与第一份不同机架的节点
- 第三份:放在与第二份同机架但不同节点
为什么要跨机架?一个机架的交换机坏了,整个机架的节点都断连。如果 3 个副本都在同一个机架,数据全丢。跨机架存储保证了即使单机架故障,数据仍然可用。
安全模式:NameNode 启动时的"保护期"
NameNode 启动时会进入安全模式(Safe Mode),此时:
- 只读,不接收写请求
- NameNode 等待 DataNode 上报块报告,检查每个 Block 的副本数是否达标
- 当达标比例超过 99.9%,且持续一段时间,自动退出安全模式
如果集群刚启动时某个机架没上线,副本数不够,安全模式会一直卡住。这时候可以手动hdfs dfsadmin -safemode leave强制退出,但要确认数据完整性没问题。
小文件处理:优化 NameNode 内存占用
过多小文件会消耗 NameNode 内存并降低处理效率,解决方案包括:
- 合并小文件:通过
hadoop fs -getmerge将多个小文件合并为大文件; - 使用 SequenceFile/ORC/Parquet:将小文件打包成列式存储格式,减少元数据量;
- 限制小文件数量:在数据采集阶段(如 Flume)配置文件滚动策略,避免生成过多小文件。
HDFS 常见操作命令与最佳实践
常用命令示例
# 创建目录hdfs dfs-mkdir/user/hadoop/test# 上传本地文件到 HDFShdfs dfs-putlocalfile.txt /user/hadoop/test/# 下载 HDFS 文件到本地hdfs dfs-get/user/hadoop/test/localfile.txt ./# 查看文件内容hdfs dfs-cat/user/hadoop/test/localfile.txt# 删除文件hdfs dfs-rm/user/hadoop/test/localfile.txt# 查看目录下文件信息(包括块大小、副本数)hdfs dfs-ls-h/user/hadoop/test