HDFS写数据过程详解
第1步:客户端发起创建请求
-
动作: 客户端应用程序通过调用HDFS客户端库的
create()方法,希望在HDFS上创建一个新文件(例如/user/test/data.txt)。 -
细节: HDFS客户端会向NameNode发起一个RPC(远程过程调用) 请求。
第2步:NameNode执行检查与响应
-
NameNode的处理:
-
权限检查: NameNode首先检查客户端用户是否有在指定目录下创建文件的权限。
-
存在性检查: 检查目标文件是否已存在。如果文件已存在,客户端会收到一个
IOException。 -
记录元数据: 如果检查通过,NameNode会在其元数据(EditLog和FsImage)中为这个新文件创建一个记录。需要注意的是,此时文件还没有任何数据块与之关联,文件长度也为0。
-
-
NameNode的响应: NameNode向客户端返回一个FSDataOutputStream对象。这个输出流是后续所有数据写入操作的核心。在底层,这个输出流封装了一个DFSOutputStream对象,它负责处理与DataNode的所有通信。
第3步:建立数据管道(Pipeline)
这是最关键且最复杂的一步。
-
获取数据块位置: 客户端通过
DFSOutputStream向NameNode申请为一个新的数据块(Block)分配目标DataNode列表。NameNode会根据HDFS的副本放置策略(默认3副本)返回一个包含多个(如3个)DataNode的地址列表。-
副本放置策略(以默认3副本、机架感知为例):
-
第一个副本: 如果客户端本身是一个DataNode,则优先放在客户端节点上;否则,随机选择一个负载不高的节点。
-
第二个副本: 放在与第一个副本不同机架的另一个随机节点上。
-
第三个副本: 放在与第二个副本相同机架的不同节点上。
-
这样做的目的是平衡数据可靠性(跨机架)和读取性能(同机架内传输)。
-
-
-
建立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步:数据包流式写入管道
-
数据分包: 客户端开始将待写入的数据(从本地文件或内存中)读入缓冲区。
DFSOutputStream会将数据切分成一个个的数据包(Packet)。每个Packet通常为64KB(可配置)。 -
加入内部队列: 这些Packet被放入一个称为数据队列(Data Queue) 的内部队列中。
-
发送Packet:
DFSOutputStream有一个独立的DataStreamer线程,它不断地从数据队列中取出Packet。 -
写入Pipeline:
DataStreamer将取出的Packet依次发送给管道中的第一个DataNode(DN A)。 -
管道内复制: DN A 接收到一个Packet后,会将其存储到本地的磁盘缓冲区,并立即转发给管道中的下一个节点DN B。同理,DN B 接收并存储后,再转发给DN C。这样,数据是以流式的、管道化的方式在网络中传输,而不是客户端依次发送3次,极大地提高了效率。
第5步:管道确认(ACK)
-
反向ACK: 当管道中最后一个DataNode(DN C)成功接收并保存了一个Packet后,它会发送一个确认消息(ACK Packet) 给前一个节点(DN B)。
-
DN B 收到DN C的ACK后,也会向前一个节点(DN A)发送ACK。
-
最终,DN A 将ACK返回给客户端。
-
确认队列: 客户端
DFSOutputStream内部维护着另一个队列,叫做确认队列(Ack Queue)。所有已发送但还未收到确认的Packet会一直留在确认队列中。当客户端收到来自整个管道的针对某个Packet的ACK后,才会将该Packet从确认队列中移除。
第6步:客户端完成与关闭
-
循环写入: 步骤4和步骤5会循环进行,直到所有数据都被写入并确认。
-
块完成: 当一个数据块被完全写入(达到默认的128MB块大小)后,客户端会再次通知NameNode。NameNode会确认该数据块及其副本的存储情况,并更新元数据。
-
申请新块: 如果文件很大,需要多个数据块,客户端会回到第3步,向NameNode为下一个数据块申请新的DataNode列表,建立新的管道,继续写入。
-
关闭输出流: 当所有数据都写入完成后,客户端调用
close()方法关闭输出流。 -
最终提交: 在关闭过程中,客户端会再次与NameNode通信,告知文件已写入完成。NameNode此时会执行一次提交操作,确保元数据(如文件大小、数据块列表等)被持久化到磁盘。至此,文件在HDFS中变为可见和可用的状态。
错误处理与容错机制
HDFS写入过程的健壮性体现在其出色的错误处理上:
-
Pipeline中的节点故障:
-
如果在写入过程中管道中的某个DataNode发生故障(例如DN B宕机):
-
管道会立即关闭。
-
任何正在传输中的Packet都会被重新加入数据队列,以确保不会丢失。
-
剩下的正常DataNode(DN A和DN C)上的当前数据块会被分配一个新的版本号(Generation Stamp),这是为了区分故障节点的陈旧数据。
-
客户端会从NameNode获取新的DataNode列表(排除故障节点),重新建立管道。
-
数据从正常的副本(DN A和DN C)复制到新的DataNode上,以确保副本数达到要求。
-
-
客户端对这一切是透明的,它会自动重试,整个过程对应用程序无感知。
-
浙公网安备 33010602011771号