1 - HDFS 文件的写入
1.1 写入过程
1、Client 发起文件上传请求,通过 RPC 与 NameNode 建立连接,NameNode 检查 Client 是否有相应权限,以及目标文件是否已存在、父目录是否存在,向 Client 返回是否可以上传;
2、Client 从 NameNode 中获取第一个 block 的传输目的地,也就是应该传输到哪些 DataNode 上;
3、NameNode 根据配置文件中指定的备份数量及机架感知原理进行文件分配,返回可用的 DataNode 的地址,假设分别是 A、B、C;
4、Client 先向 DataNode A 上传数据(也是一个 RPC 调用,建立 pipeline ),A 收到请求会继续调用 B,然后 B 调用 C,建立完成所有的 pipeline 之后,逐级返回 Client;
5、Client 开始向 DataNode A 上传第一个 block,以 packet 为单位(默认是64K),A 收到一个 packet 就会传给 B,B 传给 C,A 每传一个 packet 会放入一个 ack 队列等待应答;
6、数据被分割成一个个的 packet 数据包在 pipeline 上依次传输,在 pipeline 反方向上逐个发送 ack(操作是否成功的应答),最终由 pipeline 中第一个 DataNode 节点 A 将 pipelineack 发送给 Client;
7、当一个 block 传输完成后,Client 再次请求 NameNode 上传第二个 block ,继续上述 (4) - (7) 步骤。
1.2 写入异常时的处理
当数据写入失败时,HDFS 会作出以下反应:
NameNode 在注意到副本个数不足时,会在其他 DataNode 上创建一个新的副本。后续的数据块就能正常的接受处理。
1.3 写入的一致性
- 新建一个文件后,HDFS 的命名空间中立即可见;
- 写入文件的内容不保证立即可见(即使数据流已经调用
flush()
方法刷新并存储); - 当前正在写入的块对其他 Reader 不可见;
- 调用
hflush()
方法后,数据被写入 DataNode 的内存中,可保证对所有 Reader 可见; - 调用
hsync()
方法后,数据就会被写到磁盘上; - 如果没有调用
hflush()
或hsync()
,客户端在故障的情况下可能丢失正在写入的数据块。
2 - HDFS 文件的读取
2.1 读取过程
1、Client 向 NameNode 发起 RPC 请求,来确定请求文件 block 所在的位置;
2、NameNode 会视情况返回文件的部分或者全部 block 列表:对每个 block,NameNode 都会返回含有该 block 副本的 DataNode 地址 —— 按照集群拓扑结构得出 DataNode 与客户端的距离,然后进行排序,排序规则是:
3、Client 选取排序靠前的 DataNode 来读取 block,如果客户端本身就是 DataNode,就会从本地直接获取数据(短路读取特性);
4、底层本质是建立 Socket Stream(FSDataInputStream),重复调用父类 DataInputStream 的 read 方法,直到这个 block 上的数据读取完毕;
5、当读完列表的 block 后,若文件读取还没有结束,客户端会继续向 NameNode 获取下一批的 block 列表;
6、读取完所有的 block 后,客户端会将它们合并成一个完整的文件。
【注意】NameNode 只是返回 Client 请求的 block 的 DataNode 地址,并不会返回 block 的具体数据。
2.2 读取异常时的处理
1)如果客户端和所连接的 DataNode 在读取数据时出现故障,那客户端就会去尝试连接存储这个 block 的下一个最近的 DataNode,同时它会 记录这个发生故障的 DataNode,以免后面再次连接该节点。
2)客户端每读完一个 block 都会进行 checksum 验证,如果从某个 DataNode 上读取到的 block 是损坏的,客户端会通知 NameNode 更新该 block 的相关信息,然后从下一个拥有该 block 副本的 DataNode 继续读取文件。