Skip to content

2. 架构设计

WU Wei edited this page Oct 9, 2018 · 7 revisions

根据业务的实际需求和业务目标,ADFS的现有架构从上到下分为三层,如图表 1所示: 最上层(State Manager, Zookeeper和MetaChecker)负责元数据持久化和校验,无状态的中间层(Stateless Namenode)负责事务逻辑和最底层(Datanode)负责文件块的物理存储和访问。对比社区版HDFS的架构可以看出,ADFS在架构上最大的改动集中在Namenode部分,社区版HDFS的Namenode从功能上涵括了ADFS的上面两层,而ADFS对Namenode的功能进行了剥离。通过这样的剥离,我们得到了这样一些好处:各司其职,水平扩展和系统高可用等。接下来,我们对这些模块逐一进行介绍。

1ADFS整体架构

图表 1 ADFS整体架构

State Manager负责持久化存储ADFS集群的元数据,包括: 命名空间(Namespace)、块对应关系(BlocksMap)和Datanode信息。与社区版HDFS Namenode不同的是,State Manager将块对应关系和Datanode信息也做了持久化存储,这样避免了重启时需要通过Datanode的blockReport来构建BlocksMap和Datanode信息,提供了快速重启的必要条件。State Manager通过innodb来实现元数据的持久化存储,并且通过自己开发的Java分布式框架来实现可靠的主从热备和分布式锁。由于ADFS将原来社区HDFS Namenode的大部分内存操作转换成了在innodb级别上持久化的磁盘IO操作,单次请求的响应时间必然会受到影响,为了弥补这方面造成的性能损耗,我们一方面通过在State Manager中实现缓存来避免过多的磁盘IO读操作,另一方面采用更好的磁盘,实践中我们采用了SSD和RAID卡来提高IO性能。 Zookeeper在ADFS的架构中也承担比较重要的角色,它不仅负责记录租约,正在写入的文件,以及多块、少块和坏块等结构,而且还负责State Manager的主备选举。在实践过程中,我们发现Zookeeper有两个问题:1. 磁盘IO操作较为频繁,在压力测试时可能会导致请求响应时间过长和系统吞度量下降;2. 请求的返回数据大小有限制(可通过jute.maxbuffer环境变量来修改),压力测试中可能会超过该限制。我们现阶段的解决办法是将Zookeeper集群单独部署在装有Raid卡的服务器上,并且调整Zookeeper的配置,这样足以满足HBase的需求。不过长期来看,会把基于Zookeeper实现的Namenode结构放到State Manager中实现,并只将其中一部分持久化到Innodb中,这样Zookeeper只会作为State Manager的主备选举,读写压力会减少很多。我们在路线演进部分会对ADFS以后的架构做更详细的介绍。 MetaChecker(如图表 2)是一个独立的异步检查元数据正确性的模块,它可以作为后台程序运行,也可以通过系统管理员手动启动,它的功能类似于社区版HDFS中离开安全模式时processMisReplicatedBlocks机制,异步化检查避免了重启时集群的长时间自检,为快速重启集群创造了条件。在ADFS集群达到一定规模的情况下,MetaChecker对元数据的检查可能会影响Namenode请求元数据的响应时间,因此MetaChecker的检查可以安排在集群压力较小的时间段进行。

MetaChecker架构图

图表 2 MetaChecker架构图

Stateless Namenode是去除状态以后的Namenode实现,如图表 3所示。除了将原有状态通过State Manager和ZKClient分别保存在innodb和Zookeeper中,Stateless Namenode的业务逻辑还有几个重要的改进: 1. 去除对Namespace的全局锁,提高系统的并发性,由于ADFS的Namespace通过State Manager保存在innodb上,而innodb本身支持行级锁,能保证单行的原子操作,此外,State Manager和它所依赖的分布式框架也向Stateless Namenode提供了分布式锁机制,Stateless Namenode可以通过该机制按照文件、文件块或Datanode粒度来进行同步;2. 优化业务逻辑,精简对State Manager和ZKClient的RPC次数,由于社区版HDFS的Namenode对Namespace和BlocksMap都是内存操作,而ADFS都是对State Manager和Zookeeper的RPC操作,所以有必要对调用进行精简和优化,另外,在不影响逻辑正确性的前提下还将一些较为耗时的同步操作改造成异步操作,例如,ADFS的blockReport会比社区版HDFS的blockReport操作耗费更多的时间,从而会影响Datanode进行blockReceived请求,于是我们在Namenode的FSNamesystem类中实现了异步线程池,专门来处理异步化的blockReport,另外,我们还改进删除操作来提高响应时间,执行删除操作的文件先放入回收站,并由统一的后台线程进行来删除操作;3. 增加了Namenode调用Datanode RPC的机制——ExternalDatanodeProtocol,在社区版HDFS中,Datanode主动和Namenode联系,交互的协议DatanodeProtocol在Namenode中实现,而在ADFS中,由于Namenode是无状态的,很方面地实现了Namenode的水平扩展,具体来说,一个ADFS集群中可以部署多个Stateless Namenode,Datanode可以根据指定的策略和某一个Stateless Namenode进行通信和保持心跳,跟与之联系的Stateless Namenode称为该Datanode的Host Namenode,而其他的Stateless Namenode是它的External Namenode,它也是其他Stateless Namenode的External Datanode,ExternalDatanodeProtocol是在Datanode一端实现的,它可以方便External Namenode主动与之通信,这个功能在处理文件块副本拷贝和恢复等场景时非常有必要的。另外,在现有ADFS版本中没有实现ACL和Quota功能。

Stateless Namenode架构图

图表 3 Stateless Namenode架构图

由于Stateless Namenode做到了水平扩展,ADFS集群理论上可以达到更大规模。在正常情况下,一个Datanode或Client只会与一个Stateless保持通信,通过新增的Namenode重连机制可以使Datanode或Client在异常情况下或者人为干预情况下重新选择与之通信的Stateless Namenode,如图表 4所示。为了提供灵活的重连机制,我们定义了策略接口AbsNameNodeSelector,通过配置来指定具体的策略,目前我们实现了通过配置文件来重连的ConfNamenodeSelector策略和基于Zookeeper来重连的ZKNamenodeSelector策略。

 Namenode场景切换

图表 4 Namenode场景切换

最后,我们对ADFS工程进行了Maven化改造,通过Git+Gerrit管理代码提交和检查,并且实现了Hudson和Gerrit的集成,实现了对预提交代码的自动化回归测试。我们还在逐步完善ADFS测试体系,以实现测试的自动化和标准化。在运维和监控方面,我们开发了一些方便管理员使用的辅助工具和监控指标。

Clone this wiki locally