写点什么

Hadoop 之 HDFS 内部机制知多少?

用户头像
hanke
关注
发布于: 2021 年 01 月 26 日
Hadoop之HDFS 内部机制知多少?

在前一篇"Hadoop的MapReduce到底有什么问题"里,我们一起回顾了 MapReduce 内部机制和存在的问题。在本文中,主要讨论 Hadoop 里另外一个重要组件 HDFS 的架构和高可用相关机制。感兴趣的同学也可进一步阅读官方HDFS设计文档


HDFS 设计的目的就是分布式环境下海量数据的存储。其中最重要的目标就是:

  • 系统的高可用

  • 数据一致性

  • 高并发


HDFS 的架构与工作机制

HDFS 的架构图如下:


HDFS 主要由 Namenode 和 DataNodes 组成:

  • NameNode 职责:

* 扮演的是整个分布式存储的大脑角色。

* 存储 HDFS 所有的 metadata 信息,比如 Namespace 的名字,文件的 replicas 的个数等。

* 执行所有文件操作系统等的动作并向 DataNode 发相应的 Block 指令,比如打开、关闭、重命名、复制等操作。

* 负责 Block 和 DataNode 之间的 mapping 关系。

NameNode 的角色类似文件系统里的文件控制块角色,在 linux 里文件控制块会记录着文件权限、所有者、修改时间和文件大小等文件属性信息,以及文件数据块硬盘地址索引。 HDFS 的 Block Size 从 2.7.3 版本开始默认值从 64M 更改为 128M。


  • DataNodes 职责:

* 响应 Client 的读写请求。

* 执行来自 NameNode 的 block 操作请求,比如复制,删除,新建等命令。

* 负责向 NameNode 汇报自己的 Heartbeat 和 BlockReport。


HDFS 的 HA

  • 元数据方面

* NameNode 在 HDFS 里的重要性不言而喻,如果 NameNode 挂了或者元数据丢失了,那么整个 HDFS 也就瘫了,因此非常需要有 HA 机制。

* HDFS 采取的方案是: 主备双活NameNode + Zookeeper集群(Master选举) + Journal(共享存储)

  • 文件数据方面

* 数据通过replicas冗余来保证 HA。


更详细的信息可以参考文章HDFS的HA机制


主备 NameNode + 自动主备切换

HDFS 也可以通过手动切换主备,本文主要关注通过ZK进行辅助Master选举的方式进行主备切换。


建锁结点

当 NameNode 节点需要申请成为主结点时,需要通过 ZK 进行 Master 选举时,通过抢占在 ZK 里建立对应的锁结点。建立锁结点成功,那么说明选主成功。其中锁结点信息包括两部分:

  • 临时结点: /hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock

* 如果 ZK 在一定的时间内收到不到对应的 NameNode 的心跳,会将这个临时结点删掉。

  • 持久结点: /hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb

* 持久结点会在成为主结点是同时创建。建立持久结点的目的是为了 NameNode 和 ZK 之间通信假死带来脑裂问题。持久结点里会记录 NameNode 的地址。当发生脑裂时,下一个被选为主结点的 NameNode 会去查看是不是存在持久结点,如果存在,就会采取 Fencing 的措施,来防止脑裂问题。具体的 Fencing 方法有:

* 通过调用旧的 Active NameNode 的 HAServiceProtocolRPC 来去 transition 旧的 NameNode 为 StandBy 状态。

* 通过 SSH 方式登录到对应的 NameNode 机器上 Kill 掉对应的进程。

* 执行用户自定义的 Shell 脚本去隔离进程。


注册 Watch 监听

当 NameNode 申请成为主结点失败时,会向 ZK 注册一个监听事件,来监听对应的锁节点的目录变化,当然主要监听的是 NodeDelete 事件,会用来触发自动主备切换事件。


自动主备切换

