Hadoop核心组件之HDFS

HDFS

HDFS (Hadoop Distributed File System: Hadoop 分布式文件系统) 是运行在通用硬件上、提供流式数据操作、能够处理超大数据的分布式文件系统, Apache Hadoop 项目基于 Google GFS 论文的开源实现。HDFS 是为了解决大数据时代数据存储问题而产生的,采用分布式的存储方式,本质上是一个文件系统。

HDFS 优缺点

HDFS 具有以下优点:

  • 高容错性:数据自动保存多个副本,通过增加副本的形式,提高容错性。
  • 高吞吐量:流数据读写、大数据集
  • 易扩展性:兼容廉价的硬件设备、强大的跨平台兼容性
  • 高可靠性

由于 HDFS 特殊的设计,在实现上述优良特性的同时,也使得自身具有一些应用局限性:

  • 不适合低延迟数据访问
  • 无法高效存储大量小文件
  • 不支持多用户写入及任意修改文件

HDFS 体系结构

HDFS采用了主从 (Master/Slave) 体系模型,一个 HDFS 集群包括一个名称节点 (NameNode) 和若干个数据节点 (DataNode)。

其中,名称节点作为中心服务器,。从内部

HDFS体系结构

名称节点 (NameNode) 是一个主服务器,负责管理文件系统的命名空间 (namespace) 、数据块到具体数据节点的映射以及客户端对文件的访问。此外,还有许多数据节点 (DataNode),一般是一个节点运行一个数据节点进程,集群中的数据节点负责处理文件系统客户端的读/写请求,在名称节点的统一调度下进行数据块的创建、删除和复制等操作。

从内部看,文件被分割成一个或多个数据块 (block),这些块存储在一组数据节点中。名称节点执行文件系统命名空间操作,如打开、关闭和重命名文件和目录。它还确定块到数据节点的映射。数据节点负责处理来自文件系统客户端的读写请求。数据节点还根据来自名称节点的指令执行块创建、删除和复制。

客户端

客户端 (Client) 是用户操作 HDFS 最常用的方式,HDFS 在部署时都提供了客户端。HDFS 客户端是一个库,暴露了 HDFS 文件系统接口,这些接口隐藏了 HDFS 实现中的大部分复杂性。严格来说,客户端并不算是 HDFS 的一部分。客户端可以支持打开、读取、写入等常见的操作,并且提供了类似 Shell 的命令行方式来访问 HDFS 中的数据。此外,HDFS 也提供了 Java API,作为应用程序访问文件系统的客户端编程接口。

客户端的职责:

  • 文件切分。文件上传 HDFS 的时候,Client 将文件切分成 一个一个的Block,然后进行存储。
  • 与 NameNode 交互,获取文件的位置信息。
  • 与 DataNode 交互,读取或者写入数据。
  • Client 提供一些命令来管理 和访问HDFS,比如启动或者关闭HDFS。

HDFS 概念详解

数据块

HDFS 使用 (block) 作为存储单位,数据块是 HDFS 文件处理的最小单元。一个文件被分成多个块,默认的块大小在 Hadoop1.0 时期为 64MB,在 Hadoop2.7.3 之后为 128M。块的大小远远大于普通文件系统,可以最小化寻址开销。HDFS 数据块以文件形式存储在数据节点的磁盘上。

HDFS采用抽象的块概念可以带来以下几个明显的好处:

-支持大规模文件存储:文件以块为单位进行存储,一个大规模文件可以被分拆成若干个文件块,不同的文件块可以被分发到不同的节点上,因此,一个文件的大小不会受到单个节点的存储容量的限制,可以远远大于网络中任意节点的存储容量
-简化系统设计:首先,大大简化了存储管理,因为文件块大小是固定的,这样就可以很容易计算出一个节点可以存储多少文件块;其次,方便了元数据的管理,元数据不需要和文件块一起存储,可以由其他系统负责管理元数据
-适合数据备份:每个文件块都可以冗余存储到多个节点上,大大提高了系统的容错性和可用性

名称节点

在 HDFS 中,名称节点 (NameNode) 是主从架构中的主节点。负责管理分布式文件系统的命名空间 (Namespace):包括文件系统目录树、文件/目录信息以及文件的数据块索引。这些信息以两个核心的数据结构保存在名称节点的磁盘上,即 FsImage 和 EditLog。

