2.1 HDFS 架构与组件


文档摘要

2.1 HDFS 架构与组件 第二章:Hadoop 分布式文件系统 HDFS (Hadoop Distributed File System) 2.1 HDFS 架构与组件 2.1.1 HDFS 架构概述 HDFS 采用主/从架构 (Master/Slave Architecture),主要由以下两种类型的节点构成: NameNode (NN):作为主节点 (Master),负责管理文件系统的命名空间 (Namespace) 和元数据 (Metadata)。它维护着文件系统的目录树结构以及所有文件和目录的元数据信息,例如文件权限、副本数量、数据块的位置信息等。NameNode 并不实际存储数据块,而是记录数据块存储在哪些 DataNode 上。

2.1 HDFS 架构与组件

第二章:Hadoop 分布式文件系统 HDFS (Hadoop Distributed File System)

2.1 HDFS 架构与组件

2.1.1 HDFS 架构概述

HDFS 采用主/从架构 (Master/Slave Architecture),主要由以下两种类型的节点构成:

  • NameNode (NN):作为主节点 (Master),负责管理文件系统的命名空间 (Namespace) 和元数据 (Metadata)。它维护着文件系统的目录树结构以及所有文件和目录的元数据信息,例如文件权限、副本数量、数据块的位置信息等。NameNode 并不实际存储数据块,而是记录数据块存储在哪些 DataNode 上。

  • DataNode (DN):作为从节点 (Slave),负责存储实际的数据块。它接收来自客户端或 NameNode 的指令,执行数据块的读写操作,并定期向 NameNode 汇报自身状态和所存储的数据块信息。

除了 NameNode 和 DataNode,HDFS 架构中还包含客户端 (Client),客户端代表用户应用程序,通过与 NameNode 和 DataNode 交互来访问 HDFS 文件系统。

可以用 Mermaid 的 graph TD 图直观地展示 HDFS 的基本架构:

架构特点总结:

  • 主/从架构: NameNode 作为管理者,DataNode 作为执行者。

  • 元数据与数据分离: NameNode 管理元数据,DataNode 存储数据。

  • 分布式存储: 数据分散存储在多个 DataNode 上。

  • 高容错性: 通过数据副本机制保证数据可靠性。

  • 高吞吐量: 通过并行读写数据块提高数据访问速度。

  • 可扩展性: 易于通过增加 DataNode 节点来扩展存储容量和性能。

2.1.2 核心组件详解

接下来,我们将详细介绍 HDFS 架构中的核心组件,包括 NameNode、DataNode、Secondary NameNode(以及高可用架构下的 Standby NameNode)和 Client。

2.1.2.1 NameNode (NN)

NameNode 是 HDFS 的心脏,是整个文件系统的管理中心。它的主要职责包括:

  • 命名空间管理 (Namespace Management):NameNode 维护着文件系统的目录树结构,记录着所有的文件和目录信息,包括它们的层级关系、名称、权限、时间戳等元数据。客户端对文件系统的任何操作,例如创建、删除、重命名文件或目录,都需要经过 NameNode 的处理。

  • 元数据管理 (Metadata Management):NameNode 负责管理所有文件和目录的元数据信息。这些元数据主要包括:

    • 文件和目录的属性: 例如所有者、权限、创建时间等。

    • 数据块的位置信息 (Block Locations):记录着每个文件被分割成哪些数据块,以及这些数据块存储在哪些 DataNode 上。NameNode 并不存储实际的数据块,而是维护着数据块到 DataNode 的映射关系。

  • 客户端请求处理: NameNode 接收来自客户端的请求,例如文件读写请求、元数据操作请求等。对于读写请求,NameNode 会根据元数据信息,指示客户端与相应的 DataNode 进行数据交互。对于元数据操作请求,NameNode 会更新内存中的元数据信息,并将其持久化到磁盘。

  • 数据块管理: NameNode 负责管理数据块的生命周期,包括:

    • 数据块分配: 当客户端创建新文件时,NameNode 会根据策略选择 DataNode 存储数据块。

    • 数据块复制: 为了保证数据可靠性,HDFS 会将数据块复制多份存储在不同的 DataNode 上。NameNode 负责监控数据块的副本数量,并在副本数量不足时启动副本复制操作。

    • 数据块回收: 当文件被删除时,NameNode 会通知 DataNode 删除相应的数据块。

  • 心跳机制 (Heartbeat):NameNode 定期接收来自 DataNode 的心跳信息。心跳信息包含 DataNode 的状态、可用空间、存储的数据块信息等。通过心跳机制,NameNode 可以监控 DataNode 的健康状况,及时发现故障 DataNode,并采取相应的措施,例如数据副本的重新复制。

