首先要了解Hadoop Java API中的类,读写文件涉及到这些类:
- FileSystem
- DistributedFileSystem
- FSDataInputStream
- DFSInputStream
- FSDataOutputStream
- DFSOutputStream
- DataStreamer
读取文件
访问文件流程:
-
客户端调用
FileSystem类的实例方法open对文件进行访问 -
open方法具体由DistributedFileSystem中的实例方法open实现 -
由
DistributedFileSystem通过 RPC 调用 namenode,确定文件起始块的位置,对于每一个块,返回存有该块副本的 datanode 地址 -
根据客户端与这些 datanode 的网络拓扑距离将这些 datanode 进行排序,返回一个
FSDataInputStream对象给客户端FSDataInputStream类转而封装DFSInputStream对象,该对象管理着 datanode 和 namenode 的 I/O
基于以上,读文件流程:
-
客户端对
FSDataInputStream调用read方法 -
DFSInputStream对象连接距离最近的[文件中第一个块所在的 datanode] -
对数据流反复调用
read方法,可以将数据从 datanode 传输到客户端DFSInputStream会通过校验和确认数据的完整性- 若在读取数据过程中通信遇到错误或数据损坏,
DFSInputStream会尝试从这个块的另一个最近的 datanode 读取数据副本 - 同时记录故障的 datanode,保证以后不会反复读取故障节点后续的块
-
到达块的末端时,
DFSInputStream关闭与该 datanode 的连接,然后寻找下一个块的最佳 datanode -
读取完毕后,调用
FSDataInputStream的close方法
写入文件
首先是创建文件的流程:
-
客户端调用
FileSystem类的实例方法create尝试创建文件 -
create方法具体由DistributedFileSystem中的实例方法create实现 -
由
DistributedFileSystem通过 RPC 调用 namenode,在文件系统的命名空间新建一个文件 -
namenode 执行检查确保该文件不存在,以及客户端有创建该文件的权限
- 若检查通过,namenode 会为创建新文件记录一条记录
- 否则创建失败,并向客户端抛出IOException
-
创建成功后,
DistributedFileSystem向客户端返回一个FSDataOutputStream对象
由此客户端可以开始写入数据
写文件流程:
-
在客户端写入数据时,
DFSOutputStream将它分成一个个的数据包,并写入内部队列,称为数据队列 -
DataStreamer处理数据队列,其责任是:-
挑选出适合存储数据副本的一组 datanode
-
以上为依据,要求 namenode 分配新的数据块
-
这一组 datanode 构成一个管线,假设副本数为3,则管线中有3个节点
DataStreamer将数据包流式传输到管线中第一个 datanode(称为dn1)- dn1 存储数据包并将数据包发给 dn2,依此传递
-
-
DFSOutputStream也维护着一个内部数据包队列来等待 datanode 的收到确认回执,称为确认队列(ack queue),收到管线中所有 datanode 的确认消息后,该数据包才会从确认队列中删除 -
若任何 datanode 在数据写入期间发生故障,则执行:
- 关闭管线,确认把队列中的所有数据包都添加回数据队列的最前端,以确保故障节点下游的 datanode 不会漏掉任何一个数据包
- 为存储在另一正常 datanode 的当前数据块制定一个新的标识,并将该标识传送给 namenode,以便故障 datanode 在恢复后可以删除存储的部分数据块
- 从管线中删除
x个故障 datanode,基于n-x(n为副本数)个 datanode 构建一条新管线,余下的数据块写入管线中正常的 datanode - namenode 注意到块副本量不足时,会在另一个节点创建一个新的副本,后续的数据块继续正常接受处理
-
客户端完成了数据的写入后,调用
close方法,该操作将剩余的所有数据包写入 datanode 管线,并在联系到 namenode 告知其文件写入完成之前,等待确认 -
由于 namenode 已经知道文件由哪些块组成(因为
Datastreamer请求分配数据块),所以它在返回成功前,只需要等待数据块进行最小量的复制