HDFS 深度解析:大数据存储的"硬盘不够、单盘坏了怎么办"
做大数据的人,绕不开 HDFS。
你的数据量上了 PB 级,单机硬盘装不下;硬盘总会坏,数据丢了怎么办?HDFS 就是 Google GFS 的开源实现,专门解决这两个问题:存储不够,加机器;硬盘坏了,多副本。
HDFS 解决的两个核心问题
问题1:硬盘不够用
单机硬盘容量有限(比如一块 20TB),但数据量可能是 PB 级。解决方案:分布式存储------多台机器组成集群,数据分散存到各个节点上。机器不够就加机器,理论上无限扩展。
问题2:硬盘坏了数据丢
单机硬盘会坏,坏了数据就没了。解决方案:多副本------每个数据块默认存 3 份,分别放到不同的机器上。一台机器挂了,数据还在。
这就是 HDFS 的核心理念:用廉价硬件的集群,解决海量数据的可靠存储问题。
HDFS 核心架构:主从协同的三层模型
HDFS 是典型的 Master-Slave 架构,四个角色各司其职:
| 角色 | 管什么 | 特点 |
|---|---|---|
| NameNode | 管"账本"(元数据) | 一个集群只有一个,存内存里 |
| DataNode | 管"货"(实际数据) | 每个节点一个,存磁盘上 |
| Secondary NameNode | 帮 NameNode 合并账本 | 不是备份,是助手 |
| Client | 用户入口 | 读写文件的接口 |
#mermaid-svg-PSYWoi6hTzImupr0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PSYWoi6hTzImupr0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PSYWoi6hTzImupr0 .error-icon{fill:#552222;}#mermaid-svg-PSYWoi6hTzImupr0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PSYWoi6hTzImupr0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PSYWoi6hTzImupr0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PSYWoi6hTzImupr0 .marker.cross{stroke:#333333;}#mermaid-svg-PSYWoi6hTzImupr0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PSYWoi6hTzImupr0 p{margin:0;}#mermaid-svg-PSYWoi6hTzImupr0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PSYWoi6hTzImupr0 .cluster-label text{fill:#333;}#mermaid-svg-PSYWoi6hTzImupr0 .cluster-label span{color:#333;}#mermaid-svg-PSYWoi6hTzImupr0 .cluster-label span p{background-color:transparent;}#mermaid-svg-PSYWoi6hTzImupr0 .label text,#mermaid-svg-PSYWoi6hTzImupr0 span{fill:#333;color:#333;}#mermaid-svg-PSYWoi6hTzImupr0 .node rect,#mermaid-svg-PSYWoi6hTzImupr0 .node circle,#mermaid-svg-PSYWoi6hTzImupr0 .node ellipse,#mermaid-svg-PSYWoi6hTzImupr0 .node polygon,#mermaid-svg-PSYWoi6hTzImupr0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PSYWoi6hTzImupr0 .rough-node .label text,#mermaid-svg-PSYWoi6hTzImupr0 .node .label text,#mermaid-svg-PSYWoi6hTzImupr0 .image-shape .label,#mermaid-svg-PSYWoi6hTzImupr0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-PSYWoi6hTzImupr0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PSYWoi6hTzImupr0 .rough-node .label,#mermaid-svg-PSYWoi6hTzImupr0 .node .label,#mermaid-svg-PSYWoi6hTzImupr0 .image-shape .label,#mermaid-svg-PSYWoi6hTzImupr0 .icon-shape .label{text-align:center;}#mermaid-svg-PSYWoi6hTzImupr0 .node.clickable{cursor:pointer;}#mermaid-svg-PSYWoi6hTzImupr0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PSYWoi6hTzImupr0 .arrowheadPath{fill:#333333;}#mermaid-svg-PSYWoi6hTzImupr0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PSYWoi6hTzImupr0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PSYWoi6hTzImupr0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PSYWoi6hTzImupr0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PSYWoi6hTzImupr0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PSYWoi6hTzImupr0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PSYWoi6hTzImupr0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PSYWoi6hTzImupr0 .cluster text{fill:#333;}#mermaid-svg-PSYWoi6hTzImupr0 .cluster span{color:#333;}#mermaid-svg-PSYWoi6hTzImupr0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PSYWoi6hTzImupr0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PSYWoi6hTzImupr0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-PSYWoi6hTzImupr0 .icon-shape,#mermaid-svg-PSYWoi6hTzImupr0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PSYWoi6hTzImupr0 .icon-shape p,#mermaid-svg-PSYWoi6hTzImupr0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PSYWoi6hTzImupr0 .icon-shape .label rect,#mermaid-svg-PSYWoi6hTzImupr0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PSYWoi6hTzImupr0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PSYWoi6hTzImupr0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PSYWoi6hTzImupr0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 元数据交互
2. 数据读写
2. 数据读写
2. 数据读写
3. 心跳/块报告
3. 心跳/块报告
3. 心跳/块报告
4. 元数据合并
存储数据
存储数据
存储数据
Client
客户端
NameNode
主节点 元数据管理
DataNode1
DataNode2
DataNode3
Secondary NameNode
辅助节点
本地磁盘
Block 副本
核心分工 NameNode 管元数据,DataNode 存数据,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 常见操作命令与最佳实践
常用命令示例
shell
# 创建目录
hdfs dfs -mkdir /user/hadoop/test
# 上传本地文件到 HDFS
hdfs dfs -put localfile.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