在HDFS中,追加(Append)数据的流程是怎样的?
在HDFS中,追加(Append)数据是指向一个已经存在且已关闭的文件末尾继续写入数据的过程。
HDFS最初的设计是“一次写入,多次读取”(WORM),不支持修改和追加。但在后来的版本中(Hadoop 2.x及以后),为了支持HBase的WAL(预写日志)和Flume等流式数据写入场景,HDFS引入并完善了Append功能。
HDFS追加数据的核心难点在于处理文件的最后一个数据块(Block)。追加数据的整体流程可以分为四个主要阶段:请求追加、建立/恢复流水线、数据传输、完成与关闭。
以下是详细的流程步骤:
第一阶段:请求追加(NameNode交互)
- 客户端发起请求:客户端(Client)调用
FileSystem.append(Path f)方法,向 NameNode 发起 Append 的 RPC 请求。 - NameNode 校验:
- NameNode 检查文件是否存在。如果文件不存在或是一个目录,则报错。
- 检查客户端是否有对该文件的写权限。
- 检查文件的租约(Lease)。HDFS保证同一时刻只有一个客户端能写入文件。如果文件当前被其他客户端打开(持有租约),则请求失败。NameNode 会将该文件的租约分配给当前请求追加的客户端。
- 处理最后一个 Block:
这是 Append 与普通 Write 最大的不同点。NameNode 检查文件的最后一个 Block 是否已满(通常是 128MB):- 如果最后一个 Block 已满:NameNode 会像普通的写操作一样,为该文件分配一个新的 Block,并返回给客户端。
- 如果最后一个 Block 未满(处于 Partial 状态):NameNode 会将该 Block 的状态从
COMPLETE(完成)修改为UNDER_CONSTRUCTION(构建中)。同时,NameNode 会为该 Block 生成一个新的版本号(Generation Stamp, GS),以防止旧的或损坏的副本干扰。
- 返回位置信息:NameNode 将最后一个 Block 的信息(包括所在的 DataNode 列表、新的 GS、当前的数据长度等)封装在
LocatedBlock对象中,返回给客户端。
第二阶段:恢复与建立流水线(Pipeline Setup)
如果最后一个 Block 未满,客户端需要连接保存该 Block 副本的 DataNode,重新建立数据传输流水线。
- 客户端联系 Primary DataNode:客户端根据 NameNode 返回的 DataNode 列表,联系第一个 DataNode(Primary DataNode)。
- DataNode 状态同步与恢复:
- Primary DataNode 会与其他存放该副本的 DataNode 通信。
- 它们会比对各自磁盘上该 Block 的实际长度,协商出一个一致的最小长度(Truncate 操作,丢弃可能存在的不一致的脏数据)。
- 所有正常的 DataNode 会将该 Block 的版本号(GS)更新为 NameNode 分配的新版本号。
- 建立 Pipeline:DataNode 之间建立起数据传输的流水线(Client -> DN1 -> DN2 -> DN3)。如果有 DataNode 故障,客户端会向 NameNode 报告,并剔除故障节点,使用剩余的节点建立流水线。
第三阶段:数据传输(Data Streaming)
- 追加数据:客户端开始将要追加的数据切分成一个个 Packet(通常为 64KB)。
- 发送数据:Packet 通过构建好的 Pipeline 依次传输(Client 发给 DN1,DN1 发给 DN2,DN2 发给 DN3)。
- 接收确认(ACK):DataNode 将数据写入本地磁盘后,会按相反的顺序沿 Pipeline 返回 ACK 确认包给客户端。
- 跨 Block 写入:
- 当追加的数据填满了当前的最后一个 Block(达到 128MB)后,当前的 Pipeline 会关闭。
- 客户端会再次向 NameNode 申请一个新的 Block。
- NameNode 分配新 Block 并返回新的 DataNode 列表,客户端建立新的 Pipeline 继续写入剩余数据(这部分流程与普通的文件创建写入完全一致)。
第四阶段:完成与关闭(Close)
- 数据刷盘:客户端发送完所有数据,并收到最后一个 Packet 的 ACK 确认后,通知 DataNode 完成当前 Block 的写入(Finalize Block)。
- 通知 NameNode:客户端调用
close()方法,向 NameNode 发送completeRPC 请求。 - NameNode 更新元数据:
- NameNode 确认该文件的所有 Block(除了可能正在写入的最后一个新 Block)的副本数达到最小要求。
- 将 Block 的状态从
UNDER_CONSTRUCTION改回COMPLETE。 - 更新文件的最终长度、修改时间等元数据信息。
- 释放该文件的写租约(Lease),允许其他客户端进行操作。
总结:Append 与普通 Write 的核心区别
| 特性 | 普通写入 (Create) | 追加写入 (Append) |
|---|---|---|
| 文件状态 | 创建新文件或覆盖旧文件。 | 必须是已存在的、当前处于关闭状态的文件。 |
| NameNode操作 | 分配第一个全新的 Block。 | 查找最后一个 Block,将其状态改为 UNDER_CONSTRUCTION。 |
| DataNode操作 | 直接在新节点上创建新 Block。 | 找到原有的 DataNode,进行Block恢复和长度同步,更新Generation Stamp。 |
| 使用场景 | 批量导入静态数据。 | HBase WAL、Flume日志收集等流式持续写入场景。 |