NameNode 的元数据存储:

为了保证元数据的持久性和可靠性,NameNode 将元数据存储在磁盘上,主要包括以下两个文件:

  • FSImage (文件系统镜像):FSImage 是文件系统元数据在某一时刻的完整快照。它包含了文件系统的目录树结构以及所有文件和目录的元数据信息。FSImage 文件在 NameNode 启动时被加载到内存中,作为内存元数据的初始状态。

  • EditLog (编辑日志):EditLog 记录了文件系统元数据的变更操作日志。每次客户端对文件系统进行写操作 (例如创建、删除、修改文件或目录) 时,NameNode 会将这些操作记录到 EditLog 文件中。EditLog 文件是顺序写入的,追加速度快。

NameNode 启动时,会先加载 FSImage 文件到内存,然后重放 EditLog 文件中的操作日志,将内存中的元数据恢复到最新的状态。为了防止 EditLog 文件过大,NameNode 会定期进行 Checkpoint 操作,将 EditLog 文件中的操作合并到 FSImage 文件中,生成一个新的 FSImage 文件,并清空 EditLog 文件。Checkpoint 操作通常由 Secondary NameNode 或者 Standby NameNode (在高可用架构中) 辅助完成。

NameNode 的局限性:

  • 单点故障 (Single Point of Failure):在传统的 HDFS 架构中,只有一个 NameNode 节点。如果 NameNode 节点发生故障,整个 HDFS 集群将不可用,直到 NameNode 恢复正常。

  • 内存瓶颈: NameNode 需要将所有的元数据信息加载到内存中,这限制了 NameNode 所能管理的元数据规模,从而限制了 HDFS 集群的规模。

为了解决 NameNode 的单点故障问题和扩展性问题,Hadoop 引入了 HDFS 高可用 (High Availability, HA) 架构和联邦 (Federation) 架构。在高可用架构中,通常会配置一对 Active/Standby NameNode,当 Active NameNode 发生故障时,Standby NameNode 会自动切换为 Active 状态,保证 HDFS 集群的持续可用性。联邦架构则允许多个 NameNode 共同管理一个 HDFS 集群的命名空间,从而提高集群的扩展性和性能。

2.1.2.2 DataNode (DN)

DataNode 是 HDFS 中的工作节点,负责存储实际的数据块。一个 HDFS 集群通常包含多个 DataNode 节点,数据块被分布存储在这些 DataNode 上。DataNode 的主要职责包括:

  • 数据块存储 (Block Storage):DataNode 将数据块以文件的形式存储在本地磁盘上。每个数据块都有一个唯一的 Block ID。DataNode 按照一定的目录结构组织存储数据块文件。

  • 数据块读写 (Block Read/Write):DataNode 响应来自客户端或 NameNode 的读写请求,执行数据块的读写操作。客户端可以直接与 DataNode 进行数据交互,读取或写入数据块。

  • 数据块复制 (Block Replication):DataNode 参与数据块的复制过程。当 NameNode 指示需要复制某个数据块时,源 DataNode 会将数据块复制到目标 DataNode。DataNode 之间通过流式复制 (Streaming Replication) 的方式进行数据传输。

  • 数据块校验和 (Checksum):DataNode 负责维护数据块的校验和信息,用于检测数据块的完整性。在数据读取过程中,DataNode 会校验数据块的校验和,如果校验和不匹配,则表示数据块可能损坏,DataNode 会向 NameNode 报告错误,并尝试从其他副本读取数据。

  • 心跳和块报告 (Heartbeat and Block Report):DataNode 定期向 NameNode 发送心跳信息,汇报自身状态,例如可用空间、运行状态等。DataNode 还会定期向 NameNode 发送块报告 (Block Report),汇报自身存储的数据块信息,包括 Block ID 和数据块大小等。NameNode 通过块报告了解集群中数据块的分布情况。

