作者 | 大尊
hdfs 是 hadoop 的分布式文件系统,即 Hadoop Distributed Filesystem。下面主要讲下 HDFS 设计中的比较重要的点,使读者能通过简短的文章一窥 HDFS 的全貌,适合对 HDFS 有一点了解,但是对 HDFS 又感到困惑的初学者。本文主要参考的是 hadoop 3.0 的官方文档。
链接:http://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html
当数据集的大小超过了一台物理机所能存储的能力时,就需要将它进行分区并存储到若干不同的独立的计算机上,其中管理跨多台计算机存储的文件系统称为分布式文件系统。
目录
- 使用 HDFS 的场景
- HDFS 的工作模式
- 文件系统命名空间(namespace)
- 数据复制
- 文件系统元数据的持久化
- 通讯协议
- 健壮性
- 数据组织
- 可访问性
- 存储空间回收
1、使用 HDFS 的场景
HDFS 适合于以流式数据访问模式来存储超大的文件。即一次写入,多次读取,在数据集上长时间进行各种分析,每次分析都涉及该数据集数据的大部分甚至全部,对于超大文件,hadoop 目前以支持存储 PB 级数据。
HDFS 并不适合要求低时间延迟数据访问的应用,因为 HDFS 是为高数据吞吐量应用而优化的,这就有可能以时间延迟大为代价。
HDFS 文件系统所能存储的文件总数受限于 namenode 的内存容量,根据经验,100 百万的文件,且每个文件占一个数据块,那至少需要 300MB 的内存。
目前 hadoop 文件可能只有一个 writer, 而且写操作总是将数据添加在文件末尾,不支持在文件的任意位置进行修改。
相对于普通文件系统的数据块,HDFS 也有块的概念,默认是 128MB,HDFS 上的文件也被划分成块大小的多个分块,作为独立的存储单元,不过 HDFS 中小于一个块大小的文件不会占据整个块的空间。如果没有特别指出,文中提到的块特指 HDFS 的块。
为何 HDFS 的块如此之大,其目的是为了最小化寻址开销。这个数也不能设置的过大,mapreduce 中的 map 任务通常一次只处理一个块中的数据,因此如果任务数太少,作业的运行速度就会比较慢。
2、HDFS 的工作模式
HDFS 采用 master/slave 架构,即一个 namenode(管理者)多个 datanode(工作者)。
namenode 负责管理文件系统的命名空间。维护着文件系统树和整个树内所有的文件和目录,这些信息都保存在两个文件中,命名空间镜像文件和编辑日志文件。namenode 也记录了每个文件中各个块所在的数据节点信息。datanode 是文件系统的工作节点,它们需要存储并检索数据块(受客户端或 namenode 调度),并定期向 namenode 发送它们所存储的块的列表。
如果没有 namenode, 文件系统将无法使用,因为我们不知道如何根据 datanode 的块重建文件,所以对 namenode 进行容错是非常重要的。为此 hadoop 提供了两种机制。
第一种机制是备份那些组成文件系统元数据持久状态的文件。一般,在将持久化文件写入本地磁盘的同时,写入远程挂载的 NFS。
第二种方法是运行一个辅助 namenode,这个辅助 namenode 定期通过编辑日志合并命名空间镜像,并在本地保存合并后的命名空间镜像的副本,在 namenode 发生故障时启用。但是在主节点失效时,难免会丢失部分数据,这时可以把存储在 NFS 的 namenode 元数据复制到辅助的 namenode 上作为新的 namenode 运行。这其中涉及到故障转移的机制。稍后会做一点分析。
3、文件系统命名空间(namespace)
HDFS 支持传统的层次型文件组织结构。用户或者应用程序可以创建目录,然后将文件保存在这些目录里。
文件系统名字空间的层次结构和大多数现有的文件系统类似:用户可以创建、删除、移动或重命名文件。HDFS 支持用户磁盘配额和访问权限控制,目前还不支持硬链接和软链接。但是 HDFS 架构并不妨碍实现这些特性。
Namenode 负责维护文件系统的名字空间,任何对文件系统名字空间或属性的修改都将被 Namenode 记录下来。应用程序可以设置 HDFS 保存的文件的副本数目。文件副本的数目称为文件的副本系数,这个信息也是由 Namenode 保存的。
4、数据复制
HDFS 被设计成能够在一个大集群中跨机器可靠地存储超大文件。它将每个文件存储成一系列的数据块,除了最后一个,所有的数据块都是同样大小的。
为了容错,文件的所有数据块都会有副本。每个文件的数据块大小和副本系数都是可配置的。应用程序可以指定某个文件的副本数目。副本系数可以在文件创建的时候指定,也可以在之后改变。
HDFS 中的文件都是一次性写入的,并且严格要求在任何时候只能有一个写入者。
Namenode 全权管理数据块的复制,它周期性地从集群中的每个 Datanode 接收心跳信号和块状态报告(Blockreport)。当一个 Datanode 启动时,它会扫描本地文件系统,产生一个这些本地文件对应的所有 HDFS 数据块的列表,然后作为报告发送到 Namenode,这个报告就是块状态报告。接收到心跳信号意味着该 Datanode 节点工作正常。块状态报告包含了一个该 Datanode 上所有数据块的列表。
数据块列表获取。查看数据块的健康状态:hdfs fsck / -files -block 或者 hdfs fsck /
HDFS 的数据块存储在以_blk 为前缀名的文件中,每个块还有一个相关的带有.meta 后缀的元数据文件,元数据文件包括头部和该块各区段的一系列校验和。
当数据块的数量增加到一定规模时,datanode 会创建一个子目录来存放新的数据块及元数据信息。如果当前目录已经存储了 64 个(通过 dfs.datanode.numlocks 属性设置)数据块时,就创建一个子目录,终极目标是设计一颗高扇出的目录树。
如果 dfs.datanode.data.dir 属性指定了不同磁盘的多个目录,那么数据块会以轮转(round-robin)的方式写入到各个目录中。
在每个 datanode 上也会运行一个块扫描器,定期检测本节点上的所有块,从而在客户端读取到坏块之前就及时的检测和修复坏块。默认情况下每隔 3 周会测试块的状态,并对可能的故障进行修复。
用户可以通过 http://datanode:50070/blockScannerReport 获取该 datanode 的块检测报告。
副本存放
副本的存放是 HDFS 可靠性和性能的关键。优化的副本存放策略是 HDFS 区分于其他大部分分布式文件系统的重要特性。这种特性需要做大量的调优,并需要经验的积累。HDFS 采用一种称为机架感知 (rack-aware) 的策略来改进数据的可靠性、可用性和网络带宽的利用率。目前实现的副本存放策略只是在这个方向上的第一步。
通过一个机架感知的过程,Namenode 可以确定每个 Datanode 所属的机架 id。一个简单但没有优化的策略就是将副本存放在不同的机架上。这样可以有效防止当整个机架失效时数据的丢失,并且允许读数据的时候充分利用多个机架的带宽。这种策略设置可以将副本均匀分布在集群中,有利于当组件失效情况下的负载均衡。但是,因为这种策略的一个写操作需要传输数据块到多个机架,这增加了写的代价。
在大多数情况下,副本系数是 3,HDFS 的存放策略是将一个副本存放在本地机架的节点上,一个副本放在同一机架的另一个节点上,最后一个副本放在不同机架的节点上。这种策略减少了机架间的数据传输,这就提高了写操作的效率。
而在现实中,在 hadoop2.0 中,datanode 数据副本存放磁盘选择策略有两种方式:
第一种是沿用 hadoop1.0 的磁盘目录轮询方式,实现类:
RoundRobinVolumeChoosingPolicy.java
第二种是选择可用空间足够多的磁盘方式存储,实现类:AvailableSpaceVolumeChoosingPolicy.java
第二种策略对应的配置项是:
如果不配置,默认使用第一种方式,既轮询选择磁盘来存储数据副本,但是轮询的方式虽然能够保证所有磁盘都能够被使用,但是经常会出现各个磁盘直接数据存储不均衡问题,有的磁盘存储得很满了,而有的磁盘可能还有很多存储空间没有得到利用,所有在 hadoop2.0 集群中,最好将磁盘选择策略配置成第二种,根据磁盘空间剩余量来选择磁盘存储数据副本,这样一样能保证所有磁盘都能得到利用,还能保证所有磁盘都被利用均衡。
在采用第二种方式时还有另外两个参数会用到:
默认值是 10737418240,既 10G,一般使用默认值就行。官方解释为,首先计算出两个值,一个是所有磁盘中最大可用空间,另外一个值是所有磁盘中最小可用空间,如果这两个值相差小于该配置项指定的阀值时,则就用轮询方式的磁盘选择策略选择磁盘存储数据副本。
默认值是 0.75f,一般使用默认值就行。官方解释为,有多少比例的数据副本应该存储到剩余空间足够多的磁盘上。该配置项取值范围是 0.0-1.0,一般取 0.5-1.0,如果配置太小,会导致剩余空间足够的磁盘实际上没分配足够的数据副本,而剩余空间不足的磁盘取需要存储更多的数据副本,导致磁盘数据存储不均衡。
副本选择
为了降低整体的带宽消耗和读取延时,HDFS 会尽量让读取程序读取离它最近的副本。如果在读取程序的同一个机架上有一个副本,那么就读取该副本。如果一个 HDFS 集群跨越多个数据中心,那么客户端也将首先读本地数据中心的副本。
安全模式
Namenode 启动后会进入一个称为安全模式的特殊状态。处于安全模式的 Namenode 是不会进行数据块的复制的。Namenode 从所有的 Datanode 接收心跳信号和块状态报告。块状态报告包括了某个 Datanode 所有的数据块列表。每个数据块都有一个指定的最小副本数。
当 Namenode 检测确认某个数据块的副本数目达到这个最小值 (最小值默认是 1,由 dfs.namenode.replication.min 属性设置),那么该数据块就会被认为是副本安全(safely replicated) 的;在一定百分比(这个参数可配置, 默认是 0.999f, 属性值为 dfs.safemode.threshold.pct)的数据块被 Namenode 检测确认是安全之后(加上一个额外的 30 秒等待时间),Namenode 将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他 Datanode 上。
如果 datanode 丢失的 block 达到一定的比例,namenode 就会一直处于安全模式即只读模式。
当 namenode 处于安全模式时,该怎么处理?
找到问题所在,进行修复(比如修复宕机的 datanode)。
或者可以手动强行退出安全模式(没有真正解决问题):hdfs namenode –safemode leave。
在 hdfs 集群正常冷启动时,namenode 也会在 safemode 状态下维持相当长的一段时间,此时你不需要去理会,等待它自动退出安全模式即可。
用户可以通过 dfsadmin -safemode value 来操作安全模式,参数 value 的说明如下:
enter – 进入安全模式
leave – 强制 NameNode 离开安全模式
get – 返回安全模式是否开启的信息
wait – 等待,在执行某条命令前先退出安全模式。
5、文件系统元数据的持久化
Namenode 上保存着 HDFS 的名字空间。对于任何对文件系统元数据产生修改的操作,Namenode 都会使用一种称为 EditLog 的事务日志记录下来。例如,在 HDFS 中创建一个文件,Namenode 就会在 Editlog 中插入一条记录来表示;同样地,修改文件的副本系数也将往 Editlog 插入一条记录。Namenode 在本地操作系统的文件系统中存储这个 Editlog。
整个文件系统的名字空间,包括数据块到文件的映射、文件的属性等,都存储在一个称为 FsImage 的文件中,这个文件也是放在 Namenode 所在的本地文件系统上。
Namenode 在内存中保存着整个文件系统的名字空间和文件数据块映射 (Blockmap) 的映像(即 FsImage)。这个关键的元数据结构设计得很紧凑,因而一个有 4G 内存的 Namenode 足够支撑大量的文件和目录。
当 Namenode 启动时,或者检查点达到配置文件中的阀值,它从硬盘中读取 Editlog 和 FsImage,将所有 Editlog 中的事务作用在内存中的 FsImage 上,并将这个新版本的 FsImage 从内存中保存到本地磁盘上,然后删除旧的 Editlog,因为这个旧的 Editlog 的事务都已经作用在 FsImage 上了。这个过程称为一个检查点(checkpoint)。
hdfs dfsadmin -fetchImage fsimage.backup
// 手动从 namenode 获取最新 fsimage 文件,并保存为本地文件。
因为编辑日志会无限增长,那么恢复编辑日志的过程就会比较长,解决方案是,运行辅助 namenode, 为主 namenode 内存中的文件系统元数据创建检查点。最终主 namenode 拥有最新的 fsimage 文件和更小的 edits 文件。
这也解释了辅助 namenode 和主 namenode 拥有相近内存需求的原因(辅助 namenode 也需要把 fsimage 文件载入内存)。
创建检查点的触发条件受两个配置参数控制,
dfs.namenode.checkpoint.period 属性(辅助 namenode 每隔一段时间就创建检查点,单位 s)。dfs.namenode.checkpoint.txns, 如果从上一个检查点开始编辑日志大小达到多少的事务数时,创建检查点。
在主 namenode 发生故障时(假设没有备份),就可以从辅助的 namenode 上恢复数据。有两种实现方式。
方法一,将相关的存储目录复制到新的 namenode 中。
方法二,使用 -importCheckpoint 选项启动 namenode 守护进程,从而将辅助 namenode 用作新的主 namenode, 有个前提时,dfs.namenode.dir 属性定义的目录中没有元数据时。
6、通讯协议
所有的 HDFS 通讯协议都是建立在 TCP/IP 协议之上。客户端通过一个可配置的 TCP 端口连接到 Namenode,通过 ClientProtocol 协议与 Namenode 交互。而 Datanode 使用 DatanodeProtocol 协议与 Namenode 交互。
一个远程过程调用 (RPC) 模型被抽象出来封装 ClientProtocol 和 Datanodeprotocol 协议。在设计上,Namenode 不会主动发起 RPC,而是响应来自客户端或 Datanode 的 RPC 请求。
7、健壮性
HDFS 的主要目标就是即使在出错的情况下也要保证数据存储的可靠性。
常见的三种出错情况是:Namenode 出错, Datanode 出错和网络割裂(network partitions)。
心跳检测, 磁盘数据错误和重新复制。
每个 Datanode 节点周期性地向 Namenode 发送心跳信号。网络割裂可能导致一部分 Datanode 跟 Namenode 失去联系。Namenode 通过心跳信号的缺失来检测这一情况,并将这些近期不再发送心跳信号 Datanode 标记为宕机,不会再将新的 IO 请求发给它们。任何存储在宕机 Datanode 上的数据将不再有效。
Datanode 的宕机可能会引起一些数据块的副本系数低于指定值,Namenode 不断地检测这些需要复制的数据块,一旦发现就启动复制操作。
设置合适的 datanode 心跳超时时间,避免用 datanode 不稳定导致的复制风暴。
在下列情况下,也可能需要重新复制:某个 Datanode 节点失效,某个副本遭到损坏,Datanode 上的硬盘错误,或者文件的副本系数增大。
集群均衡(针对 datanode)
HDFS 的架构支持数据均衡策略。如果某个 Datanode 节点上的空闲空间低于特定的临界点,按照均衡策略系统就会自动地将数据从这个 Datanode 移动到其他空闲的 Datanode。
个文件的请求突然增加,那么也可能启动一个计划创建该文件新的副本,并且同时重新平衡集群中的其他数据。这个均衡策略目前还没有实现。
数据完整性(针对 datanode)
从某个 Datanode 获取的数据块有可能是损坏的,损坏可能是由 Datanode 的存储设备错误、网络错误或者软件 bug 造成的。HDFS 客户端软件实现了对 HDFS 文件内容的校验和 (checksum) 检查。
当客户端创建一个新的 HDFS 文件,会计算这个文件每个数据块的校验和,并将校验和作为一个单独的隐藏文件保存在同一个 HDFS 名字空间下。当客户端获取文件内容后,它会检验从 Datanode 获取的数据跟相应的校验和文件中的校验和是否匹配,如果不匹配,客户端可以选择从其他 Datanode 获取该数据块的副本。
元数据磁盘错误(针对 namenode 出错)
FsImage 和 Editlog 是 HDFS 的核心数据结构。如果这些文件损坏了,整个 HDFS 实例都将失效。因而,Namenode 可以配置成支持维护多个 FsImage 和 Editlog 的副本。任何对 FsImage 或者 Editlog 的修改,都将同步到它们的副本上。这种多副本的同步操作可能会降低 Namenode 每秒处理的名字空间事务数量。然而这个代价是可以接受的,因为即使 HDFS 的应用是数据密集的,它们也非元数据密集的。当 Namenode 重启的时候,它会选取最近的完整的 FsImage 和 Editlog 来使用。
另外一个可选方案是通过共享存储 NFS 或一个分布式编辑日志(也叫 journal)实现多 namenode 节点(HA),来增强故障恢复能力。
在 HDFS HA 的实现中,配置了一对 active-standby 的 namenode, 当活动的 namenode 失效,备用的 namenode 就会接管它的任务并开始服务于客户端的请求。
实现 HA 需要在架构上做如下修改:
namenode 之间通过高可用共享存储实现编辑日志的共享,当备用 namenode 接管工作之后,它将通读共享编辑日志直到末尾,实现与 active namenode 状态同步,并继续读取由活动 namenode 写入的新条目。
datanode 需要同时向两个 namenode 发送数据块处理报告,因为数据块映射信息存在 namenode 的内存,而非硬盘。
客户端使用特定的机制处理 namenode 的失效,这一机制对于用户是透明的。
辅助 namenode 的角色被 namenode 所包含,备用 namenode 为活动的 namenode 命名空间设置周期性检查。
快照
快照支持某一特定时刻的数据的复制备份。利用快照,可以让 HDFS 在数据损坏时恢复到过去一个已知正确的时间点。
8、数据组织
数据块
HDFS 被设计成支持大文件,适用 HDFS 的是那些需要处理大规模的数据集的应用。这些应用都是只写入数据一次,但却读取一次或多次,并且读取速度应能满足流式读取的需要。HDFS 支持文件的“一次写入多次读取”语义。一个典型的数据块大小是 128MB。因而,HDFS 中的文件总是按照 128M 被切分成不同的块,每个块尽可能地存储于不同的 Datanode 中。
流水线复制
当客户端向 HDFS 文件写入数据的时候,一开始是写到本地临时文件中。假设该文件的副本系数设置为 3,当本地临时文件累积到一个数据块的大小时,客户端会从 Namenode 获取一个 Datanode 列表用于存放副本。然后客户端开始向第一个 Datanode 传输数据,第一个 Datanode 一小部分一小部分 (4 KB) 地接收数据,将每一部分写入本地仓库,并同时传输该部分到列表中第二个 Datanode 节点。第二个 Datanode 也是这样,一小部分一小部分地接收数据,写入本地仓库,并同时传给第三个 Datanode。最后,第三个 Datanode 接收数据并存储在本地。
因此,Datanode 能流水线式地从前一个节点接收数据,并在同时转发给下一个节点,数据以流水线的方式从前一个 Datanode 复制到下一个。
9、可访问性
HDFS 给应用提供了多种访问方式。用户可以通过 Java API 接口访问,也可以通过 C 语言的封装 API 访问,还可以通过浏览器的方式访问 HDFS 中的文件。通过 WebDAV 协议访问的方式正在开发中。
DFSShell
HDFS 以文件和目录的形式组织用户数据。它提供了一个命令行的接口 (DFSShell) 让用户与 HDFS 中的数据进行交互。命令的语法和用户熟悉的其他 shell(例如 bash, csh)工具类似。下面是一些动作 / 命令的示例:
DFSAdmin
DFSAdmin 命令用来管理 HDFS 集群。这些命令只有 HDSF 的管理员才能使用。下面是一些动作 / 命令的示例:
浏览器接口
一个典型的 HDFS 安装会在一个可配置的 TCP 端口开启一个 Web 服务器用于暴露 HDFS 的名字空间。用户可以用浏览器来浏览 HDFS 的名字空间和查看文件的内容。
http://ip:50070
10、存储空间回收
文件的删除和恢复
当垃圾回收生效时,通过 fs shell 删除的文件并没有立刻从 HDFS 中删除。实际上,HDFS 会将这个文件重命名转移到 user//.Trash 目录。只要文件还在.Trash 目录中,该文件就可以被迅速地恢复。文件在 Trash 中保存的时间是可配置的,当超过这个时间时,Namenode 就会将该文件从名字空间中删除。删除文件会使得该文件相关的数据块被释放。注意,从用户删除文件到 HDFS 空闲空间的增加之间会有一定时间的延迟。
只要被删除的文件还在.Trash 目录中,用户就可以恢复这个文件。如果用户想恢复被删除的文件,他 / 她可以浏览.Trash 目录找回该文件。
减少副本系数
当一个文件的副本系数被减小后,Namenode 会选择过剩的副本删除。下次心跳检测时会将该信息传递给 Datanode。Datanode 遂即移除相应的数据块,集群中的空闲空间加大。同样,在调用 setReplication API 结束和集群中空闲空间增加间会有一定的延迟。
【本文为数澜用户原创内容,转载时必须标注文章的来源,文章链接,文章作者等基本信息】