跳到主要内容

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记录对文件系统的所有修改操作

元数据更新流程:

  1. 客户端发起写操作请求
  2. NameNode 先将操作记录到 EditLog
  3. 更新内存中的元数据
  4. 返回成功响应给客户端

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。

主要职责

  1. 定期合并 FsImage 和 EditLog
  2. 创建检查点,加快 NameNode 启动
  3. 提供元数据冷备份

工作流程

检查点配置

<!-- 检查点触发条件: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 副本):

放置规则:

  1. 第一个副本:优先放在客户端所在节点
  2. 第二个副本:放在不同机架的节点
  3. 第三个副本:放在第二个副本同机架的不同节点

策略优势:

  • 机架级容错:一个机架故障不会丢失数据
  • 读性能优化:可以从最近的副本读取
  • 写性能优化:利用多机架的网络带宽

副本状态

状态说明
FINALIZED已完成写入,数据完整
RBW(Replica Being Written)正在写入
RWR(Replica Waiting to be Recovered)等待恢复
RUR(Replica Under Recovery)正在恢复
TEMPORARY临时副本

HDFS 读写流程详解

文件读取流程

详细步骤:

  1. 打开文件:客户端调用 FileSystem.open() 方法,请求 NameNode 获取文件信息。

  2. 获取块位置:NameNode 返回文件的数据块列表,每个块包含存储该块的 DataNode 地址列表,按距离排序。

  3. 建立连接:客户端选择最近的 DataNode(通常是同一机架或同一节点),建立 TCP 连接。

  4. 读取数据:DataNode 将数据流式传输给客户端,客户端将数据写入本地缓冲区。

  5. 继续读取:当前块读取完成后,客户端连接下一个块的 DataNode 继续读取。

  6. 关闭连接:所有数据读取完成后,客户端调用 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();

文件写入流程

详细步骤:

  1. 创建文件:客户端调用 FileSystem.create() 方法,请求 NameNode 创建文件。

  2. 检查权限:NameNode 检查客户端是否有创建权限,检查父目录是否存在。

  3. 返回 DataNode 列表:NameNode 根据副本放置策略,返回可写入的 DataNode 列表。

  4. 建立管道:客户端与第一个 DataNode 建立连接,第一个 DataNode 与第二个建立连接,依次形成管道。

  5. 写入数据:客户端将数据分成数据包(默认 64KB),依次发送到管道。

  6. 确认写入:所有副本写入完成后,最后一个 DataNode 返回确认,依次向前传递。

  7. 关闭文件:数据写入完成后,客户端调用 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 的核心概念:

  1. 架构设计:主从架构,NameNode 管理元数据,DataNode 存储数据。

  2. 数据块与副本:文件切分成块存储,多副本保证可靠性。

  3. 读写流程:理解数据如何在客户端、NameNode、DataNode 之间流动。

  4. 安全模式:启动时的保护机制,确保数据完整性。

下一章将介绍 HDFS 的命令操作,学习如何使用命令行管理 HDFS 文件系统。