Hadoop HDFS 设计随想

目录

珍惜时间,时间要花在做有用的事情上,力戒无意义的举动

——富兰克林

引言

当数据的大小大于一台独立的电脑的存储能力时,就有必要对它进行分区并且存储在多台单独的电脑上。要将非常大的数据集合存储在多台电脑上,就会涉及到多台电脑共享的文件系统,也就是分布式文件系统。

分布式文件系统(distributed file system) 是指管理网络中跨多台计算机存储的文件系统。

分布式文件系统既然跨多台电脑,通过网络将它们互联起来,就可能会出现其中的一个电脑节点连接中断或者宕机的情况,也就是节点故障。在这种情况下也不能出现丢失整个文件系统任何数据的情况,怎么来做到呢?先让我们用发散思维的方式来思考一下。

  1. 将文件系统的每份数据备份,并且备份不能在同一台物理计算器上,这样能保证即使其中一台计算机宕机,文件系统的整体数据还是完整的

  2. 文件系统中的数据和文件信息(元信息)是两个不同的东西,是否要将它们分布在不同的地方?怎么能保证文件信息(元信息)的稳定性,在一台计算机宕机后,元信息不丢失,是否也要引入备份节点的概念?

  3. 既然文件的数据都比较大,它们的区块该如何设计?每个区块是否应该设置的更大一点?

  4. 如果文件系统信息与文件数据分离,假如存储文件系统信息的节点宕机该怎么办,如何确保分布式文件系统的高可用性?

这些发散性问题正是 HDFS 设计时要解决的问题,下面就让我们一起带着这些问题来看下 HDFS 的解决方案。

HDFS 数据块的设计

HDFS 是专门为大数据场合设计的分布式文件系统,要理解 HDFS 数据块的设计,先要理解大数据场合下超大文件的特点。所谓超大文件是指具有几百 MB,几百 GB甚至几百 TB 大小的文件。这类文件在大数据中占有的比重是非常大的。HDFS 数据块则主要是针对这些超大文件而设计的。

数据块应该设置成多大?

HDFS 中默认的块(block) 大小为 128M,为什么设计的这么大呢?

我们知道读取文件时主要有两个时间开销:磁盘传输时间和定位磁盘块的时间。如果块很小的话,一个文件会有很多磁盘块组成,光定位磁盘块就会耗费很多时间。如果将块的大小设置成 128M,定位磁盘块就花不了太多时间,主要的时间会花费在传输数据上,这样就很大程度节省了读取时间。

当然,也不能设计成过大,MapReduce 中的map 任务通常一次只能处理一个块的时间,任务数太少,作业运行的时间就比较慢了。

抽象成数据块有哪些好处?

  1. 一个文件理论上可以无限大

    一个文件可以由任意个数据块组成,而且这些数据块不需要存储在同一块磁盘上,由此可以推断出,一个文件占满整个 Hadoop 集群的整个磁盘空间。

  2. 文件数据与文件元信息分离,简化系统设计

  3. 适合数据备份,从而提高数据容错能力和可用性

    为了保证数据的高可用性,默认会将每个数据块复制到 3 台机器上,这样即使其中一台机器宕机,数据也不会丢失。

    当一台机器宕机后,就会从其他机器上读取数据,并且采取一定的步骤将宕机的机器丢失的数据块复制到其他的机器上,从而保证副本的数量。

操作块信息的命令

fsck 命令可以显示块信息,如列出文件系统中各个文件由哪些块组成的命令

hdfs fsck / -files -blocks

要获取完整的 hdfs 命令列表,可参考 这里

HDFS 中节点的设计

有几种节点类型?

从上节中,我们就看出 HDFS 的文件数据和文件元信息是分离的。由此,HDFS 集群中的节点会分成两种类型:

  • namenode 管理节点
    • 对整个文件系统进行管理
    • 维护文件系统树以及其中所有的文件和目录
    • 包含所有文件和目录的元信息,记录每个文件在各个块数据节点的信息
  • datanode 工作节点
    • 受 client 和 namenode 调度会存储和检索数据块

用户如何访问 HDFS?

用户可以通过 client 与 namenode 和 datanode 的交互来访问整个文件系统。

使用 Java 客户端需要使用 maven 引入对应的 jar 包

        <!-- hadoop 部分 begin -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>${hadoop.version}</version>
        </dependency>

此处的 hadoop.version 与 hadoop 版本对应,如

        <hadoop.version>2.6.5</hadoop.version>

如何对 namenode 容错?

namenode 中存储的是文件系统的整体信息及各个文件的元信息。如果它损坏了,就会导致文件系统上所有的数据就丢失了。因此对 namenode 容错非常重要, hadoop 提供了两种机制:

  1. 备份组成文件系统元数据持久状态的文件

    • 配置让 namenode 在多个文件系统上保存元数据的信息
    • 这种操作的特性是实时同步的,而且是原子操作
    • 实现方式就是在将信息写入本地磁盘的同时,写入一个 NFS
  2. 运行一个辅助的 namenode

    注意,这里的 namenode 并非是备用 (standby) namenode。

    它的作用就是定期合并编辑日志与命名空间镜像,从而防止编辑日志过大。并且会保存合并后的命名空间镜像的副本,并在 namenode 发生故障时使用。

    副作用就是辅助 namenode 保存的状态总是滞后于主节点,在主节点全部失效后,难免会丢失部分数据。要解决这个问题,可以将 1 中存储在 NFS 上的 namenode 元数据复制到辅助 namenode 作为新的主 namenode 运行。

