看过了 Hdfs 和 Yarn 的源码,发现两者的系统设计完全不同,根本不像是同一个 Project 的 Module,觉得很有必要对这两个 Module 源码的系统设计做一次分析。
我私下里认为两者源码系统设计之所以不同的原因无非是:
- Hdfs Module 出现的时间较早,整个 Module 的系统设计也是在开源之初就已经确定完成,之后的版本迭代中,都是基于原版 Hdfs 的代码逐步演进的,因此他的整个系统设计比较粗糙,偏实用型,结构大而全。
- 在 Yarn 之前, Hadoop 还有一个 ResourceManager 组件,叫做 JobTracker, Yarn 是后续开发人员重新设计的 ResourceManager,因此 Yarn 在设计之初,整个 Project 已经处于社区维护阶段,更由于全新建设的原因,扔掉了 JobTracker 的历史包袱,他的系统设计相对比较良好,拥有服务化和消息总线两把神兵利器。
言归正传,我们回到 Yarn 和 Hdfs 的系统设计本身,虽然 Yarn 和 Hdfs 两个 Module 各自有自身不同的多种节点,但是同一个 Module 中的节点的系统设计大致一致。在这里我只针对 Yarn 中的 ResourceManager
和 Hdfs 中的 NameNode
两者进行系统设计分析,其余节点可以参考着对应 Module 进行查看,不再做一一分析。
NameNode 的系统设计
系统结构
NameNode 作为 Hdfs 的文件系统管理节点,他的整体系统设计可以用下图进行表示
整个 NameNode 可以总结为上图。
最上方是两个对外暴露服务,一个是 HttpServer 负责提供 Http 服务展示当前的节点状态,一个是 RPCServer 负责集群间的内部通信。
第二层是一个中介者,负责响应 HttpServer 和 RpcServer 的请求,一般而言所有的请求操作都会通过 FSNamesystem 再向下分发。
第三层是具体的业务模块,主要由 BlockManager 和 FSDirectory,MetricsSystem 构成。其中 BlockManager 负责维护 DataNode 的相关信息,并持有 DataNode 所拥有的 Block 列表;FSDirectory 则负责维护当前 NameNode 的文件系统;MetricsSystem 负责记录一些行为操作细节,例如 gc 时间,节点数量等。
最下方是几个底层模块,上图中我也只列举出了部分常用的 Manager,业务模块通过对底层模块的调用实现其业务逻辑。
启动逻辑
在之前的章节 中有简单提过 Hdfs 的启动流程。
protected void initialize(Configuration conf) throws IOException {
// 启动 MetricsSystem
NameNode.initMetrics(conf, this.getRole());
if (NamenodeRole.NAMENODE == role) {
// 启动 HttpServer
startHttpServer(conf);
}
// 启动 FSNamesystem
loadNamesystem(conf);
// 启动 RpcServer
rpcServer = createRpcServer(conf);
}
在入口函数 NameNode::main
中,我们看到对于各个模块的加载是完全放在 NameNode::initialize
中进行的。
这样的做法自然是足够简单,但和 Yarn 的设计比起来就显得略微粗糙了。
Yarn 的系统设计
本文的主要介绍会在 Yarn 上,作为 Hadoop 2.x 才推出的 ResourceManager,它的系统设计相较于 Hadoop 有了很大的提升。
系统结构
上图中列出了 ResourceManager 中的一些关键组件,这些组件都继承了 Service 接口,也就是说在 ResourceManager 中的各个组件都已经 Service 化了。
ResourceManager 中默认实现了 HA 逻辑,当同时存在多个 ResourceManager 时,会通过内置的 EmbeddedElector 进行主从选举。默认的 EmbeddedElector 是 ActiveStandbyElectorBasedElectorService,在默认实现类中,同 zkfc 共享同一个主从选举逻辑,具体选举逻辑在上一篇文章中有对应介绍: Hadoop 源码学习笔记(6)--Hdfs 的备份,高可用和横向扩展。选举结束之后,只有 ActiveResourceManager 才能够启动 RMActiveServices。
而 RMActiveServices 是多个 Service 组件组成的一个集合 Service,这些 Service 负责维护当前集群中的 NodeManager 和正在运行的 App 信息。只有启动了 RMActiveServices 的 ResourceManager 才能够接收 Client 提交的 App 信息,并通过 ApplicationMasterLauncher 在 NodeManager 中开启对应的 ApplicationMaster,进入任务处理流程。
Service 化
Service 是 Yarn 中抽象出的一种组件生命周期,由 init、start、stop 三个接口方法,如方法名所示,分别对应服务的初始化、启动和终止三种行为。
在 Yarn 中,系统将每一个组件抽象化成一个 Service,若涉及到多个 Service 聚合使用,则会通过 addService 和 addIfService 方法将多个 Service 聚合到同一个 Service 中,并共享上级 Service 的生命周期。
通过生命周期共享这一策略,可以确保由 ResourceManager 启动的所有 Service 共同享有一套生命周期,上层的 Service 通过分发消息给下层的 Service,确保所有 Service 按需启动和退出。
消息总线
在 Hdfs 中,事件的分发是直接通过对对象的调用进行直接操作的,因此追踪相对简单,方便源码走读。
但是在 Yarn 中,所有事件分发都是通过消息总线完成的。在 ResourceManager 或者 NodeManager 中均会启动一个 AsyncDispatcher 对象,负责接收并分发事件。
Service 通过向 AsyncDispatcher 中 register EventHandler,可以将 EventType 和 EventHandelr 一一对应的注册进入 AsyncDispatcher 中。当使用 dispatch 进行事件分发时,根据对应的 Event,选取对应 EventHandler 进行事件消费。
通过消息总线机制,只要获取到了 AsyncDispatcher 对象,就可以对任意事件进行监听操作。这对服务管理来说是比较方便的,我们可以保证所有事件分发均运行在同一线程,并且可以在任意位置进行事件注册,但是当我们进行源码走读的时候,由于事件可能在任务位置被注册,故会对走读造成一定影响。
总结
组件 | 模块逻辑 | 消息事件分发 |
---|---|---|
Hdfs | 全部在 main 方法中依次注册,需要自身维护生命周期 | 通过组件间的相互调用实现消息分发 |
Yarn | 子模块被抽象成 Service,和顶层 Service 共享同一个生命周期 | 通过消息总线进行事件分发 |