NameNode 的自动主备切换主要由ZKFailoverController, HealthMontiorActiveStandbyElector这 3 个组件来协同实现。

  • ZKFailoverController启动时会创建 HealthMonitor 和 ActiveStandbyElector 两个组件,并向这两个组件注册对应的回调方法。

  • HealthMonitor主要是用来监控 NameNode 的健康状态,如果检测到有状态变化,会调用回调函数来通知 ZKFailoverController 进行自动的主备选举。

  • ActiveStandbyElector主要是负责和 ZK 交互, 里面封装了 ZK 相关的处理逻辑,当 ZK master 选举完成,会回调 ZKFailoverController 的相应方法来进行 NameNode 的主备状态切换。


具体的主备切换流程如下(可参考上面的 HA 图):

  • Active NameNode

① Active NameNode 上的 HealthMonitor 发现 NameNode 上状态发生变化,比如没有响应。

② 通过回调 ZKFailoverController 函数通知。

③ ZKFailoverController 收到通知后会调用 ActiveStandbyElector 去删除掉在 ZK 集群上创建的锁结点。对于正常情况下关闭的 Active NameNode,也会将持久锁结点一并删除。

④ ActiveStandbyElector call ZK 集群删除对应的锁结点。

⑤ 当删除结点成功后,AcitveStandbyElector 会回调 ZKFailoverController 方法进行通知。

⑥ ZKFailoverController 会去将 Active NameNode 的状态切换为 Standby。


  • Standby NameNode

① Standby NameNode 在第一次主备竞选时在 ZK 建立锁结点失败时会注册 Watch 监听。

② 当 Active NameNode 进行主备切换删除锁结点,NodeDelete 的事件触发 Standby NameNode 的 ActiveStandByElector 的自动创建锁结点,申请成为主结点的动作。

③ 当申请主结点被 ZK 通过后,会回调 ZKFailoverController 进行 NameNode 的状态切换。

④ ZKFailoverController 调 NameNode 方法将状态从 Standby 更新为 Active。

⑤ NameNode 从 Journal 集群里 Sync 最新元数据 EditLog 信息。

⑥ 当所有的元数据信息整体对齐后,此时的 NameNode 才会真正对外提供服务。


以上是正常情况下的主备切换流程。当 Active NameNode 整个机器宕机,或者和 ZK 失去通信后,根据 ZK 临时节点的特性,锁节点也会自动删除,自动触发主备切换。


脑裂和 Fencing

Zookeeper 的工程实践里会经常出现“假死”的情况,即客户端到服务端的心跳不能正常发出,通讯出现问题。这样当超时超过设置的 Session Timeout 参数时,Zookeeper 就会认为客户端已经挂掉了,会自动关闭 session,删除锁节点,从而引发分布式系统里的双主或者脑裂的情况。比如 HDFS 里,会触发自动的主备切换,而实际上原来的 Active NameNode 还是好的,这样就存在两个 Active NameNode 在工作。


HDFS HA 里解决脑裂问题就是在 ZK 里建立持久结点通过 Fencing 机制,可以阅读持久结点

具体到主备切换机制里,当 Standby 结点在② 时,会发现 ZK 上存在永久锁结点,那就会采取 Fencing 机制。当成功将原来的 Active NameNode 隔离(Kill 或者进程隔离等),才会真正去 call ZKFaioverController 进行状态切换。


Journal 共享存储元数据

  • Active NameNode 向 Journal 集群结点同步写 EditLog 元数据,具体可参考元数据的高并发修改部分。

  • 而 Standby NameNode 则是定时从 Journal 集群同步 EditLog 元数据到本地。

  • 在发生 NameNode 主备切换的时候,需要将 Standby 的 NameNode 的元数据同 Journal 集群结点的信息完全对齐后才可对外提供数据。


Journal 本身也是分布集群来通过 Paxos 算法来提供分布式数据一致性的保障。只有多数据结点通过投票以后才认为真正的数据写成功。


元数据保护

  • 可以通过维护多份 FSImage(落盘) + EditLog 副本来防止元数据损坏。


HDFS 的数据一致性