如何更快的访问 datanode 中访问频繁的块?

对于 datanode 中访问频繁的 block, 就会被显式缓存在 datanode 的内存中,以堆外块缓存 (off-heap block cache) 的形式存在。这样做是为了更快的访问 datanode ,提高读操作性能。

用户或应用通过在缓存池(cache pool) 中增加一个 cache directive 告知 namenode 需要缓存哪些文件以及存放多久。cache pool 是一个用来管理缓存权限和资源使用的管理性分组。

如何扩展 namenode 以存储更多的文件?

由于 namenode 中会在内存中保存文件系统中的树信息和文件元信息。当文件太多时,就会导致文件系统的信息容量要大于 namenode 的内存限制。这种情况下,就不能再存储更多的文件了。因此需要对 namenode 进行横向扩展。 HDFS Federation 联邦 HDFS 就是用来解决这个问题的。

HDFS 中的高可用性设计

HDFS 在设计时的目标就是要可运行在商用廉价计算机集群上,而对于大型集群来说,节点故障率还是非常高的。这也就表示 HDFS 遇到节点故障时,能够自动处理这些故障,保持集群的继续运行,而不会让用户察觉到明显的中断。也就是 HDFS 的高可用性设计。

在上一节,我们了解到 HDFS 中最关键的节点就是 namenode,它保存了整个文件系统树以及文件,目录的元信息。如果它出现了 单点失效 SOFP (single point of failure) 的问题,那么 HDFS 中所有的功能包括 client 都无法执行任何读取,写入等操作了。因此, 一个 namenode 节点失效后,如何让集群保持继续运行,而且不会让用户察觉到是保证 HDFS 可用性的关键。

如何处理 namenode 单点失效问题?

namenode 配置成活动(active)—备用(standy)两个节点,当活动 namenode 节点失效后,备用 namenode 就会接管它的任务,并继续处理来自 client 的请求,这些过程对用户是透明的,并且不会有明显的中断。

要实现这个功能,需要在架构上做如下修改:

  • namenode 之间要实现编辑日志的共享
  • datanode 要同时向两个 namenode 发送块处理报告
  • client 要使用特定的机制来处理 namenode 失效问题,并且这一机制对用户是透明的
  • 备用 namenode 中包含辅助 namenode 的功能

namenode 间如何共享编辑日志?

namenode 之间需要通过高可用性共享存储来共享编辑日志。存储方案有两种:NFS 和群体日志管理器 QJM(quorum journal manager)。这里重点说明下 QJM。

QJM 就是为了专门提供高可用性编辑日志而设计的,被推荐在大多数 HDFS 部署中使用。QJM 是由一组 journal node 节点组成的,典型情况下是 3 个 journal 节点。每次编辑操作时,都会将日志写入到大多数节点。

namenode 如何能快速故障切换?

由于 datanode 同时向两个 namenode 发送块处理报告,并且 namenode 会将最新的编辑日志和数据块信息存储在内存中。当活动 namenode 失效后,备用 namenode 能够在大约几十秒的时间内就能实现任务接管。这个过程都是由故障转移控制器(failover controller) 来实现的。

failover controller 故障转移控制器管理着活动 namenode 转移为备用 namenode 的转换过程。默认会使用 zookeeper 来确保有且仅有一个活动 namenode。

每个 namenode 都运行着一个故障转移控制器,其工作就是通过一个心跳机制,监视 活动 namenode 是否失效,并且在 namenode 失效时自动进行故障切换。除了自动故障转移外,管理员也可以手动发起故障转移,将两个 namenode 节点互换角色。

如何规避非平稳故障转移?

假设一个 hadoop 集群中运行着两个 namenode 节点,节点 A 和 节点 B。此时A 会活动节点,B 为备用节点,两个节点都正常运行。

在某个时间点,由于网速非常慢或者网络被分割的原因,节点 B 的故障转移控制器无法通过心跳机制联系上节点 A,但此时节点 A 也在正常运行。

在经过几十秒的时间后,节点 B 就认为节点 A 已经失效,要切换为活动节点,但此时节点 A 也正在活动,这种情况就称为非平稳故障转移。

而此时我们要做的就是确保之前的活动节点 A 不再活动(不能同时出现两个活动节点),这种行为就称为“规避”。

对于 QJM 共享存储来说,规避方式就是同一时间 QJM 只允许一个 namenode 像编辑日志中写入数据。先前活动的节点访问 QJM 时, 会设置一个规避命令来杀死 该namenode。

小结

每一种技术实现都是和需求,背景息息相关的。只有理解了需求和背景,针对技术方案的改进看起来才能是那么的自然和必然。

文末附上根据 《hadoop 权威指南》整理的 HDFS 概念思维导图,从整体上梳理下HDFS 知识点脉络。

HDFS concept

参考文档

  1. Hadoop: The Definitive Guide

  2. HDFS Architecture