HDFS 基础
HDFS(Hadoop Distributed File System)是 Hadoop 的分布式文件存储系统,是大数据存储的核心基础设施。本章将深入讲解 HDFS 的架构原理和核心概念。
HDFS 设计目标
HDFS 的设计目标源于大规模数据处理的需求:
适合大文件存储
HDFS 设计用于存储 GB 到 TB 级别的大文件。对于小文件,HDFS 的效率较低,因为每个文件都会在 NameNode 中占用元数据空间。
为什么适合大文件?
- 减少元数据占用:一个大文件只需要少量元数据
- 提高传输效率:大块数据可以充分利用网络带宽
- 降低寻址开销:减少客户端与 NameNode 的交互次数
流式数据访问
HDFS 采用"一次写入,多次读取"的数据访问模式,适合批量读取和分析场景。
流式访问的特点:
- 数据写入后很少修改,主要是读取操作
- 关注数据访问的吞吐量,而非响应延迟
- 适合离线分析、数据挖掘等场景
商用硬件
HDFS 设计运行在普通商用服务器上,通过软件层面的容错机制保证数据可靠性。
商用硬件的优势:
- 成本低:无需购买昂贵的高端服务器
- 可扩展:可以轻松添加新节点扩展集群
- 容错设计:假设硬件会故障,通过副本机制保证数据安全
高容错性
HDFS 通过多种机制保证数据可靠性:
- 数据多副本:默认 3 个副本
- 自动故障检测:通过心跳机制监控节点状态
- 自动恢复:检测到故障后自动复制副本
HDFS 架构原理
主从架构
HDFS 采用主从架构(Master-Slave),由一个 NameNode 和多个 DataNode 组成:
NameNode 详解
NameNode 是 HDFS 的主节点,是整个文件系统的核心。
元数据管理
NameNode 管理的元数据包括:
| 元数据类型 | 说明 |
|---|---|
| 文件名和目录结构 | 整个文件系统的目录树 |
| 文件属性 | 权限、修改时间、副本数等 |
| 文件块映射 | 每个文件由哪些数据块组成 |
| 数据块位置 | 每个数据块存储在哪些 DataNode |
元数据存储
NameNode 的元数据存储在内存和磁盘两个地方:
内存存储:
元数据加载到内存中,提供快速访问。这也是 NameNode 内存需求大的原因。
磁盘存储:
| 文件 | 说明 |
|---|---|
| FsImage | 命名空间的完整快照,包含所有文件和目录的元数据 |
| EditLog | 记录对文件系统的所有修改操作 |
元数据更新流程:
- 客户端发起写操作请求
- NameNode 先将操作记录到 EditLog
- 更新内存中的元数据
- 返回成功响应给客户端
FsImage 和 EditLog 的合并
随着时间推移,EditLog 会越来越大,需要定期与 FsImage 合并:
这个合并过程由 Secondary NameNode 或 Standby NameNode 完成。
DataNode 详解
DataNode 是 HDFS 的工作节点,负责实际的数据存储。
数据块存储
DataNode 将文件切分成固定大小的块存储:
文件大小:300MB
块大小:128MB
文件被切分为:
- Block 1: 0-128MB
- Block 2: 128-256MB
- Block 3: 256-300MB(实际 44MB)
心跳机制
DataNode 定期向 NameNode 发送心跳和块报告:
| 报告类型 | 频率 | 内容 |
|---|---|---|
| 心跳 | 3 秒 | 节点存活状态 |
| 块报告 | 1 小时 | 存储的所有数据块信息 |
| 增量块报告 | 即时 | 数据块变化信息 |
如果 NameNode 超过一定时间(默认 10 分钟)没有收到心跳,则认为该 DataNode 不可用。
数据块校验
DataNode 为每个数据块维护校验和,用于检测数据损坏:
- 写入数据时计算校验和
- 读取数据时验证校验和
- 发现损坏时报告 NameNode,从其他副本恢复
Secondary NameNode 详解
Secondary NameNode 不是 NameNode 的热备,它的主要作用是辅助 NameNode。
主要职责
- 定期合并 FsImage 和 EditLog
- 创建检查点,加快 NameNode 启动
- 提供元数据冷备份
工作流程
检查点配置
<!-- 检查点触发条件:EditLog 达到指定大小 -->
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
</property>
<!-- 检查点触发条件:时间间隔 -->
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
数据块与副本机制
数据块(Block)
HDFS 将文件切分成固定大小的块进行存储。
块大小配置
<!-- 数据块大小,默认 128MB(单位:字节) -->
<property>
<name>dfs.blocksize</name>
<value>134217728</value>
</property>
块大小选择原则
| 场景 | 建议块大小 | 原因 |
|---|---|---|
| 大文件分析 | 128MB 或 256MB | 减少元数据,提高传输效率 |
| 小文件较多 | 64MB | 减少空间浪费 |
| 高吞吐量场景 | 256MB | 更好利用网络带宽 |
块大小对性能的影响
块太小的问题:
- NameNode 元数据占用过多内存
- 客户端需要频繁请求块位置
- 增加网络开销
块太大的问题:
- MapReduce 任务并行度降低
- 数据本地性变差
- 小文件空间浪费严重
副本机制
HDFS 通过多副本机制保证数据可靠性。
副本数配置
<!-- 默认副本数 -->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<!-- 最小副本数(用于安全模式判断) -->
<property>
<name>dfs.namenode.replication.min</name>
<value>1</value>
</property>
副本放置策略
副本放置策略影响数据可靠性和访问性能:
默认策略(3 副本):
放置规则:
- 第一个副本:优先放在客户端所在节点
- 第二个副本:放在不同机架的节点
- 第三个副本:放在第二个副本同机架的不同节点
策略优势:
- 机架级容错:一个机架故障不会丢失数据
- 读性能优化:可以从最近的副本读取
- 写性能优化:利用多机架的网络带宽
副本状态
| 状态 | 说明 |
|---|---|
| FINALIZED | 已完成写入,数据完整 |
| RBW(Replica Being Written) | 正在写入 |
| RWR(Replica Waiting to be Recovered) | 等待恢复 |
| RUR(Replica Under Recovery) | 正在恢复 |
| TEMPORARY | 临时副本 |
HDFS 读写流程详解
文件读取流程
详细步骤:
-
打开文件:客户端调用
FileSystem.open()方法,请求 NameNode 获取文件信息。 -
获取块位置:NameNode 返回文件的数据块列表,每个块包含存储该块的 DataNode 地址列表,按距离排序。
-
建立连接:客户端选择最近的 DataNode(通常是同一机架或同一节点),建立 TCP 连接。
-
读取数据:DataNode 将数据流式传输给客户端,客户端将数据写入本地缓冲区。
-
继续读取:当前块读取完成后,客户端连接下一个块的 DataNode 继续读取。
-
关闭连接:所有数据读取完成后,客户端调用
close()方法关闭连接。
代码示例:
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path filePath = new Path("/user/data/example.txt");
FSDataInputStream inputStream = fs.open(filePath);
BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
fs.close();
文件写入流程
详细步骤:
-
创建文件:客户端调用
FileSystem.create()方法,请求 NameNode 创建文件。 -
检查权限:NameNode 检查客户端是否有创建权限,检查父目录是否存在。
-
返回 DataNode 列表:NameNode 根据副本放置策略,返回可写入的 DataNode 列表。
-
建立管道:客户端与第一个 DataNode 建立连接,第一个 DataNode 与第二个建立连接,依次形成管道。
-
写入数据:客户端将数据分成数据包(默认 64KB),依次发送到管道。
-
确认写入:所有副本写入完成后,最后一个 DataNode 返回确认,依次向前传递。
-
关闭文件:数据写入完成后,客户端调用
close()方法关闭文件。
代码示例:
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path filePath = new Path("/user/data/output.txt");
FSDataOutputStream outputStream = fs.create(filePath);
String content = "Hello, HDFS!\n";
outputStream.write(content.getBytes());
outputStream.close();
fs.close();
数据流管道
写入数据时,数据通过管道方式依次流经各个 DataNode:
客户端 -> DataNode1 -> DataNode2 -> DataNode3
管道的优势:
- 减少客户端网络负载:客户端只需发送一份数据
- 提高写入效率:并行写入多个副本
- 自动故障处理:某个 DataNode 故障时自动重试
HDFS 安全模式
什么是安全模式?
安全模式是 HDFS 的一种特殊状态,在此状态下:
- 文件系统只读,不接受写操作
- 不进行副本复制或删除
- 等待数据块达到最小副本数
安全模式触发条件
HDFS 启动时自动进入安全模式,等待:
- 所有 DataNode 汇报块报告
- 数据块达到最小副本数比例
安全模式操作
# 查看安全模式状态
hdfs dfsadmin -safemode get
# 手动进入安全模式
hdfs dfsadmin -safemode enter
# 手动离开安全模式
hdfs dfsadmin -safemode leave
# 等待离开安全模式
hdfs dfsadmin -safemode wait
安全模式配置
<!-- 最小副本数比例(达到此比例才能离开安全模式) -->
<property>
<name>dfs.namenode.safemode.threshold-pct</name>
<value>0.999f</value>
</property>
<!-- 安全模式最小存活 DataNode 数量 -->
<property>
<name>dfs.namenode.safemode.min.datanodes</name>
<value>0</value>
</property>
<!-- 安全模式等待时间(毫秒) -->
<property>
<name>dfs.namenode.safemode.extension</name>
<value>30000</value>
</property>
小结
本章深入讲解了 HDFS 的核心概念:
-
架构设计:主从架构,NameNode 管理元数据,DataNode 存储数据。
-
数据块与副本:文件切分成块存储,多副本保证可靠性。
-
读写流程:理解数据如何在客户端、NameNode、DataNode 之间流动。
-
安全模式:启动时的保护机制,确保数据完整性。
下一章将介绍 HDFS 的命令操作,学习如何使用命令行管理 HDFS 文件系统。