一、全部组件名词深度解析
(1)客户端侧组件
HDFS Client
HDFS 客户端,属于一套代码库,不是独立进程。命令行、Java 代码上传文件本质就是加载这套客户端库。
主要职责:
- 读取本地磁盘源文件;
- 逻辑切分文件为 Block 块;
- 与 NameNode 进行 RPC 远程通信,获取元数据、获取 DataNode 节点列表;
- 和 DataNode 建立网络连接,切分 Packet 数据包、发送数据;
- 接收 DataNode 返回 ACK 确认包,处理失败重传;
- 维护 FSDataOutputStream 输出流对象。
DistributedFileSystem
HDFS Java API 的核心类,实现 FileSystem 抽象父类。客户端与 NameNode 之间所有 RPC 远程调用,全部由该类完成。
注意:它不传输真实文件字节数据,只传输控制指令:创建文件、获取块节点、通知上传完成。
create() 方法
API 调用:DistributedFileSystem.create(path)。作用:向 NameNode 发送 RPC 请求,在 HDFS 指定路径新建文件。NameNode 校验通过之后,该方法返回输出流对象 FSDataOutputStream。
FSDataOutputStream
文件系统数据输出流。真正负责向外写出文件数据的对象。DistributedFileSystem 只负责控制命令;真实文件数据全部交给 FSDataOutputStream 处理。
内部封装逻辑:
- 维护数据包队列,将文件数据切割成 Packet;
- 维护应答队列,接收流水线传回 ACK 确认包;
- 管理 Pipeline 流水线网络连接;
- 处理 DataNode 故障,故障时剔除节点重建流水线;
- write():把本地字节写入流内存缓冲区;
- close():数据写完关闭流,发送 complete 通知 NameNode。
write()
写方法。程序调用 outputStream.write(byte\[\]),将本地文件字节写入输出流内存缓冲区。此时数据仅仅停留在客户端内存,还没有发送到网络。
close()
关闭输出流,非常关键。执行 close() 之后,客户端向 NameNode 发送 complete RPC 请求,告诉 NameNode 所有块上传结束,更新元数据。如果程序异常退出没有执行 close(),HDFS 只会保留临时未完成文件,其他客户端无法读取。
(2)NameNode(主节点 NN)
独立 Java 进程。
重点:NameNode 不接收、不存储任何真实文件内容字节,只处理元数据与调度,真实数据流只存在客户端和 DataNode 之间。
metadata 元数据
描述数据的数据。包含:HDFS 目录树、文件名、路径、权限、文件大小、Block 编号、每个 Block 存储在哪些 DataNode。元数据完整保存在 NameNode 内存,磁盘持久化为 fsimage 镜像文件、edits 编辑日志文件。
接收 create 创建请求时的两项校验
- 权限校验:当前用户是否拥有目标目录创建文件权限;
- 目录校验:文件父目录是否存在。
校验失败直接返回错误;校验成功,回复客户端允许上传文件。
块分配与机架感知原理
客户端申请 Block 存储节点列表时,NameNode 使用机架感知策略挑选 3 台 DataNode。默认副本放置策略:
- 第 1 副本:客户端所在机器;客户端不在集群则随机选一台 DataNode;
- 第 2 副本:另一个机架上的 DataNode;
- 第 3 副本:和第 2 副本同机架,另一台不同机器。
目的:防止单个机架断电导致全部副本丢失,同时兼顾内网传输性能。NameNode 返回 DN1、DN2、DN3 三台节点地址给客户端。
注意:此时 NameNode 不会保存 Block 和 DN 的映射关系。块位置后续由 DataNode 通过心跳上报给 NameNode。
(3)DataNode(数据节点 DN1 DN2 DN3)
独立 Java 进程,真正存储 Block 真实业务数据。
bytebuffer 字节缓冲区
DataNode 机器内存区域。网络收到 Packet 数据包,先放入 bytebuffer 内存缓冲区,之后再刷写到磁盘生成 blk-1 块文件。
blk-1、blk-2
DataNode 磁盘上的二进制物理文件。blk-1 代表第一个数据块。Block:磁盘存储逻辑单位,默认 128MB,大文件逻辑切割分散存储。
Pipeline 流水线(Block 传输通道)
串联网络链路:客户端 → DN1 → DN2 → DN3。核心特点:客户端只发送一份 Packet 数据包给 DN1;DN1 落盘之后转发 Packet 给 DN2;DN2 落盘转发 Packet 给 DN3。优势:节省客户端出口带宽,副本复制交给 DataNode 内网完成。
Packet(64KB)
网络传输最小单元,默认大小 64KB。区分:Block (128MB) = 磁盘存储单位;Packet (64KB) = 网络传输小包。128MB 的 Block 会被拆分为大量 Packet 逐个传输。Packet 包含:业务数据、校验和、序列号。客户端维护发送队列、应答队列;收到 ACK 确认之后才删除 Packet;ACK 失败则重传数据包。
ACK 应答包(Acknowledge 确认响应)
传输方向:反向流水线。DN3 完成落盘生成 ACK,依次传给 DN2→DN1→客户端。客户端收到 ACK,代表该 Packet 在 3 台 DataNode 全部持久化成功。遇到 DN 故障时,流水线剔除故障节点,剩余节点继续传输。
二、HDFS 写流程 8 步完整运行过程
上传 HDFS 路径:/root/gateway_records.txt,本地源文件 200MB。
步骤 1:客户端发起创建文件 RPC 请求
调用 DistributedFileSystem.create("/root/gateway_records.txt"),DistributedFileSystem 组件向 NameNode 发送 RPC 请求。NameNode 执行校验:检查父目录 /root 是否存在;检查客户端用户是否具备创建文件权限。此时 HDFS 无真实文件,NameNode 仅生成临时元数据记录,文件状态标记为"正在写入",还没有真实数据流动。
步骤 2:NameNode 返回上传许可
校验全部通过,RPC 回复客户端允许上传。create() 方法返回 FSDataOutputStream 输出流对象,后续写数据全部使用该输出流。
步骤 3:客户端请求第一个 Block 对应的 DataNode 列表
准备上传第一个 Block blk-1(0-128MB)。客户端 RPC 请求 NameNode,获取存放该 Block 副本的 3 台 DataNode 节点。NameNode 执行机架感知策略,筛选健康节点 DN1、DN2、DN3。
步骤 4:NameNode 返回 DN1、DN2、DN3 节点地址
NameNode 将三台 DataNode 的 IP 与端口返回客户端。此时 NameNode 内存还不会记录 Block 与 DN 的对应关系。
步骤 5:建立 Pipeline 流水线 TCP 网络通道
FSDataOutputStream 建立网络连接:客户端建立 TCP 连接到 DN1;DN1 收到连接请求,内部建立 TCP 连接连接下游 DN2;DN2 收到连接请求,内部建立 TCP 连接连接下游 DN3。完整流水线链路:客户端 → DN1 → DN2 → DN3,通道建立完成。
步骤 6:逐级 ACK 应答确认流水线就绪
确认消息从链路末尾 DN3 反向往客户端传递:DN3 连接就绪,发送 ACK 给 DN2;DN2 收到 DN3 的 ACK,发送 ACK 给 DN1;DN1 收到 DN2 的 ACK,发送 ACK 给客户端。客户端收到 ACK,整条流水线就绪,可以传输真实 Packet 数据。如果任意节点连接失败,客户端重新向 NameNode 申请新 DataNode 重建流水线。
步骤 7:以 Packet 为单位传输 blk-1(0-128MB)
客户端读取本地 200MB 文件,调用 FSDataOutputStream.write() 写入数据,数据存放在客户端内存缓冲区。缓冲区攒够 64KB,封装为 Packet 数据包,进入发送队列。客户端将 Packet 发送给 DN1。DN1 接收 Packet:放入 bytebuffer 缓冲区;写入本地磁盘 blk-1;转发 Packet 给下游 DN2。DN2 接收 Packet:放入 bytebuffer 缓冲区;写入本地磁盘 blk-1;转发 Packet 给下游 DN3。DN3 接收 Packet:放入 bytebuffer 缓冲区;写入本地磁盘 blk-1;无下游节点,停止转发,生成 ACK 确认包反向传回。ACK 沿着 DN3→DN2→DN1→客户端路径返回。客户端收到 ACK,代表 Packet 三份副本落盘成功,从应答队列移除 Packet。循环发送 Packet,直到完整 128MB blk-1 块全部传输完毕并且全部收到 ACK 确认。
步骤 8:循环处理剩余 Block 块,close 完成上传
blk-1 全部传输完成。客户端再次 RPC 请求 NameNode,获取第二个 Block blk-2(72MB)的 DataNode 节点列表。重复步骤 5-7,建立流水线传输 blk-2 所有 Packet。blk-2 传输完毕,所有块处理完成。客户端调用 FSDataOutputStream.close() 关闭输出流。close 内部发送 complete RPC 请求通知 NameNode 文件上传全部完成。NameNode 修改元数据,文件状态由"正在写入"修改为正常完成状态,HDFS 上 /root/gateway_records.txt 文件正式生效,可以被读取。
补充:各个 DataNode 定期心跳上报本机保存的 Block 编号给 NameNode,NameNode 才填充 Block 与 DataNode 位置映射。
三、底层补充细节
CheckSum 校验和
每个 Packet 携带校验和。DataNode 收到数据包进行校验,检测网络传输数据损坏,出错返回错误 ACK。
DataNode 故障处理
传输过程中某个 DataNode 宕机,客户端收不到对应 ACK。客户端剔除故障节点,剩余可用节点继续完成当前块传输。后续 NameNode 检测副本数量不足,后台自动复制补齐 3 副本。
Block 与 Packet 差异
Block(128MB):面向磁盘存储,减少 NameNode 元数据条目;Packet(64KB):面向网络传输,流式发送、及时确认,不需要等待完整块读完。
数据流隔离
真实文件字节流只在客户端与 DataNode 之间传输;NameNode 只处理 RPC 控制消息,不转发业务数据,保障 NameNode 性能。