1、HDFS架构
核心概念
namenode:负责执行文件namespace有关的操作,比如文件的重命名、移动、打开关闭文件目录等。同时namenode还负责元数据的存储,记录文件中各个数据块的位置信息。
datanode:负责提供来自客户端的读写请求,执行块的删除、创建操作。
namespace:HDFS的文件namespace与Linux类似,支持文件或者目录的创建、删除、移动、重命名等操作。支持配置用户和权限,但不支持软硬连接。namenode负责维护文件namespace,记录对文件namespace或其属性的任何修改。
namenode对namespace的管理采用了三种存储方式:
memory mete data:内存元数据,在namenode启动服务时会把fsimage从磁盘加载进内存,有了元数据就可以向客户端提供读写服务了。
fsimage:fsiamge是存储在磁盘中的元数据文件镜像,整个文件系统的元数据(包括所有目录和文件inode序列化消息)都永久保存在此文件中。
edit log:是数据操作日志文件,可以通过日志运算出元数据,客户端对HDFS中的文件进行新增修改删除之前,会把此操作行为记录到edit log中,再把操作成功代码发送给客户端,edit log需要通过checkpoint机制融合到fsimage中,以保证namenode中的数据是最新的。
secondary namenode:namenode 将对文件系统的修改存储为edit logs。当 namenode 启动时,它从fsimage 中读取元数据信息,然后应用edit logs。然后将新的 HDFS 状态写入 fsimage 并使用一个空的edit logs开始正常操作。由于 namenode 仅在启动期间合并 fsimage 和edit logs,edit logs可能会随着时间的推移在繁忙的集群上变得非常大。更大的编辑文件的另一个副作用是下一次重新启动 namenode 需要更长的时间。secondary namenode 通过定期合并 fsimage 和 edits log 文件的方式,并将 edits log 大小保持在一个限制内。它通常运行在与namenode 不同的机器上,目的就是为了减轻namenode的edit logs和fsimage时的压力。
checkpoint node:
namenode 使用两个文件来持久化namespace:
fsimage和 edits logs。当 namenode 启动时,它会合并 fsimage 以及edit logs以提供文件系统元数据的最新视图。 namenode 然后用新的 HDFS 状态覆盖 fsimage 并开始一个新的edit logs。
Checkpoint node会周期性地创建namespace的检查点。检查点触发时,它会从active namenode 下载 fsimage 和edit logs,在本地合并它们,并将最新的fsimage上传回active namenode。 Checkpoint 节点通常运行在与namenode 不同的机器上。
Checkpoint node将最新的检查点存储在与 namenode 目录结构相同的目录中,这样做可以方便namenode随时读取最新的检查点中保存的fsimage快照进行恢复。你可以在集群配置文件中指定多个检查点节点,防止单点故障。
Backup node:
Backup node提供与 Checkpoint node相同的检查点功能,并在其内存中维护namespace的最新副本,该副本始终与active namenode 状态同步。除了从 namenode 接收文件系统编辑的日志流并将其持久化到磁盘外,Backup node还将应用这些变更到它自己内存中的namespace副本中,从而创建namespace的备份。Backup node不需要从active namenode 下载 fsimage 和edit logs来创建检查点,而 Checkpoint node 或 secondary namenode 则需要这样,因为它已经具有namespace的最新状态在内存中。Backup node检查点过程更高效,因为它只需要将namespace保存到本地 fsimage 文件并重置edit logs即可。由于 Backup node在内存中维护namespace的副本,因此其 RAM 要求与 namenode 相同。namenode 一次支持一个备份节点。如果正在使用备份节点,则不能注册任何checkpoint node。
2、架构的稳定性保证
1、心跳和重新复制
每个datanode定期向namenode发送心跳信息,如果超过指定时间没有收到心跳信息,将datanode标记为死亡。namenode不会将任何新的IO请求发送到死亡的datanode上,也不会再使用这些datanode上的数据。由于数据不可用,可能会导致某些块的复制因子小于其指定值,namenode会跟踪这些块,并在必要的时候进行重新复制。
2、数据的完整性
存储在datanode上的数据可能发生损坏,为了避免读取到已经损坏的数据块,hdfs提供了完整的数据校验机制来保证读取数据的准确性。当客户端创建文件时,它会计算每个文件的数据块的校验和,并将校验和存储在同一hdfsnamespace下的隐藏文件中。当客户端检索文件内容时,它会验证从每个datanode读取到的数据的校验和是否匹配,匹配失败,说明数据块的文件已经损坏,会从其它datanode读取这个数据块的可用副本。
3、支持快照
支持在特定的时刻存储数据副本,以便在发生损坏的时候,回滚到健康的状态。
3、高可用
1、HDFS namenode 高可用整体架构
设计思想
在典型的 HA 集群中,两台或多台独立的机器被配置为 namenode。在任何时间点,只有一个 namenode 处于 Active 状态,而其他 namenode 处于 Standby 状态。 Active namenode 负责集群中的所有客户端操作,而 Standbys 只是充当工作人员,维护足够的状态以在必要时提供快速故障转移。
为了让备用节点保持其状态与活动节点同步,两个节点都与一组称为“JournalNodes”(JNs)的独立守护进程进行通信。当active namenode 执行任何修改时,它会将这些修改记录持久化到JN 中的大多数的节点上。 Standby 节点能够从 JN 读取编辑,并不断观察它们对编辑日志的更改。当standby namenode看到编辑修改时,它会将这些修改应用到自己的namespace。在发生故障转移时,standby namenode会确保在将自己提升为active namenode之前,已从 JournalNodes 读取所有编辑,这样可以确保namespace状态在发生故障转移之前完全同步。
为了提供快速故障转移,standby namenode还必须具有有关集群中块位置的最新信息。为了实现这一点,DataNodes配置了所有namenodes的位置,并向所有namenodes发送块位置信息和心跳。
一次只有一个 namenode 处于活动状态对于 HA 集群的正确操作至关重要。否则,namespace状态将在两者之间迅速分化,从而有数据丢失或其他不正确结果的风险。为了确保此属性并防止所谓的“脑裂情况”,JournalNodes 一次只允许一个 namenode 成为写入者。在故障转移期间,要变为活动的 namenode 将简单地接管写入 JournalNodes 的角色,这将有效地阻止其他 namenode 继续处于 Active 状态,从而允许新的 Active 安全地进行故障转移。
高可用架构组成
1、Active namenode 和 Standby namenode:两台 namenode 形成互备,一台处于 Active 状态,为主 namenode,另外一台处于 Standby 状态,为备 namenode,只有主 namenode 才能对外提供读写服务。
2、主备切换控制器 ZKFailoverController:ZKFailoverController 作为独立的进程运行,对 namenode 的主备切换进行总体控制。ZKFailoverController 能及时检测到 namenode 的健康状况,在主 namenode 故障时借助 Zookeeper 实现自动的主备选举和切换,当然 namenode 目前也支持不依赖于 Zookeeper 的手动主备切换。
具体职责如下:
- 健康监控——ZKFC 使用健康检查命令定期 ping 其本地 namenode。只要namenode及时响应健康状态,ZKFC就认为该节点是健康的。如果节点崩溃、冻结或以其他方式进入不健康状态,健康监视器会将其标记为不健康。
- ZooKeeper 会话管理——当本地 namenode 健康时,ZKFC 在 ZooKeeper 中保持一个打开的会话。如果本地 namenode 处于活动状态,它还会持有一个特殊的“锁”znode。这个锁使用了 ZooKeeper 对“临时”节点的支持;如果会话过期,锁定节点将被自动删除。
- 基于 ZooKeeper 的选举——如果本地 namenode 是健康的,并且 ZKFC 发现当前没有其他节点持有锁 znode,它将自己尝试获取锁。如果成功,则它“赢得了选举”,并负责运行故障转移以使其本地 namenode 处于活动状态。故障转移的过程类似于上面描述的手动故障转移:首先,如果有必要,之前的活动被隔离,然后本地namenode转换为活动状态。
3、Zookeeper 集群:为主备切换控制器提供主备选举支持。
具体职责如下:
- 故障检测——集群中的每个 namenode 机器都在 ZooKeeper 中维护一个持久会话。如果机器崩溃,ZooKeeper 会话将过期,通知其他 namenode 应该触发故障转移。
- 活动 namenode 选举 - ZooKeeper 提供了一种简单的机制来专门选举一个节点作为活动节点。如果当前活动的 namenode 崩溃,另一个节点可能会在 ZooKeeper 中获取一个特殊的排他锁,指示它应该成为下一个活动的。
4、共享存储系统:共享存储系统是实现 namenode 的高可用最为关键的部分,共享存储系统保存了 namenode 在运行过程中所产生的 edit logs。active namenode 和standby namenode 通过共享存储系统实现元数据同步。在进行主备切换的时候,新的主 namenode 在确认元数据完全同步之后才能继续对外提供服务。
5、DataNode 节点:除了通过共享存储系统共享 HDFS 的元数据信息之外,主 namenode 和备 namenode 还需要共享 HDFS 的数据块和 DataNode 之间的映射关系。DataNode 会同时向主 namenode 和备 namenode 上报数据块的位置信息。
2、namenode主备切换的实现
namenode 主备切换主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 这 3 个组件来协同实现:
ZKFailoverController 作为 namenode 机器上一个独立的进程启动 (在 hdfs 启动脚本之中的进程名为 zkfc),启动的时候会创建 HealthMonitor 和 ActiveStandbyElector 这两个主要的内部组件,ZKFailoverController 在创建 HealthMonitor 和 ActiveStandbyElector 的同时,也会向 HealthMonitor 和 ActiveStandbyElector 注册相应的回调方法。
HealthMonitor 主要负责检测 namenode 的健康状态,如果检测到 namenode 的状态发生变化,会回调 ZKFailoverController 的相应方法进行自动的主备选举。
ActiveStandbyElector 主要负责完成自动的主备选举,内部封装了 Zookeeper 的处理逻辑,一旦 Zookeeper 主备选举完成,会回调 ZKFailoverController 的相应方法来进行 namenode 的主备状态切换。
3、防止脑裂
Zookeeper 在工程实践的过程中经常会发生的一个现象就是 Zookeeper 客户端”假死”,所谓的”假死”是指如果 Zookeeper 客户端机器负载过高或者正在进行 JVM Full GC,那么可能会导致 Zookeeper 客户端到 Zookeeper 服务端的心跳不能正常发出,一旦这个时间持续较长,超过了配置的 Zookeeper Session Timeout 参数的话,Zookeeper 服务端就会认为客户端的 session 已经过期从而将客户端的 Session 关闭。”假死”有可能引起分布式系统常说的双主或脑裂 (brain-split) 现象。具体到本文所述的 namenode,假设 namenode1 当前为 Active 状态,namenode2 当前为 Standby 状态。如果某一时刻 namenode1 对应的 ZKFailoverController 进程发生了”假死”现象,那么 Zookeeper 服务端会认为 namenode1 挂掉了,根据前面的主备切换逻辑,namenode2 会替代 namenode1 进入 Active 状态。但是此时 namenode1 可能仍然处于 Active 状态正常运行,即使随后 namenode1 对应的 ZKFailoverController 因为负载下降或者 Full GC 结束而恢复了正常,感知到自己和 Zookeeper 的 Session 已经关闭,但是由于网络的延迟以及 CPU 线程调度的不确定性,仍然有可能会在接下来的一段时间窗口内 namenode1 认为自己还是处于 Active 状态。这样 namenode1 和 namenode2 都处于 Active 状态,都可以对外提供服务。这种情况对于 namenode 这类对数据一致性要求非常高的系统来说是灾难性的,数据会发生错乱且无法恢复。Zookeeper 社区对这种问题的解决方法叫做 fencing,中文翻译为隔离,也就是想办法把旧的 Active namenode 隔离起来,使它不能正常对外提供服务。
ActiveStandbyElector 为了实现 fencing,会在成功创建 Zookeeper 节点 hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb 的持久节点,这个节点里面保存了这个 Active namenode 的地址信息。Active namenode 的 ActiveStandbyElector 在正常的状态下关闭 Zookeeper Session 的时候 (注意由于/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb。但是如果 ActiveStandbyElector 在异常的状态下 Zookeeper Session 关闭 (比如前述的 Zookeeper 假死),那么由于/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 是持久节点,会一直保留下来。后面当另一个 namenode 选主成功之后,会注意到上一个 Active namenode 遗留下来的这个节点,从而会回调 ZKFailoverController 的方法对旧的 Active namenode 进行 fencing。
在进行 fencing 的时候,会执行以下的操作:
- 首先尝试调用这个旧 Active namenode 的 HAServiceProtocol RPC 接口的 transitionToStandby 方法,看能不能把它转换为 Standby 状态。
- 如果 transitionToStandby 方法调用失败,那么就执行 Hadoop 配置文件之中预定义的隔离措施,Hadoop 目前主要提供两种隔离措施,通常会选择 sshfence:
- sshfence:通过 SSH 登录到目标机器上,执行命令 fuser 将对应的进程杀死;
- shellfence:执行一个用户自定义的 shell 脚本来将对应的进程隔离;
4、共享存储
过去几年中 Hadoop 社区涌现过很多的 namenode 共享存储方案,比如 shared NAS+NFS、BookKeeper、BackupNode 和 QJM(Quorum Journal Manager) 等等。目前社区已经把由 Clouderea 公司实现的基于 QJM 的方案合并到 HDFS 的 trunk 之中并且作为默认的共享存储实现,本部分只针对基于 QJM 的共享存储方案的内部实现原理进行分析。为了理解 QJM 的设计和实现,首先要对 namenode 的元数据存储结构有所了解。
namenode 在执行 HDFS 客户端提交的创建文件或者移动文件这样的写操作的时候,会首先把这些操作记录在 EditLog 文件之中,然后再更新内存中的文件系统镜像。内存中的文件系统镜像用于 namenode 向客户端提供读服务,而 EditLog 仅仅只是在数据恢复的时候起作用。
namenode 会定期对内存中的文件系统镜像进行 checkpoint 操作,在磁盘上生成 FSImage 文件,FSImage 文件的文件名形如 fsimage_{end_txid} 表示这个 fsimage 文件的结束事务 id,例如上图中的 fsimage_0000000000000000020。在 namenode 启动的时候会进行数据恢复,首先把 FSImage 文件加载到内存中形成文件系统镜像,然后再把 EditLog 之中 FsImage 的结束事务 id 之后的 EditLog 回放到这个文件系统镜像上。
基于QJM实现的共享存储系统主要保存edit log而非fsimage文件,fsimage文件还是在namenode的本地磁盘上,QJM使用多个名为JournalNode 的节点组成的JournalNode 集群来存储edit log,每个JournalNode 存储相同的edit log副本,每当namenode向本地写edit log时,也会同时向JournalNode 集群中的每一个节点发送写请求,只要大多数的节点返回成功,就认为成功,一个2n+1的节点,按照最大数原则,最多同时容忍n台节点挂掉。
activate namenode向JournalNode 集群提交edit log后,standby namenode会定期从集群拉取处于 finalized 状态的 EditLog Segment,回放到内存中的文件镜像中,而不会把editlog再保存到本地磁盘上。虽然 Active namenode 向 JournalNode 集群提交 EditLog 是同步的,但 Standby namenode 采用的是定时从 JournalNode 集群上同步 EditLog 的方式,那么 Standby namenode 内存中文件系统镜像有很大的可能是落后于 Active namenode 的,所以 Standby namenode 在转换为 Active namenode 的时候需要把落后的 EditLog 补上来。
处于 Standby 状态的 namenode 转换为 Active 状态的时候,有可能上一个 Active namenode 发生了异常退出,那么 JournalNode 集群中各个 JournalNode 上的 EditLog 就可能会处于不一致的状态,所以首先要做的事情就是让 JournalNode 集群中各个节点上的 EditLog 恢复为一致。另外如前所述,当前处于 Standby 状态的 namenode 的内存中的文件系统镜像有很大的可能是落后于旧的 Active namenode 的,所以在 JournalNode 集群中各个节点上的 EditLog 达成一致之后,接下来要做的事情就是从 JournalNode 集群上补齐落后的 EditLog。只有在这两步完成之后,当前新的 Active namenode 才能安全地对外提供服务。
4、Observer namenode
在启用 HA 的 HDFS 集群中,有一个 Active namenode 和一个或多个 Standby namenode。 Active namenode 负责为所有客户端请求提供服务,而 Standby namenode 仅通过跟踪来自 JournalNode 的编辑日志以及块位置信息(通过接收来自所有 DataNode 的块报告)来保持有关namespace的最新信息。这种架构的一个缺点是 Active namenode 可能是一个单一的瓶颈,并且会因客户端请求而过载,尤其是在繁忙的集群中。
HDFS Observer namenode 的一致性读取功能通过引入一种名为 Observer namenode 的新型 namenode 来解决上述问题。与 Standby namenode 类似,Observer namenode 使自己保持最新的namespace和块位置信息。此外,它还具有提供一致性读取的能力。由于在大多数环境下都是读取请求占多数,Observer namenode有助于负载平衡 active namenode 流量并提高整体吞吐量。
在新架构中,一个 HA 集群可以由处于 3 种不同状态的名称节点组成:活动、备用和观察者。状态转换可以发生在 active 和 standby、standby 和 observer 之间,但不能直接发生在 active 和 observer 之间。
为了确保单个客户端内的读写一致性,在 RPC 标头中引入了状态 ID,该状态 ID 在 namenode 中使用事务 ID 实现。当客户端通过 Active namenode 执行写入时,它会使用来自 namenode 的最新事务 ID 更新其状态 ID。在执行后续读取时,客户端将此状态 ID 传递给 Observer namenode,然后 Observer namenode 将检查自己的事务 ID,并确保在为读取请求提供服务之前,自己的事务 ID 已赶上请求的状态 ID。这确保了来自单个客户端的“读己之写”语义。
edit log的延迟对于 Observer namenode 至关重要,因为它直接影响事务在 Active namenode 中应用和在 Observer namenode 中应用之间的延迟。为了解决这个问题,HDFS引入了一种名为“Edit Tailing Fast-Path”的机制,用以减少这种延迟。例如基于 RPC而不是 HTTP,JournalNode 上的内存缓存等。有关更多详细信息,请参阅 HDFS-13150。
5、HDFS Federation
single namenode
HDFS has two main layers:
- Namespace
-
- 由目录、文件和块组成。
- 它支持所有与名称空间相关的文件系统操作,例如创建、删除、修改和列出文件和目录。
- Block Storage Service
-
- Block Management (performed in the namenode)
-
-
- Provides Datanode cluster membership by handling registrations, and periodic heart beats.
- Processes block reports and maintains location of blocks.
- Supports block related operations such as create, delete, modify and get block location.
- Manages replica placement, block replication for under replicated blocks, and deletes blocks that are over replicated.
-
-
- Storage - is provided by Datanodes by storing blocks on the local file system and allowing read/write access.
之前的 HDFS 架构只允许整个集群使用一个namespace。在该配置中,单个namenode管理namespace。 HDFS Federation 通过向 HDFS 添加对多个namenode/namespace的支持来解决此限制。
Multiple namenodes/Namespaces
为了水平扩展名称服务,联邦使用多个独立的namenode/namespace。 namenodes之间是独立的,不需要相互协调。 Datanodes被所有namenodes用作块的公共存储。每个 Datanode 都向集群中的所有 namenode 注册。Datanode发送周期性心跳和块报告。它们还处理来自namenode的命令。
用户可以使用 ViewFs 创建个性化的namespace视图。 ViewFs 类似于某些 Unix/Linux 系统中的客户端挂载表。
pool是属于单个namespace的一组块。datanode存储集群中所有pool的块。每个pool都是独立管理的。这允许namespace为新块生成块 ID,而无需与其他namespace协调。namenode故障不会阻止datanode为集群中的其他namenode提供服务。
Namespace 和它的pool一起称为 Namespace Volume。它是一个独立的管理单元。当一个namenode/namespace被删除时,Datanodes对应的block pool也被删除。
使用HDFS联邦时,需要创建ClusterID,ClusterID标识符用于标识集群中的所有节点。格式化 namenode 时,将提供或自动生成此标识符。
HDFS联邦带来的好处:
- 扩展性:HDFS联邦增加了namespace水平扩展能力。通过允许将更多namenode添加到集群,来解决超大型集群或包含大量小文件的集群的namenode限制。
- 性能:HDFS的吞吐量不受单个namenode的限制。向集群中添加更多的 namenode 可以扩展HDFS的读/写吞吐量。
- 隔离:单个 namenode 在多用户环境中不提供隔离。例如,实验性的应用程序可能会使 namenode 过载并降低生产关键应用程序的速度。通过使用多个 namenode,可以将不同类别的应用程序和用户隔离到不同的namespace。