FsImage 用于维护文件系统树以及文件树中所有的文件和文件夹的元数据,操作日志文件 EditLog 中记录了所有针对文件的创建、删除、重命名等操作。
名称节点记录了每个文件中各个块所在的数据节点的位置信息。

名称节点的主要职责:

  1. 管理 HDFS 的命名空间
  2. 配置副本策略;
  3. 管理数据块(Block)映射信息;
  4. 处理客户端读写请求。

FsImage

FsImage 文件包含文件系统中所有目录和文件 inode 的序列化形式。
每个 inode 是一个文件或目录的元数据的内部表示,并包含此类信息:文件的复制等级、修改和访问时间、访问权限、块大小以及组成文件的块。
对于目录,则存储修改时间、权限和配额元数据

FsImage 文件没有记录每个块存储在哪个数据节点。而是由名称节点把这些映射信息保留在内存中,当数据节点加入 HDFS 集群时,数据节点会把自己所包含的块列表告知给名称节点,此后会定期执行这种告知操作,以确保名称节点的块映射是最新的。

名称节点的启动

在名称节点启动的时候,它会将 FsImage 文件中的内容加载到内存中,之后再执行 EditLog 文件中的各项操作,使得内存中的元数据和实际的同步,存在内存中的元数据支持客户端的读操作。

一旦在内存中成功建立文件系统元数据的映射,则创建一个新的 FsImage 文件和一个空的 EditLog 文件。

名称节点启动之后,HDFS 中的更新操作会重新写到 EditLog 文件中,因为 FsImage 文件一般都很大(GB级别的很常见),如果所有的更新操作都往 FsImage 文件中添加,这样会导致系统运行的十分缓慢,但是,如果往 EditLog 文件里面写就不会这样,因为 EditLog 要小很多。每次执行写操作之后,且在向客户端发送成功代码之前,EditLog 文件都需要同步更新。

FsImage 与 EditLog 的合并

在名称节点运行期间,HDFS 的所有更新操作都是直接写到 EditLog 中,久而久之, EditLog 文件将会变得很大

从 名称节点的启动 可以看到,当名称节点重启的时候,名称节点需要先将FsImage里面的所有内容映像到内存中,然后再一条一条地执行EditLog中的记录,当EditLog文件非常大的时候,会导致名称节点启动操作非常慢,而在这段时间内HDFS系统处于安全模式,一直无法对外提供写操作,会严重影响使用。

这时需要使用第二名称节点 (SecondaryNameNode) 来解决。第二名称节点是 HDFS 架构中的一个组成部分,它是用来保存名称节点中对HDFS 元数据信息的备份,并减少名称节点重启的时间。

第二名称节点

第二名称节点 (SecondaryNameNode) 一般是单独运行在一台机器上,SecondaryNameNode的工作情况:

  1. SecondaryNameNode 会定期与 NameNode 通信,请求其停止使用 EditLog 文件,暂时将新的写操作写到一个新的文件 edit.new 上,这个操作是瞬间完成,上层写日志的函数完全感觉不到差别;
  2. SecondaryNameNode 通过 HTTP GET 的方式从 NameNode 上获取 FsImage 和 EditLog 文件,并下载到本地的相应目录下;
  3. SecondaryNameNode 将下载下来的 FsImage 载入到内存,然后一条一条地执行 EditLog 文件中的各项更新操作,使得内存中的 FsImage 保持最新;这个过程就是 EditLog 和 FsImage 文件合并;
  4. SecondaryNameNode执行完 <3> 操作之后,会通过 post 方式将新的 FsImage 文件发送到 NameNode 节点上;
  5. NameNode 将从 SecondaryNameNode 接收到的新的 FsImage 替换旧的 FsImage 文件,同时将 edit.new 替换 EditLog 文件。

通过这个过程 EditLog 就变小了。

命名空间

HDFS 的命名空间包含目录、文件和块。在 HDFS1.0 的体系结构中,整个 HDFS 集群中只有一个命名空间,并且只有唯一一个名称节点,该节点负责对这个命名空间进行管理。HDFS 使用的是传统的分级文件体系,因此,用户可以像使用普通文件系统一样,创建、删除目录和文件,在目录间转移文件,重命名文件等。

