2.2 HDFS 数据存储与管理 第二章:Hadoop 分布式文件系统 HDFS (Hadoop Distributed File System) 2.2 HDFS 数据存储与管理 2.2.1 HDFS 数据存储核心概念 理解 HDFS 的数据存储与管理,首先需要掌握几个核心概念: 数据块 (Block): HDFS 并非将整个文件存储在一个节点上,而是将文件分割成多个固定大小的数据块 (Block) 进行存储。默认情况下,HDFS 的块大小为 128MB (Hadoop 2.x 及更高版本)。将文件分割成块有以下优势: 简化数据管理: 数据块是 HDFS 存储和管理的最小单元。管理固定大小的块比管理大小不一的文件更简单高效。
2.2.1 HDFS 数据存储核心概念
理解 HDFS 的数据存储与管理,首先需要掌握几个核心概念:
数据块 (Block): HDFS 并非将整个文件存储在一个节点上,而是将文件分割成多个固定大小的数据块 (Block) 进行存储。默认情况下,HDFS 的块大小为 128MB (Hadoop 2.x 及更高版本)。将文件分割成块有以下优势:
简化数据管理: 数据块是 HDFS 存储和管理的最小单元。管理固定大小的块比管理大小不一的文件更简单高效。
提高数据并行度: 由于文件被分割成多个块,这些块可以分布在集群的不同节点上,从而在数据访问时实现并行处理,提高数据读取速度。
容错和数据恢复: 数据块的冗余备份 (副本) 存储在不同的节点上,当某个节点发生故障时,系统可以从其他节点获取数据块的副本,保证数据的可靠性和可用性。
NameNode (名称节点): NameNode 是 HDFS 的核心组件,负责管理文件系统的命名空间和元数据。元数据包括:
文件和目录的结构信息: 文件和目录的层次结构、名称、权限等。
文件块的映射信息: 记录每个文件被分割成哪些块,以及这些块存储在哪些 DataNode 上。
NameNode 本身并不存储实际的数据块,它只存储元数据信息。客户端对 HDFS 的操作 (如创建文件、读取文件、删除文件等) 都需要先与 NameNode 交互,获取元数据信息。NameNode 是 HDFS 集群的单点故障,因此 HDFS 通常会配置 Secondary NameNode 或 HA (High Availability) 机制来提高 NameNode 的可靠性。
DataNode (数据节点): DataNode 是 HDFS 集群中的工作节点,负责实际存储数据块。集群中可以有多个 DataNode,每个 DataNode 存储一部分数据块。DataNode 还会定期向 NameNode 发送心跳信息和块报告,汇报自身状态和所存储的数据块信息。NameNode 通过心跳信息监控 DataNode 的健康状况,通过块报告了解数据块的存储位置。
副本 (Replication): 为了保证数据的可靠性和容错性,HDFS 会将每个数据块复制多份,并将这些副本存储在不同的 DataNode 上。副本的数量称为副本因子 (Replication Factor),默认情况下为 3。这意味着每个数据块都会有三个副本,分别存储在不同的 DataNode 上。当某个 DataNode 发生故障导致数据块丢失时,系统可以从其他 DataNode 获取副本,保证数据的可用性。副本机制是 HDFS 实现高可靠性的关键。
数据本地性 (Data Locality): HDFS 尽量将计算任务分配到存储数据块的 DataNode 上执行,以减少数据在网络上的传输,提高数据处理效率。这就是数据本地性原则。当客户端请求读取数据时,NameNode 会告知客户端数据块所在的 DataNode 位置,客户端会尽可能选择靠近数据块的 DataNode 进行数据读取,或者直接在 DataNode 上启动计算任务。
2.2.2 HDFS 数据存储架构
HDFS 的数据存储架构可以用下图来表示:
架构说明:
客户端 (Client): 用户通过客户端与 HDFS 交互,提交文件操作请求。
NameNode: 接收客户端请求,管理元数据,并返回数据块位置信息给客户端。
DataNode: 存储实际数据块,响应客户端的数据读写请求,并定期向 NameNode 汇报状态。
数据流: 客户端与 DataNode 直接进行数据传输,NameNode 只参与元数据管理,不参与数据传输,减轻了 NameNode 的负载,提高了系统性能。
2.2.3 HDFS 数据写入过程
当客户端向 HDFS 写入数据时,数据写入过程如下:
写入过程详解:
客户端向 NameNode 发送文件写入请求。
NameNode 检查权限和目录结构,如果通过,则返回可用的 DataNode 列表 (通常为 3 个,根据副本因子决定)。 NameNode 会根据一定的策略选择 DataNode,例如考虑 DataNode 的磁盘空间、负载均衡等。
客户端首先将数据写入第一个 DataNode (DataNode1)。 数据以流式方式写入,通常会先写入 DataNode 的本地磁盘缓存。
DataNode1 接收到数据块后,将数据块复制到第二个 DataNode (DataNode2)。 这个复制过程是在 DataNode 之间以管道方式进行的,DataNode1 在接收数据的同时,也将其转发给 DataNode2。
DataNode2 接收到数据块后,再将其复制到第三个 DataNode (DataNode3)。 同样以管道方式进行。
当所有副本都写入成功后,最后一个 DataNode (DataNode3) 向前一个 DataNode (DataNode2) 发送确认信息,DataNode2 再向 DataNode1 发送确认信息,最终 DataNode1 向客户端发送数据块写入成功确认。 这种链式确认机制保证了数据写入的可靠性。
客户端收到所有数据块的写入成功确认后,向 NameNode 告知文件写入完成。
NameNode 更新元数据,记录文件和数据块的映射关系,以及数据块的存储位置。
2.2.4 HDFS 数据读取过程
当客户端从 HDFS 读取数据时,数据读取过程如下:
读取过程详解:
客户端向 NameNode 发送文件读取请求。
NameNode 检查权限和文件是否存在,如果通过,则返回文件数据块的位置列表 (DataNode 列表)。 NameNode 会根据数据本地性原则,优先返回距离客户端较近的 DataNode 列表。
客户端根据返回的数据块位置列表,直接向 DataNode 发送数据块读取请求。 客户端会选择一个最佳的 DataNode (例如,网络距离最近或负载最低的 DataNode) 进行读取。
DataNode 从本地磁盘读取数据块,并将数据返回给客户端。
客户端接收到所有数据块后,将数据块组合成完整的文件。
2.2.5 HDFS 数据管理操作与代码实践
HDFS 提供了丰富的命令行工具和 Java API 用于数据管理操作。以下将介绍常用的操作及代码实践。
2.2.5.1 命令行操作 (Hadoop CLI)
Hadoop 提供了一套命令行工具 hdfs dfs (或 hadoop fs) 用于与 HDFS 交互。常用的命令包括:
创建目录: hdfs dfs -mkdir <目录路径>
hdfs dfs -mkdir /user/hadoop/input 在 HDFS 根目录下创建 /user/hadoop/input 目录。上传文件: hdfs dfs -put <本地文件路径> <HDFS 目录路径> 或 hdfs dfs -copyFromLocal <本地文件路径> <HDFS 目录路径>
hdfs dfs -put localfile.txt /user/hadoop/input 将本地文件 localfile.txt 上传到 HDFS 的 /user/hadoop/input 目录。下载文件: hdfs dfs -get <HDFS 文件路径> <本地目录路径> 或 hdfs dfs -copyToLocal <HDFS 文件路径> <本地目录路径>
hdfs dfs -get /user/hadoop/output/part-r-00000 localdir 将 HDFS 文件 /user/hadoop/output/part-r-00000 下载到本地目录 localdir。查看目录列表: hdfs dfs -ls <HDFS 目录路径>
hdfs dfs -ls /user/hadoop/input 列出 HDFS 目录 /user/hadoop/input 下的文件和目录。查看文件内容: hdfs dfs -cat <HDFS 文件路径>
hdfs dfs -cat /user/hadoop/input/localfile.txt 查看 HDFS 文件 /user/hadoop/input/localfile.txt 的内容。删除文件或目录: hdfs dfs -rm <HDFS 文件或目录路径> 或 hdfs dfs -rm -r <HDFS 目录路径> (递归删除目录及其内容)
例如:hdfs dfs -rm /user/hadoop/input/localfile.txt 删除 HDFS 文件 /user/hadoop/input/localfile.txt。
例如:hdfs dfs -rm -r /user/hadoop/output 递归删除 HDFS 目录 /user/hadoop/output 及其所有内容。
复制文件: hdfs dfs -cp <源 HDFS 路径> <目标 HDFS 路径>
hdfs dfs -cp /user/hadoop/input/file1.txt /user/hadoop/backup/file1.txt 将 HDFS 文件 /user/hadoop/input/file1.txt 复制到 /user/hadoop/backup/file1.txt。移动文件或目录: hdfs dfs -mv <源 HDFS 路径> <目标 HDFS 路径>
hdfs dfs -mv /user/hadoop/input/file1.txt /user/hadoop/processed/file1.txt 将 HDFS 文件 /user/hadoop/input/file1.txt 移动到 /user/hadoop/processed/file1.txt。查看文件大小: hdfs dfs -du -h <HDFS 路径> (以人类可读的方式显示文件大小)
hdfs dfs -du -h /user/hadoop/input 查看 HDFS 目录 /user/hadoop/input 及其包含内容的大小。代码实践 (命令行操作):
假设 Hadoop 集群已启动,并且配置了 Hadoop 环境变量。打开终端,执行以下命令:
创建 HDFS 目录:
hdfs dfs -mkdir /user/test hdfs dfs -ls /user
执行后,应该能看到 /user 目录下新增了 test 目录。
上传本地文件到 HDFS:
先在本地创建一个文本文件 test.txt,内容随意。
echo "Hello HDFS!" > test.txt hdfs dfs -put test.txt /user/test hdfs dfs -ls /user/test
执行后,应该能看到 /user/test 目录下上传了 test.txt 文件。
查看 HDFS 文件内容:
hdfs dfs -cat /user/test/test.txt
执行后,应该在终端输出 "Hello HDFS!"。
下载 HDFS 文件到本地:
hdfs dfs -get /user/test/test.txt ./downloaded_file.txt ls cat downloaded_file.txt
执行后,本地目录应该会生成 downloaded_file.txt 文件,内容与 test.txt 一致。
删除 HDFS 文件和目录:
hdfs dfs -rm /user/test/test.txt hdfs dfs -rmdir /user/test hdfs dfs -ls /user
执行后,/user/test 目录应该被删除。
2.2.5.2 Java API 操作 (Hadoop FileSystem API)
Hadoop 提供了 Java API org.apache.hadoop.fs.FileSystem 用于程序化地访问 HDFS。以下代码示例演示了常用的 HDFS 操作:
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FSDataInputStream; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; public class HDFSExample { public static void main(String[] args) throws IOException { Configuration conf = new Configuration(); // 加载 Hadoop 配置 FileSystem fs = FileSystem.get(conf); // 获取 FileSystem 实例 // 1. 创建目录 Path dirPath = new Path("/user/java_test"); if (!fs.exists(dirPath)) { fs.mkdirs(dirPath); System.out.println("Directory created: " + dirPath); } // 2. 上传文件 Path localFilePath = new Path("local_file.txt"); // 假设本地存在 local_file.txt Path hdfsFilePath = new Path("/user/java_test/uploaded_file.txt"); fs.copyFromLocalFile(localFilePath, hdfsFilePath); System.out.println("File uploaded from " + localFilePath + " to " + hdfsFilePath); // 3. 读取文件内容 Path readFile = new Path("/user/java_test/uploaded_file.txt"); try (FSDataInputStream inputStream = fs.open(readFile); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { String line; System.out.println("File content:"); while ((line = reader.readLine()) != null) { System.out.println(line); } } // 4. 写入文件 Path writeFile = new Path("/user/java_test/new_file.txt"); try (FSDataOutputStream outputStream = fs.create(writeFile)) { outputStream.writeBytes("This is written by Java API.\n"); outputStream.writeBytes("Another line.\n"); System.out.println("File written to " + writeFile); } // 5. 下载文件 Path downloadHDFSPath = new Path("/user/java_test/new_file.txt"); Path localDownloadPath = new Path("downloaded_java_file.txt"); fs.copyToLocalFile(false, downloadHDFSPath, localDownloadPath, true); // 第一个参数 false 表示不删除源文件,第四个参数 true 表示覆盖本地文件 System.out.println("File downloaded from " + downloadHDFSPath + " to " + localDownloadPath); // 6. 删除文件和目录 Path deleteFilePath = new Path("/user/java_test/uploaded_file.txt"); fs.delete(deleteFilePath, false); // false 表示不递归删除目录 System.out.println("File deleted: " + deleteFilePath); Path deleteDirPath = new Path("/user/java_test"); fs.delete(deleteDirPath, true); // true 表示递归删除目录及其内容 System.out.println("Directory deleted: " + deleteDirPath); fs.close(); // 关闭 FileSystem 连接 } }
代码实践 (Java API):
准备环境: 确保你的 Java 开发环境已配置好 Hadoop 客户端依赖 (例如,在 Maven 或 Gradle 项目中添加 Hadoop 客户端依赖)。
创建本地文件: 在代码的同目录下创建 local_file.txt 文件,内容随意。
运行代码: 编译并运行 HDFSExample.java 程序。
观察结果: 程序将会在 HDFS 上创建目录、上传文件、读取文件内容、写入文件、下载文件、删除文件和目录,并在控制台输出相应的操作信息。
注意:
在运行 Java API 代码之前,需要确保 Hadoop 集群已启动,并且客户端能够连接到 NameNode。
Configuration conf = new Configuration(); 会加载 Hadoop 默认配置文件 (如 core-site.xml, hdfs-site.xml),这些配置文件通常包含了 NameNode 的地址等信息。如果需要连接到特定的 Hadoop 集群,需要修改这些配置文件或通过代码配置 Configuration 对象。
异常处理 (例如 IOException) 在实际开发中需要更加完善。