DataNode 的数据存储结构:

DataNode 在本地磁盘上会创建多个目录来存储数据块文件。这些目录通常由 dfs.data.dir 属性配置。DataNode 会根据一定的策略将数据块文件分散存储在这些目录下,以平衡磁盘 I/O 负载。

每个数据块文件通常包含两个文件:

  • 数据文件 (Data File):存储实际的数据块内容。

  • 元数据文件 (Metadata File):存储数据块的元数据信息,例如校验和信息。

DataNode 的数量和容量:

HDFS 集群的存储容量和吞吐量主要取决于 DataNode 的数量和容量。可以通过增加 DataNode 节点来扩展 HDFS 集群的存储容量和性能。DataNode 通常部署在廉价的商用硬件上,可以方便地进行横向扩展。

2.1.2.3 Secondary NameNode (SNN)

在传统的 HDFS 架构中,Secondary NameNode 并非 NameNode 的热备节点,它主要用于辅助 NameNode 进行 Checkpoint 操作,帮助 NameNode 合并 EditLog 和 FSImage,减少 NameNode 启动恢复时间,并为 NameNode 提供冷备。

Secondary NameNode 的主要职责包括:

  • Checkpoint 操作: Secondary NameNode 定期从 NameNode 获取 EditLog 和 FSImage 文件,将 EditLog 文件中的操作合并到 FSImage 文件中,生成一个新的 FSImage 文件 (称为 Checkpoint FSImage)。然后,Secondary NameNode 将新的 FSImage 文件发送回 NameNode,并清空自己的 EditLog 文件。NameNode 在下次重启时,可以使用最新的 FSImage 文件,从而减少启动恢复时间。

  • EditLog 备份: Secondary NameNode 可以定期从 NameNode 获取 EditLog 文件进行备份,以防止 EditLog 文件丢失导致数据丢失。

Secondary NameNode 的工作流程:

  1. 定期请求: Secondary NameNode 定期向 NameNode 发送 Checkpoint 请求。

  2. EditLog 和 FSImage 获取: NameNode 收到请求后,会滚动当前的 EditLog 文件,生成一个新的 EditLog 文件,并将当前的 FSImage 文件和滚动前的 EditLog 文件发送给 Secondary NameNode。

  3. Checkpoint 合并: Secondary NameNode 将接收到的 EditLog 文件中的操作合并到 FSImage 文件中,生成一个新的 Checkpoint FSImage 文件。

  4. Checkpoint FSImage 回传: Secondary NameNode 将新的 Checkpoint FSImage 文件发送回 NameNode。

  5. 替换和清空: NameNode 使用接收到的 Checkpoint FSImage 文件替换旧的 FSImage 文件。

  6. EditLog 清空: NameNode 清空滚动前的 EditLog 文件,开始使用新的 EditLog 文件记录后续操作。

Secondary NameNode 的局限性:

  • 并非热备: Secondary NameNode 并非 NameNode 的热备节点,当 NameNode 发生故障时,Secondary NameNode 不能自动接管 NameNode 的工作。

  • 数据可能丢失: 在 NameNode 发生故障到恢复期间,如果 EditLog 文件丢失或者损坏,可能会导致部分元数据操作丢失。

在高可用 (HA) 架构中,Secondary NameNode 被 Standby NameNode 取代。Standby NameNode 是 NameNode 的热备节点,可以实时同步 Active NameNode 的元数据信息,并在 Active NameNode 发生故障时自动切换为 Active 状态,保证 HDFS 集群的持续可用性。

2.1.2.4 Client (客户端)