数据节点

数据节点是分布式文件系统 HDFS 的从节点,负责数据的存储和读取。它会根据客户端或者是名称节点的调度来进行数据的存取。

数据节点作为从节点,会不断向名称节点发送心跳、数据块汇报以及缓存汇报。名称节点会通过汇报信息的响应向数据节点发送指令,数据节点会执行这些指令(例如创建、删除或者复制数据。)

每个数据节点中的数据会被保存在各自节点的本地Linux文件系统中

HDFS存储原理

作为一个分布式文件系统,为了保证系统的容错性和可用性,HDFS采用了多副本方式对数据进行冗余存储,通常一个数据块的多个副本会被分布到不同的数据节点上。

这种多副本方式具有以下几个优点:

  1. 加快数据传输速度
  2. 容易检查数据错误
  3. 保证数据可靠性

存取策略

数据存放策略:

  • 第一个副本:放置在上传文件的数据节点;如果是集群外提交,则随机挑选一台磁盘不太满、CPU不太忙的节点
  • 第二个副本:放置在与第一个副本不同的机架的节点上
  • 第三个副本:与第一个副本相同机架的其他节点上
  • 更多副本:随机节点

数据读取策略:

  • HDFS提供了一个API可以确定一个数据节点所属的机架ID,客户端也可以调用API获取自己所属的机架ID。
  • 当客户端读取数据时,从名称节点获得数据块不同副本的存放位置列表,列表中包含了副本所在的数据节点,可以调用API来确定客户端和这些数据节点所属的机架ID,当发现某个数据块副本对应的机架ID和客户端对应的机架ID相同时,就优先选择该副本读取数据,如果没有发现,就随机选择一个副本读取数据。

数据错误与恢复

HDFS具有较高的容错性,可以兼容廉价的硬件,它把硬件出错看作一种常态,而不是异常,并设计了相应的机制检测数据错误和进行自动恢复,主要包括以下几种情形:

名称节点出错:

名称节点保存了所有的元数据信息,其中,最核心的两大数据结构是 FsImage 和 Editlog,如果这两个文件发生损坏,那么整个 HDFS 实例将失效。因此,HDFS 设置了备份机制,把这些核心文件同步复制到备份服务器 SecondaryNameNode 上。当名称节点出错时,就可以根据备份服务器 SecondaryNameNode 中的 FsImage 和 Editlog 数据进行恢复。

数据节点出错:

  • 每个数据节点会定期向名称节点发送“心跳”信息,向名称节点报告自己的状态
  • 当数据节点发生故障,或者网络发生断网时,名称节点就无法收到来自一些数据节点的心跳信息,这时,这些数据节点就会被标记为“宕机”,节点上面的所有数据都会被标记为“不可读”,名称节点不会再给它们发送任何I/O请求
  • 这时,有可能出现一种情形,即由于一些数据节点的不可用,会导致一些数据块的副本数量小于冗余因子
  • 名称节点会定期检查这种情况,一旦发现某个数据块的副本数量小于冗余因子,就会启动数据冗余复制,为它生成新的副本
  • HDFS 和其它分布式文件系统的最大区别就是可以调整冗余数据的位置

数据出错:

网络传输和磁盘错误等因素,都会造成数据错误。

  • 客户端在读取到数据后,会采用md5和sha1对数据块进行校验,以确定读取到正确的数据。
  • 在文件被创建时,客户端就会对每一个文件块进行信息摘录,并把这些信息写入到同一个路径的隐藏文件里面。
  • 当客户端读取文件的时候,会先读取该信息文件,然后,利用该信息文件对每个读取的数据块进行校验,如果校验出错,客户端就会请求到另外一个数据节点读取该文件块,并且向名称节点报告这个文件块有错误,名称节点会定期检查并且重新复制这个块。

读写流程

读数据流程

import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataInputStream;
public class ReadFile {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://localhost:9000");
conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
FileSystem fs = FileSystem.get(conf);
Path file = new Path("test");
FSDataInputStream getIt = fs.open(file); //步骤 1
BufferedReader d = new BufferedReader(new InputStreamReader(getIt));
String content = d.readLine(); //读取文件一行
System.out.println(content);
d.close(); //关闭文件
fs.close(); //关闭hdfs
} catch (Exception e) {
e.printStackTrace();
}
}
}

