HDFS 分布式文件系统
什么是 HDFS
HDFS(Hadoop Distributed File System)是 Hadoop 的核心存储组件,基于 Google GFS 论文实现。它将大文件切分成固定大小的数据块(Block),分散存储在集群的多台机器上,并通过副本机制保证高可用。
核心设计目标:
- 存储超大文件(GB 到 TB 级别单文件)
- 流式数据访问(一次写入,多次读取)
- 运行在廉价商用硬件上
- 高容错性(硬件故障是常态)
架构设计
核心组件
┌─────────────────────────────────────────────────────┐
│ HDFS 集群 │
│ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ NameNode │ │ Secondary NameNode │ │
│ │ (主节点) │◄──────►│ (辅助节点) │ │
│ │ - 元数据 │ │ - 合并 EditLog │ │
│ │ - 目录树 │ │ - 不是 HA 备份! │ │
│ └──────┬───────┘ └──────────────────────┘ │
│ │ │
│ 心跳 & 块报告 │
│ │ │
│ ┌──────▼──────────────────────────────────────┐ │
│ │ DataNode 集群 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │DataNode1│ │DataNode2│ │DataNode3│ │ │
│ │ │ Block A │ │ Block A │ │ Block B │ │ │
│ │ │ Block B │ │ Block C │ │ Block C │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘NameNode(名称节点)
NameNode 是 HDFS 的大脑,负责管理文件系统元数据:
- 文件目录树结构
- 文件与 Block 的映射关系
- Block 与 DataNode 的映射关系(运行时维护,不持久化)
持久化机制:
FsImage:文件系统元数据的快照(磁盘)EditLog:对元数据的修改操作日志(磁盘)- 启动时:加载 FsImage + 回放 EditLog → 内存中的元数据
单点问题
NameNode 是单点,一旦宕机整个 HDFS 不可用。生产环境必须配置 HA(高可用),使用两个 NameNode(Active + Standby)。
DataNode(数据节点)
DataNode 负责实际的数据存储:
- 存储数据块(Block)及其校验和(Checksum)
- 定期向 NameNode 发送心跳(默认 3 秒)
- 定期发送块报告(默认 6 小时),汇报本节点所有 Block 信息
- 执行客户端的读写请求
Block(数据块)
HDFS 将文件切分成固定大小的 Block:
- Hadoop 2.x 默认 128MB(Hadoop 1.x 是 64MB)
- 每个 Block 默认保存 3 个副本(可配置)
- 副本放置策略:第一副本本地,第二副本同机架不同节点,第三副本不同机架
文件 example.txt(300MB)
│
├── Block 0(0-128MB) → DataNode1, DataNode2, DataNode4
├── Block 1(128-256MB)→ DataNode2, DataNode3, DataNode5
└── Block 2(256-300MB)→ DataNode3, DataNode1, DataNode6读写流程
写入流程
客户端
│
1. 调用 DistributedFileSystem.create()
│
2. NameNode 检查权限、文件是否存在,返回 DataNode 列表
│
3. 客户端建立 Pipeline(流水线)
│ Client → DN1 → DN2 → DN3
│
4. 数据以 Packet(64KB)为单位写入 Pipeline
│ 每个 Packet 写完后,DN 逐级返回 ACK
│
5. 文件关闭时,通知 NameNode 完成关键点:
- 数据直接从客户端流向 DataNode,不经过 NameNode
- Pipeline 写入:DN1 收到数据后立即转发给 DN2,不等全部写完
读取流程
客户端
│
1. 调用 DistributedFileSystem.open()
│
2. NameNode 返回文件的 Block 列表及对应 DataNode(按距离排序)
│
3. 客户端直接从最近的 DataNode 读取每个 Block
│
4. 读取完成,关闭流HDFS HA 高可用
生产环境必须配置 HA,防止 NameNode 单点故障:
┌─────────────────────────────────────────────────────┐
│ HDFS HA 架构 │
│ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Active NN │ │ Standby NN │ │
│ │ (主 NameNode)│◄──────►│ (备 NameNode) │ │
│ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │
│ └──────────┬────────────────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ JournalNode 集群 │ │
│ │ (共享 EditLog) │ │
│ │ JN1 JN2 JN3 │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ ZooKeeper 集群 │ │
│ │ (自动故障切换) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────┘- JournalNode:Active NN 将 EditLog 写入 JournalNode 集群,Standby NN 实时同步
- ZooKeeper:监控 NameNode 状态,Active 宕机时自动切换到 Standby
常用命令
bash
# 查看目录
hdfs dfs -ls /
# 上传文件
hdfs dfs -put localfile.txt /user/hadoop/
# 下载文件
hdfs dfs -get /user/hadoop/file.txt ./
# 创建目录
hdfs dfs -mkdir -p /user/hadoop/data
# 删除文件
hdfs dfs -rm /user/hadoop/file.txt
# 删除目录(递归)
hdfs dfs -rm -r /user/hadoop/data
# 查看文件内容
hdfs dfs -cat /user/hadoop/file.txt
# 查看文件大小
hdfs dfs -du -h /user/hadoop/
# 查看 Block 信息
hdfs fsck /user/hadoop/file.txt -files -blocks -locations
# 查看 NameNode 状态
hdfs dfsadmin -report
# 安全模式(启动时自动进入,等待 Block 报告)
hdfs dfsadmin -safemode get
hdfs dfsadmin -safemode leaveJava API 示例
java
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
public class HdfsDemo {
public static void main(String[] args) throws Exception {
// 创建配置
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://namenode:9000");
// 获取文件系统实例
FileSystem fs = FileSystem.get(conf);
// 上传文件
Path src = new Path("/local/data.txt");
Path dst = new Path("/hdfs/data.txt");
fs.copyFromLocalFile(src, dst);
// 读取文件
FSDataInputStream in = fs.open(new Path("/hdfs/data.txt"));
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) > 0) {
System.out.write(buffer, 0, bytesRead);
}
in.close();
// 列出目录
FileStatus[] statuses = fs.listStatus(new Path("/hdfs/"));
for (FileStatus status : statuses) {
System.out.println(status.getPath() + " " + status.getLen());
}
// 删除文件
fs.delete(new Path("/hdfs/data.txt"), false);
fs.close();
}
}性能调优
小文件问题
HDFS 不适合存储大量小文件(每个文件在 NameNode 内存中占约 150 字节元数据):
解决方案:
- HAR(Hadoop Archive):将小文件打包成 HAR 文件
- SequenceFile:将小文件合并成 SequenceFile(key=文件名,value=内容)
- CombineFileInputFormat:Spark/MapReduce 读取时合并小文件
- Hive 分区合并:定期合并小文件
bash
# 创建 HAR 文件
hadoop archive -archiveName data.har -p /input /output
# 查看 HAR 文件
hdfs dfs -ls har:///output/data.har/数据倾斜
某些 DataNode 存储数据过多时,可以使用 Balancer 重新均衡:
bash
# 启动 Balancer(带宽限制 100MB/s)
hdfs balancer -threshold 10 -bandwidth 104857600关键配置参数
xml
<!-- hdfs-site.xml -->
<!-- Block 大小(128MB) -->
<property>
<name>dfs.blocksize</name>
<value>134217728</value>
</property>
<!-- 副本数 -->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<!-- NameNode 内存(生产环境建议 32-64GB) -->
<property>
<name>dfs.namenode.handler.count</name>
<value>100</value>
</property>
<!-- DataNode 数据目录(多磁盘) -->
<property>
<name>dfs.datanode.data.dir</name>
<value>/data1/hdfs,/data2/hdfs,/data3/hdfs</value>
</property>常见问题排查
NameNode 进入安全模式
bash
# 查看安全模式状态
hdfs dfsadmin -safemode get
# 强制退出安全模式(谨慎使用)
hdfs dfsadmin -safemode leaveBlock 丢失/损坏
bash
# 检查文件系统健康状态
hdfs fsck / -list-corruptfileblocks
# 删除损坏文件
hdfs fsck / -deleteDataNode 磁盘满
bash
# 查看各节点磁盘使用
hdfs dfsadmin -report
# 清理回收站
hdfs dfs -expunge