客户端是用户应用程序与 HDFS 集群交互的接口。客户端可以是运行在集群外部的应用程序,也可以是运行在集群内部的应用程序。客户端的主要职责包括:

  • 文件操作: 客户端提供了一系列 API,用于对 HDFS 文件系统进行各种操作,例如:

    • 文件创建 (Create):创建新文件。

    • 文件读取 (Read):读取文件内容。

    • 文件写入 (Write):写入文件内容。

    • 文件删除 (Delete):删除文件。

    • 文件重命名 (Rename):重命名文件。

    • 目录创建 (Mkdir):创建目录。

    • 目录删除 (Rmdir):删除目录。

    • 文件列表 (List):列出目录下的文件和目录。

    • 文件属性查看 (Get File Status):查看文件或目录的属性信息。

  • 与 NameNode 交互: 客户端首先与 NameNode 建立连接,发送元数据操作请求,例如文件创建、文件打开、文件列表等。NameNode 接收到请求后,会根据元数据信息进行处理,并返回相应的响应,例如文件的数据块位置信息。

  • 与 DataNode 交互: 对于数据读写请求,客户端会根据 NameNode 返回的数据块位置信息,直接与相应的 DataNode 建立连接,进行数据传输。客户端将数据写入 DataNode,或者从 DataNode 读取数据。客户端通常会并行地与多个 DataNode 建立连接,以提高数据传输的吞吐量。

  • 数据切分和组装: 当客户端写入大文件时,客户端会将文件切分成多个数据块,然后将这些数据块分别写入不同的 DataNode。当客户端读取大文件时,客户端会从多个 DataNode 并行读取数据块,然后将这些数据块组装成完整的文件。

客户端与 HDFS 交互流程 (以文件写入为例):

  1. 客户端发送文件写入请求到 NameNode: 客户端首先向 NameNode 发送文件写入请求,请求创建一个新的文件。

  2. NameNode 返回数据块位置信息: NameNode 接收到请求后,根据文件大小和副本数量等信息,分配多个 DataNode 存储数据块,并将数据块的位置信息返回给客户端。

  3. 客户端将数据块写入 DataNode: 客户端根据 NameNode 返回的位置信息,直接与多个 DataNode 建立连接,并行地将数据块写入 DataNode。

  4. DataNode 进行数据块副本复制: DataNode 接收到数据块后,会根据副本数量要求,将数据块复制到其他 DataNode。HDFS 默认的副本数量为 3,即每个数据块会被复制三份存储在不同的 DataNode 上。

  5. DataNode 写入完成确认: DataNode 完成数据块写入后,会向客户端发送写入完成确认消息。

  6. 客户端发送写入完成响应到 NameNode: 客户端收到所有 DataNode 的写入完成确认消息后,会向 NameNode 发送写入完成响应,告知 NameNode 文件写入完成。NameNode 会更新元数据信息,记录文件的 Block Locations 信息。

2.1.3 代码实践:HDFS 客户端基本操作

为了更好地理解 HDFS 客户端如何与 HDFS 集群交互,我们来看一些 Java 代码示例,演示 HDFS 客户端的基本操作,例如连接 HDFS、创建目录、上传文件、下载文件、列出目录等。

环境准备:

在进行代码实践之前,需要确保已经搭建好 Hadoop 环境,并且可以正常访问 HDFS 集群。需要引入 Hadoop 客户端依赖,例如在 Maven 项目的 pom.xml 文件中添加以下依赖:

<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>${hadoop.version}</version> <!- 替换为你的 Hadoop 版本 --> </dependency>

代码示例:

以下代码示例演示了 HDFS 客户端的一些基本操作:

