一、HDFS简介

HDFS(Hadoop Distributed File System)是一个分布式文件系统,它的主要设计目标是为了解决‌存储和处理大规模数据的挑战‌,尤其针对‌低成本硬件集群‌和‌高吞吐量批处理‌场景。其有以下几个主要特性:

  1. 跨平台(底层由Java开发,天然支持跨平台部署)
  2. 高容错(数据冗余存储,数据块默认有3个副本)
  3. 高吞吐(并行读取或写入多个数据块,且是顺序读写,流式数据访问)
  4. 横向拓展(NameNode只管理元数据,实际数据由DataNode分布式存储)

但HDFS也有一些局限性,比如以下几个点

  1. 小文件性能差(小文件如果非常多,其元数据会占用大量空间)
  2. 不支持文件修改(只能顺序追加)

所以HDFS更适用于大规模数据存储以及离线批处理场景

二、系统架构

HDFS采用的是Master/Slave主从架构,由一个NameNode和多个DataNode节点组成,架构示意图如下:
image.png

NameNode(管理节点):是HDFS的重要组成部分,管理整个HDFS集群,主要有以下几个作用:

  1. 元数据管理:维护文件命名空间和数据块之间的映射关系(比如/logs/a.log这个文件,需要维护这个文件分为哪几个数据块,每个数据块在哪个DataNode节点上,对应的副本又在哪个DataNode节点上)
  2. 客户端协调:客户端读写文件时,都会先与NameNode节点进行通信,拿到协调结果后再跟实际的DataNode进行通信(后面会详细介绍客户端读写过程)
  3. DataNode故障处理:DataNode定时向NameNode发送心跳,如果检测心跳超时,则标记DataNode失效,触发副本复制,保持副本数量一致

DataNode(数据节点):是实际存储数据的节点,主要有以下几个作用

  1. 数据块存储:每个文件都会被切分成一个个数据块Block(默认128M),而DataNode负责实际存储这些数据块及其副本(比如数据块副本数为3,则3个DataNode节点上各存储一个该数据块的副本)
  2. 心跳与状态汇报:DataNode每隔一定的周期(默认3秒)向NameNode发送心跳,并且定期向NameNode汇报本地存储的数据块列表
  3. 数据读写操作:与客户端进行交互,响应客户端与其他DataNode的数据块读写请求

SecondaryNameNode(SNN):NameNode的辅助,定期地将edits文件合并到fsimage文件(这个操作叫checkpoint),避免edits文件过大,影响NameNode启动恢复时间

三、NameNode持久化设计

NameNode会将元数据全量存储到内存中,这样可以快速响应客户端请求,但内存并不会永久保留,所以NameNode对元数据进行了持久化设计(快照+操作日志,与Zookeeper设计思想基本一致),保证元数据的持久化。持久化的关键在于两个文件,fsimage快照文件 和 edits操作日志文件,存放的路径在NameNode的 ${dfs.namenode.name.dir}/current 目录下,如下图
image.png

edits操作日志

edits文件是增量操作日志文件,会记录所有对元数据的修改操作(比如创建/删除文件、修改权限等)。

生成机制:NameNode会实时将元数据操作追加到edits文件中,并更新内存中的元数据,并且当edits文件达到阈值(默认64M),滚动生成一个新的edits文件

fsimage快照文件

fsimage文件是某一时刻HDFS的元数据全量快照文件,记录了完整目录结构、文件权限、数据块分配信息等。所以当前HDFS的全量元数据 = 最新fsimage + edits

生成机制:SecondaryNameNode会定期(默认1小时)将fsimage和edits日志合并,生成新的fsimage快照

快照合并步骤

  1. SecondaryNameNode 请求NameNode停止使用当前edits文件,也就是上图里圈红的edits_inprogress文件,生成新edits.new继续记录操作
  2. SecondaryNameNode下载fsimage和旧edits文件到本地,将fsimage文件反序列化到内存中,并重放旧edits文件,得到全量元数据
  3. 将内存中的全量元数据序列化生成fsimage文件,并上传给NameNode

启动恢复

NameNode加载最新的fsimage快照到内存中,并重放edits_inprogress文件,将元数据更新到最新状态(与上面SecondaryNameNode合并过程中的操作是一致的)

四、客户端读写流程

客户端读取文件

image.png

  1. 客户端向NameNode请求文件的元数据(比如数据块位置,副本信息)
  2. NameNode返回文件对应的数据块列表Block IDs以及每个数据块分别对应哪个DataNode
  3. 客户端拿到数据块信息,与对应的DataNode建立连接,并请求数据块
  4. DataNode返回数据块
  5. 客户端验证数据块的CRC32校验和,保证数据完整性,并按逻辑顺序将多个数据块拼接为完整文件
  6. 关闭与DataNode的连接

客户端写入文件

image.png

  1. 客户端向NameNode发起文件写入请求
  2. NameNode为文件第一个数据块分配写入通道(假设有个DataNode节点,副本为3,那么通道可以是DN1 -> DN3 -> DN5)
  3. 客户端与DN1建立连接
  4. 开始链式传输第一个数据块,传输链路:客户端 -> DN1 -> DN3 -> DN5(客户端不直接与DN3、DN5建立连接)
  5. 每个DataNode接受完数据块之后,会向上一个DataNode节点返回ACK,直到DN1返回ACK给客户端
  6. 客户端向NameNode提交该数据块,表示该数据块已完成写入
  7. 重复2-6步骤,提交所有数据块后,通知NameNode整个文件已完成写入
  8. NameNode更新该文件的元数据信息

五、总结

HDFS作为Hadoop生态的存储基石,提供了统一、可靠、可拓展的底层存储,支持多种数据格式(如文本、parquet、orc等),与MapReduce、Hive、Spark、Flink等计算框架深度融合,最近几年lakehouse(湖仓一体)架构开始慢慢走进大家的视野,HDFS或对象存储也可以作为数据湖的底层存储,所以HDFS在大数据领域扮演着非常重要的角色。


kamier
1.5k 声望497 粉丝