如果你也是一名大数据方向的工作者, 那么就一定听说过分布式文件系统(HDFS). 本文将介绍 HDFS,详细阐述其中的重要概念、体系架构、存储原理和读写过程, 如果你希望从零开始了解这个系统, 那么可以阅读本篇文章.
为了解决大数据量下的高效存储问题, 谷歌开发了分布式文件系统(Google File System, GFS), 通过网络实现文件在多态及其上的分布式存储. 而 Hadoop 分布式文件系统(Hadoop Distributed File System, HDFS)则是针对 GFS 的开源实现, 和 MapReduce 一起成为 Haddop 两大核心组成部分. 总的而言, HDFS 实现了以下目标:
- 兼容廉价的硬件设备: HDFS 将硬件节点失效的情况视为"正常"情况, 设计了快速检测应急教案故障和自动恢复的机制. 也开源持续监视, 容错处理等. 可以实现在硬件出错的情况下也能实现数据的完整性.
- 流数据读写
- 大数据集
- 简单的文件模型: HDFS 采用"一次写入、多次读取"的简单文件模型, 文件一旦完成写入并关闭后就只能读取.
- 强大的跨平台兼容: HDFS 采用 Java 语言实现, 具有良好的跨平台兼容, 任何支持 JVM 的机器都可以运行 HDFS.
但是 HDFS 也有一定的局限性:
- 不适合低延迟的访问: HDFS 主要面对大规模批量处理的数据, 采用流式数据读取, 具有很高的吞吐率, 但是同时意味着有较高的延迟.
- 无法高效存储大量小文件: HDFS 一个块的较大, 而文件大小如果小于一个块则无法高效存储. 具体原因在文章后面有具体的讨论.
- 不支持多用户写入以及任意修改文件: HDFS 只允许一个文件的写入者, 不允许多个用户对同一个文件进行写操作. 并且无法执行随机写操作, 只能追加.
HDFS 中有几个非常关键的概念, 包括: 块、名称节点、数据节点、第二名称节点.
HDFS和传统文件系统一样, 将文件分为了不同的块, 默认的一个块的大小是 64 MB. 可以看出相对于传统文件系统的几千字节, HDFS 的块要大得多. 这么做的优势是可以 最小化寻址开销. 采用抽象的块概念的好处:
- 支持大规模文件存储: 大规模文件被分割成不同的块, 可以分别存储在不同的机器上, 不受节点本地存储容量的限制.
- 简化系统设计: 首先, 因为块的大小是固定的, 简化了存储管理. 其次, 元数据与文件库不需要一起存储, 方便了元数据管理.
- 适合数据备份
在 HDFS 中有两种节点: 名称节点(Name Node) 和 数据节点(Data Node).
名称节点负责管理分布式文件系统的命名空间(Namespace), 保存了两个核心的数据结构: FsImage 和 EditLog. 其中 FsImage 用于维护文件系统树以及文件树中所有的文件和文件夹的元数据. EditLog 中记录了所有针对文件的创建、删除、重命名等操作.
数据节点是 HDFS 的工作节点, 负责数据的存储和读取, 会根据客户端或者名称节点的调度来进行数据的存储和检索, 并向名称节点定期发送自己存储的块的列表. 每个数据节点的数据会被保存在各自节点的本地 Linux 文件系统中.
FsImage 和 EditLog 是如何工作的?
名称节点在启动时会将 FsImage 的内容加载进入内存, 然后执行 EditLog 中的所有操作使得内存中的所有元数据保持最新的状态. 在完成这一切后, 名称节点会创建一个新的 FsImage 文件和一个空的 EditLog 文件. 此时名称节点启动成功, 进入正常工作状态.
此后 HDFS 的更新操作都会写入 EditLog. 不写入 FsImage 的原因是 FsImage 的大小通常很大(一般在 GB 以上), 如果所有更新操作都写入 FsImage 的话更新操作会很缓慢. 而 EditLog 通常非常小, 可以高效写入.
我们称名称节点在启动过程中加载 FsImage 和 EditLog 的过程为 "安全模式"
, 只能对外提供读操作, 无法提供写操作. 启动结束后, 退出"安全模式", 进入正常运行状态后对外提供读写操作.
前面已经解释过 FsImage 和 EditLog 的作用, 显然, 随着名称节点不断运行, EditLog 也会变得越来越大, 使得写入的过程变得异常缓慢. 不仅如此, 当节点重启时需要重新执行所有的 EditLog 中的操作, EditLog 如果过大会导致名称节点处于"安全模式"的时间过长, 无法正常对外界提供写操作, 影响用户的正常使用.
为了有效解决 EditLog 过大带来的一系列问题, HDFS 在设计中采用了 第二名称节点(Secondary NameNode). 它具有两个功能:
- 完成 EditLog 和 FsImage 的合并. 减少 EditLog 的大小, 缩短名称节点重启时间.
- 作为名称节点的"检查点", 保存名称节点的元数据信息.
我们现在逐一解释这两个功能:
EditLog 和 FsImage 的合并
- 每隔一段时间, 第二名称节点会和名称节点通信, 请求其停止 EditLog 文件(假设此刻为 t1). 此时名称节点会暂时将新达到的写操作添加到新文件
EditLog.new
中. - 第二名称节点把名称节点中的 FsImage 文件和 EditLog 文件拉回到本地, 再加载到内存中并在内存中对这两个文件进行合并操作, 即在内存中逐一执行 EditLog 使得 FsImage 处于最新状态.
- 合并结束后, 第二名称节点会把合并后的 FsImage 文件发回名称节点.
- 名称节点会用新的 FsImage 替换原先旧的 FsImage 文件, 同时用
EditLog.new
替换 EditLog(假设此刻为 t2).
通过上述的操作我们有效地减少了 EditLog 的大小.
作为名称节点的检查点
从上面的合并操作中我们可以得知: 第二名称节点会定期与名称节点通信并拉取 EditLog 和 FsImage 文件. 所以我们可以将第二名称节点看作是名称节点的"检查点", 周期性地备份名称节点中的元数据信息. 当名称节点发生故障, 我们可以使用第二名称节点中的元数据进行系统恢复.
但是显然, 在第二名称节点合并操作得到新的 FsImage 文件并不包含 t1 到 t2 时刻之间发生的更新操作, 所以如果名称节点在 t1 到 t2 时刻之间发生故障, 系统就会丢失一部分元数据信息. 在 HDFS 中也不支持系统直接切换到第二名称节点, 所以第二名称节点只是一个检查点, 并不会达到热备份的效果, 名称节点的元数据仍然有数据丢失的风险.
在本小节我们将简要介绍 HDFS 的体系结构, 然后介绍命名空间管理、通信协议、客户端, 最后会讨论 HDFS 体系的局限性.
HDFS 采用的是一个主从结构(Master/Slave)模型, 一个 HDFS 集群包含一个名称节点(有且仅有一个)和若干个数据节点.
名称节点作为中心服务器, 负责管理文件系统的命名空间以及客户端对文件的访问. 数据节点一般是一个节点运行一个数据节点进程, 负责文件系统客户端的读写请求, 在名称节点的统一调度下进行数据块的创建、删除和复制等操作. 每个数据节点的数据事实上保存在其本地的 Linux 文件系统中. 数据节点会向名称节点发送心跳来报告自己的状况, 没有按时发送心跳的数据节点会被标注为"宕机", 不会再分配任何 I/O 请求.
在系统内部, 文件会被切分为若干个数据块, 这些数据块会被分布到若干个数据节点上. 当客户端需要访问一个文件时, 首先把文件名发送给名称节点, 名称节点会根据文件名找到对应的所有数据块, 再根据每个数据块的信息找到实际存储这些数据块的数据节点, 并把数据节点位置返还给客户端. 最后客户端直接访问这些数据节点获取数据. 可以看到, 在这个过程中名称节点并不直接参与数据的传输.
HDFS的命名空间包括: 目录、文件和块. 命名空间管理是指: 命名空间支持对HDFS中的目录、文件和块进行类似文件系统的创建、修改和删除等基本操作.
当前的 HDFS 体系结构中, 整个 HDFS 集群只有一个命名空间, 并且只有唯一一个命名节点, 该节点负责对这个命名空间进行管理.
局限
目前 HDFS 还未实现磁盘配额和文件访问权限等功能, 也不支持文件的硬链接和软连接.
HDFS 的通信协议都是构建在 TCP/IP 协议上的. 客户端通过一个可配置的端口向名称节点发起 TCP 链接, 并使用客户端协议与名称节点进行通信. 名称节点和数据节点之间则使用数据节点的协议进行通信. 客户端与数据节点使用 RPC(Remote Procedure Call)来实现.
客户端是用户操作 HDFS 最常用的方法, 不过严格来说客户端不算是 HDFS 的一部分. 可以支持打开、读取、写入等常见操作, 也可以使用 shell 命令来访问数据.
HDFS 只有一个名称节点在简化系统设计的同时带来了一些问题:
- 命名空间的限制: 名称节点是保存在内存中的, 受到硬件内存的限制.
- 性能瓶颈: 整个分布式系统的吞吐量受到单个名称节点的吞吐量的限制.
- 隔离问题: 由于集群中只有一个名称节点, 只有一个命名空间, 无法对不同的程序进行隔离.
- 集群可用性: 一旦名称节点发生故障, 会导致整个集群不可用.
为了保证系统的容错性和可用性, HDFS 采用了多副本方式对数据进行冗余存储, 通常一个数据块的多个副本会被分布到不同的数据节点上. 这种多副本方法有以下优点:
- 加快数据传输: 当多个客户端需要访问同一个文件时, 可以让客户端从不同的副本中读取数据.
- 容易检查数据错误.
- 保证数据可靠性.
数据的存取策略是分布式文件系统的核心内容, 很大程度上会影响到整个分布式文件系统的读写性能.
HDFS 采用了以机架为基础的数据存放策略.
节点在同一个机架和不同机架的区别
不同机架之间的数据通信需要经过交换机或者路由器, 同一个机架中不同机器之间的通信不需要经过交换机和路由器.
这意味着同一个机架中不同机器之间的通信要比不同机架之间的通信带宽大.
HDFS 默认策略是每个数据节点都在不同的机架上. 这样做有优点也有缺点:
优点
- 高可靠性. 一个机架出现故障可以使用其他机架上的副本.
- 读取速度高. 多个副本可以并行读取.
- 更容易实现负载均衡和错误处理.
缺点
写入数据时无法充分利用同一个机架内部的带宽.
HDFS 默认的复制因子是 3, 每个文件块都会被同时复制到三个地方, 其中有两个副本放在同一个机架的不同机器上, 第三个副本放在不同机架的机器上.
数据读取 HDFS 提供了一个 API 可以确定一个数据节点所属的机架 ID, 客户端可以调用 API 获取自己所属的机架 ID. 当客户端读取数据时, 从名称节点获取数据块不同副本的存放位置列表, 可以调用 API 来确定客户端和这些数据节点所属的机架 ID. 如果在同一个机架, 则可以优先读取该副本.
数据复制 HDFS 数据复制采用了流水线复制的策略. 当客户端写入一个文件时:
首先文件被写入本地, 并被切分成不同的数据块.
每个块都向名称节点发起写请求.
名称节点根据数据节点使用情况选择一个数据节点列表返回给客户端.
客户端将数据首先写入列表的第一个数据节点, 并将列表传递给第一个数据节点.
第一个数据节点接受到 4 KB 数据时, 写入本地, 并向列表的第二个数据节点发起连接请求, 将自己已经接受到的 4 KB 数据传给第二个数据节点.
第二个数据节点也以此类推, 在接收到 4 KB 时向第三个数据节点进行请求, 形成流水线.
当文件写完的时候, 数据复制也会同时完成.
HDFS 拥有非常高的容错性, 使得它可以兼容廉价的硬件设备. HDFS将硬件设备出错堪称时一种常态而非异常, 并设计了相应的机制检测数据错误和进行自动恢复. 分为以下三种情况:
- 名称节点出错 名称节点保存着所有的元数据信息, 其中包含最核心的 EditLog 和 FsImage, 如果发生损坏则整个 HDFS 实例失效.
Hadoop 采用两种机制来确保名称节点的安全:
把名称节点的元数据信息同步存储到其他文件系统. 比如远程挂载 NFS.
运行一个第二名称节点.
数据节点出错 每个数据节点会定期向名称节点发送"心跳"信息, 向名称节点报告自己的状态. 当数据节点发生故障或者网路出现断网时, 名称节点会把收不到"心跳"的数据节点标记为"宕机", 节点上所有的数据都标记为"不可读", 名称节点后续不会再发送任何 I/O 请求.
而标注完"宕机"后, 会有部分数据块的副本数量小于冗余因子, 此时名称节点会定期检测这种情况, 一旦发现则会启动数据冗余复制.
- 数据出错 网络传输和磁盘都会导致数据错误. 客户端在读取数据后会采用 md5 和 shal 对数据进行校验, 以确保读到的时正确的数据. 而此处校验的信息是客户端在文件被创建的时候写入同一个路径的隐藏文件夹下的.
当客户端校验出错, 客户端会请求其他数据节点的数据块副本, 并向名称节点报告该数据块有错误, 名称节点会定期检查并重新复制这个数据块.