HDFS写数据过程详解

第1步:客户端发起创建请求

  • 动作: 客户端应用程序通过调用HDFS客户端库的 create() 方法,希望在HDFS上创建一个新文件(例如 /user/test/data.txt)。

  • 细节: HDFS客户端会向NameNode发起一个RPC(远程过程调用) 请求。

第2步:NameNode执行检查与响应

  • NameNode的处理:

    1. 权限检查: NameNode首先检查客户端用户是否有在指定目录下创建文件的权限。

    2. 存在性检查: 检查目标文件是否已存在。如果文件已存在,客户端会收到一个 IOException

    3. 记录元数据: 如果检查通过,NameNode会在其元数据(EditLog和FsImage)中为这个新文件创建一个记录。需要注意的是,此时文件还没有任何数据块与之关联,文件长度也为0。

  • NameNode的响应: NameNode向客户端返回一个FSDataOutputStream对象。这个输出流是后续所有数据写入操作的核心。在底层,这个输出流封装了一个DFSOutputStream对象,它负责处理与DataNode的所有通信。

第3步:建立数据管道(Pipeline)

这是最关键且最复杂的一步。

  1. 获取数据块位置: 客户端通过 DFSOutputStream 向NameNode申请为一个新的数据块(Block)分配目标DataNode列表。NameNode会根据HDFS的副本放置策略(默认3副本)返回一个包含多个(如3个)DataNode的地址列表。

    • 副本放置策略(以默认3副本、机架感知为例):

      • 第一个副本: 如果客户端本身是一个DataNode,则优先放在客户端节点上;否则,随机选择一个负载不高的节点。

      • 第二个副本: 放在与第一个副本不同机架的另一个随机节点上。

      • 第三个副本: 放在与第二个副本相同机架的不同节点上。

      • 这样做的目的是平衡数据可靠性(跨机架)和读取性能(同机架内传输)。

  2. 建立Pipeline: DFSOutputStream 开始与返回的DataNode列表建立数据写入管道。

    • 客户端首先连接到列表中的第一个DataNode(比如 DN A)。

    • DN A 再连接到列表中的第二个DataNode(DN B)。

    • DN B 再连接到第三个DataNode(DN C)。

    • 这样,一条从客户端 -> DN A -> DN B -> DN C 的管道(Pipeline) 就建立成功了。ACK(确认信息)将沿着相反的方向(DN C -> DN B -> DN A -> Client)传回。

第4步:数据包流式写入管道

  1. 数据分包: 客户端开始将待写入的数据(从本地文件或内存中)读入缓冲区。DFSOutputStream 会将数据切分成一个个的数据包(Packet)。每个Packet通常为64KB(可配置)。

  2. 加入内部队列: 这些Packet被放入一个称为数据队列(Data Queue) 的内部队列中。

  3. 发送Packet: DFSOutputStream 有一个独立的DataStreamer线程,它不断地从数据队列中取出Packet。

  4. 写入Pipeline: DataStreamer 将取出的Packet依次发送给管道中的第一个DataNode(DN A)。

  5. 管道内复制: DN A 接收到一个Packet后,会将其存储到本地的磁盘缓冲区,并立即转发给管道中的下一个节点DN B。同理,DN B 接收并存储后,再转发给DN C。这样,数据是以流式的、管道化的方式在网络中传输,而不是客户端依次发送3次,极大地提高了效率。

第5步:管道确认(ACK)

  1. 反向ACK: 当管道中最后一个DataNode(DN C)成功接收并保存了一个Packet后,它会发送一个确认消息(ACK Packet) 给前一个节点(DN B)。

  2. DN B 收到DN C的ACK后,也会向前一个节点(DN A)发送ACK。

  3. 最终,DN A 将ACK返回给客户端。

  4. 确认队列: 客户端 DFSOutputStream 内部维护着另一个队列,叫做确认队列(Ack Queue)。所有已发送但还未收到确认的Packet会一直留在确认队列中。当客户端收到来自整个管道的针对某个Packet的ACK后,才会将该Packet从确认队列中移除。

第6步:客户端完成与关闭

  1. 循环写入: 步骤4和步骤5会循环进行,直到所有数据都被写入并确认。

  2. 块完成: 当一个数据块被完全写入(达到默认的128MB块大小)后,客户端会再次通知NameNode。NameNode会确认该数据块及其副本的存储情况,并更新元数据。

  3. 申请新块: 如果文件很大,需要多个数据块,客户端会回到第3步,向NameNode为下一个数据块申请新的DataNode列表,建立新的管道,继续写入。

  4. 关闭输出流: 当所有数据都写入完成后,客户端调用 close() 方法关闭输出流。

  5. 最终提交: 在关闭过程中,客户端会再次与NameNode通信,告知文件已写入完成。NameNode此时会执行一次提交操作,确保元数据(如文件大小、数据块列表等)被持久化到磁盘。至此,文件在HDFS中变为可见和可用的状态。

 

错误处理与容错机制

HDFS写入过程的健壮性体现在其出色的错误处理上:

  • Pipeline中的节点故障:

    • 如果在写入过程中管道中的某个DataNode发生故障(例如DN B宕机):

      1. 管道会立即关闭。

      2. 任何正在传输中的Packet都会被重新加入数据队列,以确保不会丢失。

      3. 剩下的正常DataNode(DN A和DN C)上的当前数据块会被分配一个新的版本号(Generation Stamp),这是为了区分故障节点的陈旧数据。

      4. 客户端会从NameNode获取新的DataNode列表(排除故障节点),重新建立管道。

      5. 数据从正常的副本(DN A和DN C)复制到新的DataNode上,以确保副本数达到要求。

    • 客户端对这一切是透明的,它会自动重试,整个过程对应用程序无感知。

posted @ 2025-08-25 17:04  景、  阅读(36)  评论(0)    收藏  举报