import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.*; import org.apache.hadoop.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; public class HDFSClientExample { public static void main(String[] args) throws IOException { // 1. 获取 Configuration 对象 Configuration conf = new Configuration(); // 可以通过配置文件设置 HDFS 地址,或者在代码中指定 // conf.set("fs.defaultFS", "hdfs://hadoop-master:9000"); // 2. 获取 FileSystem 对象 FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop-master:9000"), conf); // 替换为你的 NameNode 地址 try { // 3. 创建目录 Path dirPath = new Path("/test-dir"); if (!fs.exists(dirPath)) { fs.mkdirs(dirPath); System.out.println("目录创建成功: " + dirPath); } else { System.out.println("目录已存在: " + dirPath); } // 4. 上传文件 Path localFilePath = new Path("local-file.txt"); // 本地文件路径,需要先创建 local-file.txt 文件 Path hdfsFilePath = new Path("/test-dir/uploaded-file.txt"); fs.copyFromLocalFile(localFilePath, hdfsFilePath); System.out.println("文件上传成功: " + hdfsFilePath); // 5. 下载文件 Path downloadHdfsFilePath = new Path("/test-dir/uploaded-file.txt"); Path downloadLocalFilePath = new Path("downloaded-file.txt"); fs.copyToLocalFile(downloadHdfsFilePath, downloadLocalFilePath); System.out.println("文件下载成功: " + downloadLocalFilePath); // 6. 列出目录下的文件 Path listDirPath = new Path("/test-dir"); FileStatus[] fileStatuses = fs.listStatus(listDirPath); System.out.println("目录文件列表: " + listDirPath); for (FileStatus fileStatus : fileStatuses) { System.out.println("- " + fileStatus.getPath()); } // 7. 读取文件内容 Path readFileHdfsPath = new Path("/test-dir/uploaded-file.txt"); FSDataInputStream inputStream = fs.open(readFileHdfsPath); System.out.println("文件内容: " + readFileHdfsPath); IOUtils.copyBytes(inputStream, System.out, 4096, false); // 将输入流内容复制到标准输出 inputStream.close(); System.out.println(); // 8. 删除文件 Path deleteFilePath = new Path("/test-dir/uploaded-file.txt"); boolean deletedFile = fs.delete(deleteFilePath, false); // false 表示不能递归删除目录 if (deletedFile) { System.out.println("文件删除成功: " + deleteFilePath); } else { System.out.println("文件删除失败: " + deleteFilePath); } // 9. 删除目录 Path deleteDirPath = new Path("/test-dir"); boolean deletedDir = fs.delete(deleteDirPath, true); // true 表示可以递归删除目录 if (deletedDir) { System.out.println("目录删除成功: " + deleteDirPath); } else { System.out.println("目录删除失败: " + deleteDirPath); } } finally { // 10. 关闭 FileSystem 对象 fs.close(); } } }

代码详解:

  1. 获取 Configuration 对象: Configuration conf = new Configuration(); 创建 Hadoop 配置对象。Configuration 对象加载 Hadoop 配置文件 (例如 core-site.xml, hdfs-site.xml),用于配置 HDFS 客户端的连接参数。

  2. 获取 FileSystem 对象: FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop-master:9000"), conf); 通过 FileSystem.get() 方法获取 FileSystem 对象,用于操作 HDFS 文件系统。需要指定 HDFS 的 URI 地址 (例如 hdfs://hadoop-master:9000) 和 Configuration 对象。

  3. 创建目录: fs.mkdirs(dirPath); 使用 mkdirs() 方法创建目录。如果目录已存在,则不进行任何操作。

  4. 上传文件: fs.copyFromLocalFile(localFilePath, hdfsFilePath); 使用 copyFromLocalFile() 方法将本地文件上传到 HDFS。

  5. 下载文件: fs.copyToLocalFile(downloadHdfsFilePath, downloadLocalFilePath); 使用 copyToLocalFile() 方法将 HDFS 文件下载到本地。

  6. 列出目录下的文件: fs.listStatus(listDirPath); 使用 listStatus() 方法列出目录下的文件和目录信息。返回 FileStatus 数组,每个 FileStatus 对象包含文件或目录的元数据信息。

  7. 读取文件内容: fs.open(readFileHdfsPath); 使用 open() 方法打开 HDFS 文件,返回 FSDataInputStream 对象,用于读取文件内容。IOUtils.copyBytes() 方法将输入流的内容复制到标准输出。

  8. 删除文件: fs.delete(deleteFilePath, false); 使用 delete() 方法删除文件。第二个参数 false 表示不能递归删除目录,如果要删除目录,需要设置为 true

  9. 删除目录: fs.delete(deleteDirPath, true); 使用 delete() 方法删除目录。第二个参数 true 表示可以递归删除目录,即删除目录及其下的所有文件和子目录。

  10. 关闭 FileSystem 对象: fs.close(); 在使用完 FileSystem 对象后,需要关闭它,释放资源。

代码运行:

  1. 将上述 Java 代码保存为 HDFSClientExample.java 文件。

  2. 编译 Java 代码:javac HDFSClientExample.java

  3. 运行 Java 代码:java HDFSClientExample (确保 Hadoop 环境变量已配置正确,并且 Hadoop 集群正在运行)

运行成功后,可以在 HDFS 文件系统中看到创建的目录和上传的文件,以及在本地文件系统中看到下载的文件。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U