元数据一致性

  • 主备双活 NameNode 之间的元数据

* 通过 Journal 共享存储 EditLog,每次切换主备时只有对齐 EditLog 以后才能对外提供服务。

  • 内存与磁盘里元数据

* 内存里的数据 = 最新的FSImage + EditLog

* 当有元数据修改时,往内存写时,需要先往 EditLog 里记录元数据的操作记录。

* 当 EditLog 数据满了以后,会将 EditLog 应用 FSImage 里并和内存里的数据做同步,生成新的 FSImage,清空 EditLog。


数据一致性

HDFS 会对写入的所有数据计算校验和(checksum),并在读取数据时验证。

  • 写入的时候会往 DataNode 发 Checksum 值,最后一个写的 DataNode 会负责检查所有负责写的 DataNode 的数据正确性。

  • 读数据的时候,客户端也会去和存储在 DataNode 中的校验和进行比较。


HDFS 高并发

元数据的高并发修改

主要的流程图如下:


参考博文


主要的过程:

当有多个线程排除申请修改元数据时,会需要经过两阶段的对元数据资源申请加锁的过程。

  • 第一次申请锁成功的线程,会首先生成全局唯一且递增的 txid 来作为这次元数据的标识,将元数据修改的信息(EditLog 的 transaction 信息)写入到当下其中一个 Buffer 里(没有担任刷数据到磁盘的角色的 Buffer 里)。然后第一次快速释放锁。

  • 此时前一步中的线程接着发起第二次加锁请求:

* 如果请求失败(比如现在正在有其他的线程正在写 Buffer)会将自己休眠 1s 然后再发起新的加锁请求。

* 如果第二次请求加锁成功,会先 check 是否有线程正在进行刷磁盘的操作:

* 如果是,那么就快速释放第二次加锁然后再把自己休眠等待下次加锁请求(因为已经有人在刷磁盘了,为了不阻塞其他线程写 Buffer,先释放锁信息)。

* 如果不是,那么会接着 check 是否自己的 EditLog 信息已经由在后面的其他线程刷进磁盘里:

* 如果是,那么就直接释放第二次加锁请求直接线程退出,因为不再需要它做任何事情;

* 如果还没刷进去,那么就由该线程担任起切换 Buffer 并刷数据到磁盘和 Journal 集群结点的重任。在切换 Buffer 以后,该线程会进行第二次释放锁的动作,这样其他线程可以继续往切换后的 Buffer 写数据了。在慢慢刷数据到本地磁盘或者通过网络刷数据到 Journal 结点的过程中,不会阻塞其他线程同时的写请求,提高并发量。


主要的方法:

  • 分段加锁机制 + 内存双缓冲机制

* 分段加锁是指:

* 第一阶段是在写内存缓冲区的申请对修改加锁。

* 第二段是在申请刷缓冲区的数据到磁盘、Journal 集群资格的时候申请加锁。

* 整个过程中只有一个锁,保护的元数据的资源。当开始刷数据时,会立刻释放锁,不会阻塞后续其他往内存缓冲区写数据的线程。

* 内存双缓存:

* 缓冲 1 用来当下的写入 Log。

* 缓冲 2 用来读取已经写入的刷到磁盘和 Journal 结点。

* 两个缓存会交换角色(需要时机判断)

  • 缓冲数据批量刷磁盘+网络优化

  • 多线程并发吞吐量支持


Reference


更多大数据相关分享,可在微信公众号搜索“数据元素”或扫描下方二维码。



发布于: 2021 年 01 月 26 日阅读数: 740
用户头像

hanke

关注

凡是过往,皆为序章 2019.09.11 加入

热爱大数据技术沉淀和分享,致力于构建让数据业务产品更易用的大数据生态圈,为业务增值。

评论 (2 条评论)

发布
用户头像
这是用什么工具画的图?
2021 年 02 月 04 日 13:28
回复
draw.io
2021 年 02 月 05 日 10:54
回复
没有更多了
Hadoop之HDFS 内部机制知多少?