Architecture #
1. NameNode #
Namespace
- NameNode 保存 Namespace,Namespace 是文件和目录的层次结构表示。
- 文件和目录在 namespace 上使用 inode 表示。inode 记录了权限,修改和访问时间,名称空间和磁盘空间配额之类的属性。
- HDFS将整个 Namespace 保存在内存中。
Block 到 DataNodes 的映射
- 文件内容分成 Block(通常为128兆字节,可根据文件选择),每个 block 在多个 DataNodes 上独立复制(通常为三个,可选择)。
- 客户端读取文件时,首先请求 NameNode 获得包含文件 block 的 datanodes 的位置,然后客户端从与它距离最近的 datanode上读取。
- 客户端在写入数据时,首先请求 Namenode 选择block 对应的副本写入的 datanodes。然后客户端通过 Pipeline 的方式写入到 datanodes。
block 的副本位置可能会随着时间而变化,NameNode 不保存 block 副本位置。
checkpoint 和 journal
- inode 数据,每个文件的 block 列表,namenode 的 metadata 称为 image。
- 存储在本机磁盘上的 image 记录称为 checkpoint。
- 修改 image 的日志称为 journal,journal 也存储在本机磁盘上。
- 故障时可以通过重放这些日志恢复内存中的状态。同时为了提高可用性,checkpoint 和 journal 会在另一台 namenode上冗余存储。
2. DataNodes #
- *block 表示:**dataNode 使用主机的文件系统表示每个 block 副本
- 第一个文件包含数据本身
- 第二个文件是 block 的元数据,包括块数据的 checksum 以及 block 的generation stamp。
- *注册消息:**DataNode 启动时向 NameNode 进行注册(handshake)
- NameNode 验证 DataNode 的 namespace id 和 系统 version,若其中之一不匹配,DataNode 会自动关闭。
- Namespace id 时格式化文件系统时分配的,用于存储在集群的所有节点上。和 NameNode 不一致的 namespace id 的节点无法加入集群。
- DataNode 的namespace id 如果为空(表示新初始化的)可以加入集群,使用 NameNode 分配的 namespace id。
- NameNode 为注册上来的 DataNode 分配 store id。
- *Block Reports 消息:**DataNode 通过发送该消息向 NameNode 告知拥有的 block 副本。
- 消息包含 datanode 管理的每个 block 副本的 id, generation stamp 和 length。
- DataNode 注册后,立即发送第一个 block report。
- 后续的 block report 每小时发送一次。
心跳消息:
- 默认的心跳间隔是三秒。
- 如果NameNode在十分钟内 没有收到来自DataNode 的心跳, 则 NameNode 认为 DataNode 停止服务,并且由该DataNode 托管的块副本不 可用。
- 然后,NameNode 计划在其他DataNodes上创建这些数据块的新副本。
- NameNode 不直接调用 DataNodes,控制指令附加在心跳消息中: - 复制到其他 datanode; - 删除本地 block 的副本复制品; - 重新注册或关闭 datanode ; - datanode 立即发送 block report;
- Namenode 每秒可以处理数千个心跳消息,同时不会影响其他操作。
3. HDFS Client #
HDFS 提供客户端代码库帮助应用程序访问文件系统。
- 当一个应用程序读取一个文件时,HDFS客户端首先向 NameNode请求一个DataNodes列表,该列表包含文件块的 副本。然后,它直接联系DataNode,请求传输所需的块。
- 当客户端写入时,它首先要求NameNode选择DataNodes来 托管文件的第一个块的repli- cas。客户端组织从节点到 节点的管道并发送数据。当第一个数据块填满时,客户端 请求选择新的数据节点来托管下一个数据块的副本
- 与传统的文件系统不同,HDFS提供了公开文件块的位置的API。这允许像MapReduce框架这样的应用程序将任务安排到数据的位置,从而改善了读取性能。
- 允许应用程序设置文件的复制因子。默认情况下,文件的复制因子为三个。对于经常访问的关键文件或文件,具有更高的复制因子可以提高其对故障的容忍度并增加其读带宽。
4. Image and Journal #
image 是 namenode的元数据,checkpoint 将 image 写入磁盘。journal 是一个 WAL log,记录文件系统需要持久化的操作。
NameNode 启动期间,通过 checkpoint 初始化 namespace image,重放 journal 记录的更改恢复文件系统最新的状态。如果 checkpoint 或 journal 丢失/损坏,则 namespace 会不可用。
NameNode 可以并发处理多个客户端的请求,但将事务提交到磁盘是一个瓶颈,其他线程都需要等待,直到其的一个线程执行完 flush-and-sync。Namenode 对多个事务进行了批处理优化。当一个线程进行 flush-and-sync 时,剩余的线程只需要检查事务是否已提交,避免flush-and-sync操作。
5. Checkpoint Node #
HDFS 的 Namenode 是一个多角色进程,还可以成为 CheckPointNode 或 BackupNode。在节点启动中指定。
CheckpointNode 在与 NameNode 不同的节点上运行,定期从 namenode 下载 checkpoint 和 journal 并在本地进行合并。同时将新的检查点返回给 NameNode。新的检查点上传到 NamenNode,NameNode 截断(truncate)当前 journal 的尾部。
对于大 型集群,处理一周的日志需要一个小时。好的做法是每天创建 checkpoint。
6. BackupNode #
和 CheckpointNode 一样,BackupNode 能够定期创建 checkpoint,但它还维护一个最新的 namespace 的内存 image,该 image 与 NameNode 的状态保持同步。因此 BackupNode 创建检查点不需要从 NameNode download,因为 BackupNode 自己的磁盘中已经有最新的 namespace image。
BackupNode 接受 NameNode 的 namespace 事务的日志流,将它们保存到自己的存储目录中,并将这些事务应用到自己内存中的 namespace image。
BackupNode 可以被看作只读的 NameNode。它包含除 block 位置之外的所有元数据信息。可以执行 NameNode 所有常规的操作:不涉及 namespace 的修改或 block 位置的信息。
7. Upgrades, File System snapshots #
快照可以让管理员永久保存文件系统的当前状态,系统启动时提供选项创建快照。如果开启选项,Namenode 首先读取 checkpoint 和 journal 并将其合并到内存中。然后在新的位置写入新的 checkpoint 和空的 journal,使旧的 checkpoint 和 journal 保持不变。
握手期间,NameNode 指示 DataNodes 是否创建本地快照。DataNode 不能通过复制数据文件目录来创建本地快照,因为这会使集群上的每个DataNode的存储容量增加一倍。
DataNode 使用硬链接 (hard link)的方式:每个 DataNode 都会创建一个存储数据目录的副本,将现有的 block 硬链接到其中。当 DataNode 删除一个 block 时,它只删除硬链接,追加期间修改的 block 使用 copy-on-write 技术。因此,旧的 block 副本在其旧目录中保持不变。
启动系统启动时,集群管理员可以选择是否将 HDFS 使用快照回滚。NameNode 恢复创建快照时保存的 checkpoint。DataNodes 恢复以前重命名的目录,并启动后台进程来删除在创建快照后创建的 block 副本。
系统迭代可能会导致 NameNode 的 checkpoint 和 journal 的格式发生变化,或者导致 DataNode上的 block 副本文件的数据表示发生变化。
布局版本标识数据表示格式, 持久存储在NameNode 和DataNodes 的存储目录中。启动时,每个节点将当前软件的布局版本与其存储的版本进行比较,并自动将数据从旧格式转换到新格式。系统使用新的软件布局版本重新启动时,转换需要强制创建快照。
HDFS 不会将 NameNode 和 DataNodes 的布局版本分开,快照创建时必须的。只备份 namespace 状态仍然会导致全部数据丢失, 因为 NameNode 不会识别 DataNodes 报告的块,并且会命令它们删除。在这种情况下,回滚将恢复元数据,但数据本 身将会丢失。为了避免数据丢失,需要一个协调的快照。
File I/O operations and Replication management #
1. File Read and Write #
lease 机制: 客户端打开文件(Open)被授予该文件的租约(lease),租约持有期间其他客户端无法写入该文件。客户端通过向 NameNode 发送心跳消息续租。文件关闭(Close),租约被撤销。
租赁持续时间受软限制和硬限制的约束:
- 在软限制到期之前,客户端独占文件。如果软限制到期,并且客户端未关闭文件或续租,则其他一个客户端可以抢占该租约。
- 如果硬限制到期(一小时)后,客户端未能续租,HDFS 会认为客户端已退出,将自动关闭文件,并恢复租约。
写入:
- 大的文件被分为多个 block,写入 block 时联系NameNode,NameNode 分配block 唯一 ID以及确定DataNode 列表存放该 block。
- 待写入的多个 DataNode 组成 Pipeline, Pipeline的顺序使从客户端到最后一个DataNode 的总网络距离最短。
- 客户端按照Pipelie写入,在一个 packet buffer 填满(通常为64 KB)后,数据被推送到 Pipeline。客户端可以不用等待将下一个分组推送到Pipeline。客户端使用滑动窗口处理 packet 的流量控制。
- 数据写入文件后,HDFS 不保证数据在文件关闭前对新的 reader 可见。如果应用程序需要保证可见性,需要显式调用hflush操作。hflush 调用后,当前 packet 立即推送到 Pipeline,hflush 阻塞直到 Pipeline 的所有 DataNodes 都知道数据包已传输成功。在hflush操作之前 写入的所有数据肯定对读者可见。
校验和:
- 客户端创建HDFS文件,它会计算每个 block 的 checksum, 与数据一起发送到 DataNode。DataNode 存储每个 block 的校验和。
- Datanode将checksums存储在元数据文件中,与块的数据文件分开。
- 客户端在读取时验证 checksum,检测数据是否损坏。若 checksum 不匹配。客户端联系 NameNode,告知损坏的副本,然后从另一个 DataNode 获取。
读取:
- 客户端打开要读取的文件时,会从NameNode获取 block 列表和每个 block 副本的位置。每个 block 的位置根据它们离 reader (客户端)的距离排序。
- 读取数据 block 的内容时,客户端首先尝试读取最近的 block 副本。如果读取失败,客户端将按顺序尝试下一个复制副本。
HDFS 允许客户端读取已经打开同时正在写入的文件。当读取时,NameNode 不知道写入的最后一个块的长度。在这种情况下,客户端在开始读取其内容之前,会向其中一个副本询问最新的长度。
Block Placement #
图3显示了现代IDC中一种常见的集群拓扑示例。同一机架中节点之间的带宽大于不同机架中节点之间的网络带宽。
HDFS 通过两个节点之间的距离来估计它们之间的网络带宽:从一个节点到其父节点的距离假定为1。两个节点 之间的距离可以通过合计它们到最近的共同祖先的距离来计算。两个节点之间的距离越短,意味着它们可以用来传输数据的带宽越大。
HDFS 允许管理员配置脚本:当DataNode 向 NameNode 注册时,NameNode 执行脚本来确定该节点属于哪个机架。如果没有,NameNode 会假设所有节点都属于一个默认的单个机架。
副本的放置数据可靠性、读取/写入性能很重要。良好的副本放置策略:提供高数据可靠性,可用性和网络带宽利用率。HDFS提供可配置的 block 放置策略接口。
默认的HDFS数据块放置策略在最小化写入成本和最大 化数据可靠性、可用性和聚合读取带宽之间进行了权衡:
- 创建新 block 时,第一个副本放置在 writer (客户端)所在的节点上,将第二个和第三个复制副本放置在不同机架中的两个不同节点上,其余的复制副本放置在随机节点 上。
- 限制:一个节点上放置不超过一个副本,且当副本的数量少于机架数量的两倍时,同一机架上 放置不超过两个副本。
读取和写入都按照 DataNodes 和客户端的距离进行排序,写入组织成 Pipeline。
- 该策略减少了机架间、节点间的写流量,总体上提高了写性能。
- 在三个副本的情况下,可以减少读取数据时使用的汇聚层交换机的网络带宽,因为一个 block 放在两个不同的机架 上,而不是三个。
默认的HDFS复制位置策略可以总结如下:
- 没有 DataNode 含有任何 block 一个以上的副本。
- 如果集群上有足够的机架,则没有机架包含相同 block 两个以上的副本。
2. Replication management #
NameNode 维护每个 block 预定副本数。DataNode block report 到达 NameNode 时,NameNode 检测 block 副本数:不足或者过渡。
当一个 block 副本数过渡时,NameNode会选择一个副本来删除:
- NameNode不希望减少托管副本的机架数量,
- 其次希望从可用磁盘空间最少的 DataNode 中删除副本
目标是在不降低数据块可用性的情 况下平衡各数据节点的存储利用率。
当 block 副本数不足时,它会被放入复制优先级队列:
- 只有一个副本的 block 具有最高优先级,而
- block 数量超过复制因子三分之二的 block 具有最低优先级。
后台线程定期扫描复制队列放置新的副本,block 复制遵循与新 block 放置相似的策略:
- 如果现有副本的数量为1,将下一个副本放在不同的机架上。
- 如果现有副本的梳理为2,如果这两个副本在同一机架 上,则第三个副本被放置在不同的机架上。
NameNode 确保 block 的所有副本都位于一个机架 上:
- 如果 NameNode 检测到某个 block 的副本出现相同机架上,NameNode 会将该块视为副本数不足,并使用上述的块放置策略将该块复制到不同的机架上。
- 在 NameNode 收到副本已创建的通知后,该块现在是被过度复制的。然后,NameNode 将删除旧的副本。
因为过度复制策略倾向于不减少机架数量。
3. Balancer #
HDFS块放置策略未考虑数据磁盘磁盘空间利用率。
这是为了避免将新的(更有可能被引用)data放在一小部分datanodes。因此,数据可能并不总是在数据台上均匀放置。当将新节点添加到群集中时,也会发生不平衡。
Balancer 是 集群平衡磁盘空间使用的工具。它将一个阈值作为输入参数,该阈值是(0,1) 范围内 的一个分数。
- 对于每个DataNode,节点的利用率 (节点中已磁盘用空间与节点磁盘空间总容量的比率) 与整个群集的利用率 (集群中已用空间与集群总容量的比率) 相差不超过阈值, 则集群是平衡的。
- Balancer 迭代地将副本从利用率较高的 DataNode 移动到利用率较低的 DataNode。
- Balancer 选择要移动的副本并决定其目的地时,保证该 block 不会减少副本或机架的数量。
Balancer 可以限制 block 平衡操作时的带宽。
4. Block Scanner #
DataNode 运行一个 block scanner,该 scanner 定期扫描 block 的副本,验证存储的校验和是否与块数据匹配。每个scanner 周期,block scanner 会调整磁盘读取带宽,以便在可配置的周期内完成验证。
当客户端读取或 block scanner 程序检测到损坏的 block 时,它会通知NameNode。NameNode 将副本标记为损坏,但不立即删除副本。
- NameNode 复制损坏block 的可用的 block 副本。
- 当可用副本数达到 block 的复制因子时,才会移除损坏的副本。
如果客户端读取一个完整的 block 并且 checksum 验证成功,会通知DataNode。DataNode 将该副本作为验证的副本。
每个 block的验证时间存储在人类可读日志文件中。何适合在 DataNode 的 top-level 目录最多存在 current 和 prev 两个日志。新的验证时间附加到 current。每个 DataNode 在内存中也维护一个副本按验证时间排序的扫描列表。
5. Decommissioing #
集群管理员可以从集群中移除指定的 DataNode。NameNode 会将标记为移除的 DataNode 上的 block 迁移,一旦完成迁移,节点进入 decommissioned 状态,可以从集群安全的移除。
6. Inter-Cluster Data Copy #
HDFS 提供了DistCp 工具用于大型集群之间的复制,它本质上是一个 MapReduce 任务。
Summary #
- HDFS 通过 single-writer-multiple-reader 和客户端 lease 独占文件写的方式解决了 GFS 被人诟病的 relax consistency 的问题,但是这种方式会导致客户端出现假死和脑裂的情况,论文中没有提及。
- HDFS 的 NameNode 存在单点问题,高可用使用主备模型,当主故障时,备份接管期间需要一定时间,系统仍然不可用。
- NameNode 提供 namespace 和 inode属性,可以进行类 POSIX 语义的文件系统,为了加速元数据的访问时 namespace 是 in-memoery 的,这也导致了 master 管理的文件有限,因此海量小文件会让 master 内存爆掉。