HDFS 客户端读取一个 HDFS 文件基本分为以下几个步骤:

  1. 打开 HDFS 文件:客户端首先调用 DistributedFileSystem.open() 方法打开 HDFS 文件。(此方法会在底层调用 ClientProtocol.open() 返回一个 FSDataInputStream 对象用于读取数据块。HDFSDataInputStream 其实是 DFSInputStream 的装饰类,真正进行数据块读取操作的是 DFSInputStream 对象。)
  2. 从 NameNode 获取 DataNode 的地址:在 DFSInputStream 的构造方法中,会调用 ClientProtocal.getBlockLocations() 远程调用 NameNode,获得文件开始部分数据块的位置信息。NameNode 返回保存该数据块的所有数据节点的地址并根据距离客户端远近进行排序,DFSInputStream 选择一个最优的 DataNode 节点。
  3. 从 DataNode 读取数据块:客户端通过调用 DFSInputStream.read() 方法从选定的 DataNode 读取数据块,数据会以数据包 (packet) 为单位从 DataNode 通过流式接口传输到客户端。当到达数据块读取完毕后,DFSInputStream 关闭和该数据节点的连接。
  4. 继续通过ClientProtocal.getBlockLocations() 查找下一个数据块,并传输。
  5. 关闭输入流:当整个数据读取完毕,通过 FSDataInputStream.close() 关闭输入流。

HDFS 读数据流程

写数据流程

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
public class Chapter3 {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://localhost:9000");
conf.set("fs.hdfs.impl","org.apache.hadoop.hdfs.DistributedFileSystem");
FileSystem fs = FileSystem.get(conf);
byte[] buff = "Hello world".getBytes(); // 要写入的内容
String filename = "test"; //要写入的文件名
FSDataOutputStream os = fs.create(new Path(filename));
os.write(buff,0,buff.length);
System.out.println("Create:"+ filename);
os.close();
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

HDFS 客户端写入一个 HDFS 文件基本分为以下几个步骤:

  1. 创建文件:写入一个文件时,会首先调用 DistributedFileSystem.create() 方法在 HDFS 文件系统中创建一个新的空文件。(这个方法在底层会通过调用 ClientProtocol.create() 方法通知 NameNode 执行对应的操作,NameNode 会首先在文件系统目录树中指定路径下添加一个新的空白文件,然后将创建新文件的操作记录到 editlog 中。完成 ClientProtocol.create() 后,DistributedFileSystem.create() 返回一个 FSDataOutputStream 对象,此对象封装了一个 DFSOutputStream 对象,实际执行写入数据的是 DFSOutputStream 对象。)
  2. 建立数据流管道:获取到 DFSOutputStream 对象后,客户端就可以使用 DFSOutputStream.write() 来写入数据了。此时 FSDataOutputStream 会首先调用 ClientProtocol.addBlock() 向 NameNode 申请一个新的空数据块,addBlock() 会返回一个 LocatedBlock 对象,此对象保存了存储这个数据块的所有 DataNode 的位置信息。获取到数据流管道中所有的 DataNode 信息后,DFSOutputStream 就开始建立数据流管道写入数据块了。
  3. 通过数据流管道写入数据:成功建立数据流管道后,写入 DFSOutputStream 的数据会先被缓存在数据流中,之后这些数据会被切分成一个个数据包 (packet)通过数据流管道发送到所有数据节点。这里的每个数据包都会通过数据流管道依次写入数据节点的本地存储。每个数据包都有个确认包,确认包会逆序通过数据流管道回到输出流。输出流在确认了所有数据节点已经写入这个数据包之后,就从对应的缓存队列删除这个数据包。当客户端写满一个数据块之后,会调用 addBlock()申请一个新的数据块,然后循环执行写入操作。
  4. 关闭输入流并提交文件: 完成整个文件的数据块传输之后,DFSOutputStream 调用 ClientProtocal.complete() 方法,通知 NameNode 关闭文件。

HDFS 写数据流程

posted @   DataHut  阅读(213)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示