1. 读流程
①客户端通过调用 FileSystem 对象的 open() 方法来打开希望读取的文件,对于HDFS来说,这个对象是 DistributedFileSystem 的一个实例。
②DistributedFileSystem 通过使用远程过程调用(RPC)来调用 NameNode,以确定文件起始块的位置。
③对于每一个块, NameNode 返回存有该块副本的 DataNode 地址。此外,这些 DataNode 根据它们与客户端的距离来排序(根据集群的网络拓扑)。
如果该客户端本身就是一个 DataNode (比如在一个 MapReduce 任务中),那么该客户端将会从保存有相应数据块副本的本地 DataNode 读取数据。
④DistributedFileSystem 类返回一个 FSDataInputStream 对象(一个支持文件定位的输入流)给客户端以便读取数据。FSDataInputStream 类转而封装 DFSInputStream 对象,该对象管理着 DataNode 和 NameNode 的I/O 。
⑤接着,客户端对这个输人流调用 read() 方法。存储着文件起始几个块的 datanode 地址的 DFSInputStream 随即连接距离最近的文件中第一个块所在的 DataNode。通过对数据流反复调用 read() 方法,可以将数据从 DataNode 传输到客户端。
⑥到达块的末端时, DFSInputStream 关闭与该 DataNode 的连接, 然后寻找下一个块的最佳 DataNode。
所有这些对于客户端都是透明的,在客户看来它一直在读取一个连续的流客户端从流中读取数据时,块是按照打开 DFSInputStream 与 DataNode 新建连接的顺序读取的。
它也会根据需要询问 NameNode 来检索下一批数据块的 DataNode 的位置。
⑦一旦客户端完成读取,就对 FSDataInputStream 调用 close() 方法。
2. 读取异常处理
在读取数据的时候,如果 DFSInputStream 在与 DataNode 通信时遇到错误,会尝试从这个块的另外一个最邻近 DataNode 读取数据。
它也记住那个故障 DataNode,以保证以后不会反复读取该节点上后续的块。
DFSInputStream 也会通过校验和确认从 DataNode 发来的数据是否完整。
如果发现有损坏的块, DFSInputStream 会试图从其他 DataNode 读取其副本,也会将被损坏的块通知给 NameNode。
这个设计的一个重点是,客户端可以直接连接到 DataNode 检索数据, 且 NameNode 告知客户端每个块所在的最佳 DataNode。
由于数据流分散在集群中的所有 DataNode,所以这种设计能使 HDFS 扩展到大量的并发客户端。
同时,NameNode 只需要响应块位置的请求(这些信息存储在内存中,因而非常高效),无须响应数据请求,否则随着客户端数量的增长, NameNode 会很快成为瓶颈。
3. 写流程
我们要考虑的情况是如何新建一个文件,把数据写入该文件,最后关闭该文件。
① 客户端通过对 DistributedFileSystem 对象调用 create() 来新建文件。
② DistributedFileSystem 对 NameNode 创建一个RPC调用,在文件系统的命名空间中新建一个文件,此时该文件中还没有相应的数据块。
③ NameNode 执行各种不同的检查以确保这个文件不存在以及客户端有新建该文件的权限。如果这些检查均通过, NameNode 就会为创建新文件记录一条记录;否则文件创建失败并向客户端抛一个 IOException 异常。
④ DistributedFileSystem 向客户端返回一个 FSDataOutputStream 对象,由此客户端可以开始写入数据。就像读取事件一样, FSDataOutputStream 封装一个 DFSOutputStream 对象, 该对象负责处理 DataNode 和 NameNode 之间的通信。
⑤ 在客户端写人数据时, DFSOutputStream 将它分成一个个的数据包,并写入内部队列,称为“数据队列”(data queue)。DataStreamer 处理数据队列,它的责任是挑选出适合存储数据副本的一组 DataNode ,并据此来要求 NameNode 分配新的数据块。
⑥ 这一组 DataNode 构成一个管线(Pipeline)一一我们假设副本数为3,所以管线中有3个节点。
⑦ DataStreamer 将数据包流式传输到管线中第1个 DataNode ,该 DataNode 存储数据包并将它发送到管线中的第2个 DataNode 。
⑧ 同样,第2个 DataNode 存储该数据包并且发送给管线中的第3个(也是最后一个) DataNode。
⑨ DFSOutputStream 也维护着一个内部数据包队列来等待 DataNode 的收到确认回执,称为“确认队列”(ack queue)。收到管道中所有 DataNode 确认信息后,该数据包才会从确认队列删除。
⑩ 客户端完成数据的写入后,对数据流调用 close() 方法。
该操作将剩余的所有数据包写入 DataNode 管线,并在联系到 NameNode 告知其文件写入完成之前,等待确认。
NameNode 已经知道文件由哪些块组成(因为 DataStreamer 请求分配数据块),所以它在返回成功前只需要等待数据块进行最小量的复制。
4. 写流程异常处理
4.1 单DataNode故障
如果任何 DataNode 在数据写入期间发生故障,则执行以下操作(对写入数据的客户端是透明的)。
- 首先关闭管线,确认把队列中的所有数据包都添加回数据队列的最前端,以确保故障节点下游的 DataNode 不会漏掉任何一个数据包。
- 为存储在另一正常 DataNode 的当前数据块指定一个新的标识,并将该标识传送给 NameNode ,以便故障 DataNode 在恢复后可以删除存储的部分数据块。
- 从管线中删除故障 DataNode ,基于两个正常 DataNode 构建一条新管线。余下的数据块写入管线中正常的 DataNode。
- NameNode 注意到块副本数量不足时, 会在另一个节点上创建一个新的副本。后续的数据块继续正常接受处理。
4.2 多DataNode故障
在一个块被写入期间可能会有多个 DataNode 同时发生故障,但非常少见。
只要写入了 dfs.NameNode.replication.min 的副本数(默认为1),写操作就会成功。
并且这个块可以在集群中异步复制,直到达到其目标副本数(dfs.replication的默认值为3)。
5. Federation联邦机制
5.1 当前HDFS架构局限性
当下的 HDFS 体系结构仅允许单个 NameNode 维护文件系统名称空间。注意 HA 体系中虽然说允许多个 NameNode,但是他们所维护的是同一套文件系统名称空间。这种体系目前存在着一些弊端和局限性:
- DataNode 磁盘存储空间不够增加节点,NameNode 内存不够是否可以无限扩容。一种是 DataNode 横向扩展机器增加节点,一种是纵向扩展单机加内存。
- 由于名称空间和存储层的紧密耦合,NameNode 的替代实现很困难。这限制了其他服务直接使用块存储。唯一的 NameNode 成了唯一入口。
- 文件系统的操作还限于 NameNode 一次处理的任务数。因此,群集的性能取决于 NameNode 吞吐量。
- 同样,由于使用单个名称空间,因此使用群集的占用者组织之间没有隔离。
5.2 HDFS Federation架构
5.2.1 简介
Federation中文意思为联邦、联盟,是 NameNode 之间的 Federation,也就是集群中会有多个 NameNode。多个 NameNode 的情况意味着有多个 namespace。注意,这区别于 HA 模式下的多 NameNode,HA 中它们是拥有着同一个 namespace。
Federation 体系中多个 namenode 之间相互独立且不需要互相协调,各自分工,管理自己的区域。每个 DataNode 要向集群中所有的 namenode 注册,且周期性地向所有 namenode 发送心跳和块报告,并执行来自所有 namenode 的命令。
上图中,有多个 NameNode,分别表示为 NN1,NN2,… NNn。NS1,NS2 等是由它们各自的 NameNode 管理的多个名称空间。
每个名称空间都有其自己的块池(block pool)(NS1 具有 Pool1,NS2 具有 Pool2,依此类推)。每个 DataNode 存储集群中所有块池的块。
HDFS Federation 体系结构中的块池是属于单个名称空间的块的集合。每个块池彼此独立地进行管理。在删除 NameNode 或名称空间时,DataNode 中存在的相应块池也将被删除。在升级群集时,每个名称空间卷都作为一个单元进行升级。
5.2.2 好处
- 命名空间可伸缩性:使用 Federation,可以水平扩展名称空间。这对大型群集或包含太多小文件的群集有利,因为向群集添加了更多的 NameNode。
- 性能:由于文件系统操作不受单个 NameNode 吞吐量的限制,因此可以提高文件系统的性能。
- 隔离:由于有多个名称空间,它可以为使用群集的占用者组织提供隔离。
5.3 配置示例
<configuration>
<property>
<name>dfs.nameservices</name>
<value>ns1,ns2</value>
</property>
<property>
<name>dfs.namenode.rpc-address.ns1</name>
<value>nn-host1:rpc-port</value>
</property>
<property>
<name>dfs.namenode.http-address.ns1</name>
<value>nn-host1:http-port</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address.ns1</name>
<value>snn-host1:http-port</value>
</property>
<property>
<name>dfs.namenode.rpc-address.ns2</name>
<value>nn-host2:rpc-port</value>
</property>
<property>
<name>dfs.namenode.http-address.ns2</name>
<value>nn-host2:http-port</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address.ns2</name>
<value>snn-host2:http-port</value>
</property>
</configuration>