2013年4月,阿里云梯集群所在的数据中心(IDC机房)的机位已满,无法继续扩充集群。根据当时阿里集团数据量的增长趋势,在可以预见的很短时间内,集群规模将因为机房机位不足而无法继续扩充。由于当时云梯的Hadoop版本还不支持单集群跨机房分布的功能,所以阿里集团的大数据业务 将因为集群规模的限制而停止发展。云梯的跨机房项目就在这种背景下开始的。目标非常明确:构建一个支持跨机房的Hadoop集群。
技术挑战
要构建一个跨机房的Hadoop集群,有非常多的技术难点。
难点1:NameNode的扩展性
众所周知,Hadoop HDFS中的NameNode单点是阻碍Hadoop集群能够无限扩充的一个最大问题点。云梯在跨机房之前一直是单NameNode的结构,不管如何优化,其服务能力有其上限。虽然经过云梯开发团队的多轮优化,已能超过5000台规模(日平均RPC访问量达到25亿次),但考虑将规模扩大一倍的话,显然 无法实现。所以云梯的Hadoop版本要能支持多NameNode就非常必要。
难点2:机房间网络限制
有些问题并不是将其中一个机房的所有Slave直接汇报给另外一个机房的Master就可以解决的,因为机房间的带宽是一个巨大的障碍。
难点3:数据应该如何跨机房分布
切分成多NameNode以后,势必需要对数据进行划机房甚至是跨机房的分布,分布的策略需要从业务层面进行整体的规划。这个问题的解决方案并不在本文讨论范围之内,所以只简单提出一下。实际上,云梯团队是根据上层应用对数据的访问分布和需求情况聚类生成的数据分布。
难点4:计算应该如何跨机房调度
数据跨机房分布后,计算调度该如何进行最优的调度策略,以避免数据在机房间的来回拷贝以及作业跨机房读取数据呢?
难点5:几十PB数据的迁移,以及带数据升级
带着上百PB数据进行集群整体升级,数据不能有任何丢失,是一个非常大的挑战。
难点6:怎样做到对用户透明?
实现了多Master以后,如何对用户透明,不需要云梯上几十万个job做任何修改就能无缝兼容,是对开发团队的另一个巨大挑战。
难点7:方案是否能扩展到多机房(>=3)?
为了以后进一步跨越更多的机房,云梯版本需要考虑的不仅是双机房分布,而是多机房分布。
解决方案的详细步骤
明确了需求和难点,接下来就需要有明确的实施步骤,经过开发团队、测试团队、运维团队和业务团队的多方沟通和头脑风暴,云梯跨机房项目确定了如下的技术实施步骤(这里的设计方案,是按照整个项目实际的实施步骤顺序来介绍的,以方便大家理解每一步的初衷和解决的问题,会对每一步解决了什么问题进行相应介绍)。
第一步,将云梯集群升级为支持Federation版本(基于云梯自身的版本进行开发),将现有NameNode作为一个NameSpace,为“NameNode1”,该“NameNode1”的NameSpace下拥有云梯的全量数据,规模为5000台。
第二步,在同机房中搭建另一个NameNode,为“NameNode2”。该NameNode下的NameSpace为空,刚开始不管理任何数据。同时在所有的DataNode上创建针对NameNode2的BlockPool,用来向NameNode2汇报。
第三步,将NameNode1中的部分数据(如50%)迁移到NameNode2(这里的迁移包括NameSpace中的元数据迁移和底下DataNode磁盘中的block)。这一步完成之后,云梯结构如图1所示。这一步是一个非常大的难点。
图1 云梯多NameNode架构图
可以看出,完成了这一步,基本上就解决了前述难点1(NameNode的扩展性问题)。到这一步结束,单点NameNode就变成了多个,原先由一个 NameNode来承担的对文件系统所有元数据的访问被分摊到了多个NameNode上,NameNode的性能、内存和扩展性问题都不复存在。
经历完上述三步,实际上云梯已经完成了多NameNode的切分,但数据仍然在一个机房里面,分别由两个NameNode来分别管理。此时将另一个机房(机 房B)已经准备就绪的Slave机器开始同时向两个NameNode进行汇报。也就是说,将另一个机房的Slave机器进行上线服务。
第五 步,将NameNode2从原先的机房(机房A)转移到另一个机房(机房B)。这样,两个NameNode从物理机房上就已经分离,只不过 NameNode2上管理的数据所对应的block块仍然分布在机房A,需要对这部分数据(图1中的/group/B和/group/D的blocks) 进行迁移。这里的迁移方式很特殊,云梯团队开发了一个新的Master节点,叫做CrossNode,来实现数据的跨机房分布和跨机房拷贝的策略,将在后 面的内容中详细介绍。
经过第五步以后,云梯已经达到多NameNode切分,以及数据的NameSpace切分以及相应的block多机房 分布。接下来的一个问题是:如何让底层的这些变更对上层业务完全透明?我们采取的策略是ViewFS。简单地说,ViewFS是云梯开发团队全新开发的一 个实现Client端对多NameNode的透明感知组件,让客户端能够自动找到正确的NameNode来进行数据的读写。实现了ViewFS的的云梯 Hadoop Client结构如图2所示。
图2 云梯Client架构图
经过客户端的改造和升级,以前老的访问方式由智能的Client接管。由Yunti3FileSystem根据访问路径自动选择要访问的NameNode,实现了数据的分切对客户端透明。也由此解决了前述难点6(怎样做到对用户透明)。
到此为止,云梯的数据实现了跨机房分布,NameSpace实现了跨机房切分,客户端访问也实现了对用户透明。接下来就是计算调度跨机房的实现。由于MapReduce本身的特征,计算调度很多情况下是跟着数据走的,所以要实现跨机房计算调度,云梯采取的策略如图3所示。
图3 云梯跨机房调度架构图
从图3可以看出,云梯集群分别在两个机房各启动了一个MapReduce,用来做实际任务的分配和调度,而job在提交到JobTracker之前,有一个新开发的JTProxy组件来对job的提交进行前提判断。由于云梯的计算资源是按照组来划分的,所以JTProxy要根据计算组的配置来决定向哪个JobTracker进行job提交。并且JTProxy还提供了一个统一的Web界面供用户查询多个JobTracker上的作业运行情况。至此,前述难点4(计算应该如何跨机房调度)迎刃而解。
经过上述多个步骤,云梯集群就实现了多NameNode切分、数据的跨机房分布和管理、计算的 跨机房调度等,虽然物理上云梯集群跨越两个数据中心,但对上层业务来说,完全感知不到底层的变动。不仅如此,由于实现了多Master的切分,让多个 NameNode来分担以前一个NameNode来管理的所有数据,所以也很大程度上释放了以前单NameNode节点在扩展性和性能上的瓶颈,让以前因 为单Master节点带来的种种问题全部都迎刃而解。同时也为将来集群的进一步扩充留下很大的余地。基本上,在基于跨机房的那个版本和框架下,云梯 Hadoop集群几乎是没有物理上限的一个集群了。
细心的读者肯定发现,难点5的解决方案没有详细说明,下面对此进行详细讲述。
问题:在上述第三步中,我们怎样将NameNode1中的部分数据(比如50%)迁移到NameNode2,迁移的同时还不影响业务的正常运行呢?(这里的迁移包括NameSpace中的元数据迁移和底下DataNode磁盘中的block)。这个问题本身是一个非常复杂的场景,云梯开发团队也是通过几个步 骤来一点点地实现的。
需要先让新的NameNode2获取原先NameNode1上所有的元数据的全量拷贝。这是开发团队通过将 NameNode1上最新的fsimage用到NameNode2上,并进行load来完成的。这样,新的NameNode2就拥有了NameNode1上所有的文件和目录结构,以及文件的block组成情况,唯一缺的就是每个block在哪些DataNode上的分布情况,这一部分需要通过 DataNode的blockReport来进行构建。
完成了上述步骤以后,还需要让所有的DataNode上原本的单一 BlockPool变成多个。其中BlockPool1对应原先的DataNode上所有block数据的全量,而BlockPool2则是空。要让 BlockPool2中拥有BlockPool1中的所有block,有两种方式:一种是进行全量拷贝;另一种方式比较讨巧,是将BlockPool1中 的所有block及其对应的目录在BlockPool2中创建全量的hardlink。由于只是磁盘层面的硬连接创建,所以不占额外的存储空间,而且很 快。这样,当所有的DataNode启动时,原本的BlockPool1中的数据向NameNode1汇报,而BlockPool2中拥有的同样一份数据 向NameNode2汇报。这样当集群启动完毕以后,两个NameNode都拥有整个集群数据的元数据全量,只不过底层的存储只有一份,通过硬连接来实现 block块数据共享。
完成上一步后,集群的状态是:两个NameNode都管理了整个集群的全量数据,但实际上数据切分并不是要让两个 NameNode都管理全量数据,而是分别管理一半的数据(或者根据实际访问需求来进行特定花费)。也就是说,NameNode1上虽然管理了全量,但需 要删掉一部分不由它管理的数据,NameNode2也是一样,而两个NameNode管理的数据的总和就是原本的全量数据。由于底层block进行了硬连 接的创建,所以只要不在两个NameNode上都删掉同一份数据,那么数据本身在DataNode上就不会丢失。接下来还需要完成两件事:第一,客户端能 根据不同的目录访问特定的NameNode(数据切分);第二,将仍在A机房但需要迁移到B机房的那部分数据(NameNode2应该管理的那部分数据) 的block块迁移到B机房。
对于上述第一点,让客户端对不同数据的访问自动找到相应的NameNode,是通过前述的ViewFS来实现 的。而对上述的第二点,对数据的实际迁移过程,则是通过云梯开发团队全新开发的一个新的Master组件——CrossNode来实现的。图4是 CrossNode的架构图。
图4 云梯CrossNode架构图
CrossNode 是一个全新独立的Master节点,它所管理的是:读取一个配置文件,这个配置文件中记录了哪些文件和目录下的数据需要跨机房放置。例如,一个文件原本有 3个副本,都在A机房,把这个文件的路径写入到CrossNode的配置文件中,让CrossNode知道这个文件需要跨机房放置,并且在A机房是3个副 本,B机房需要2个副本,主角CrossNode只负责从A机房拷贝2个副本到B机房。同时由于在跨机房那个目录中会有新的文件被创建和写入,所以这些目 录下的文件在写入完成后,也需要后期对其进行跨机房副本放置的处理,这些也都是CrossNode来完成的。
因此,将NameNode2需 要管理的那部分数据从A机房迁移到B机房的方式为:将这些文件和目录配置到CrossNode的配置文件中,这样CrossNode就会发现有那么多的数 据需要在A机房和B机房同时放置,如3:3,于是会对这些数据的block进行跨机房拷贝,直到所有的block全部在B机房拥有3个副本,然后将 CrossNode中的配置进行修改,例如修改成0:3,这样表示A机房需要0个副本,B机房需要3个。于是CrossNode重新工作,将A机房原本的 3个副本进行删除,保留B机房的3个副本,支持完成NameNode2管理的所有数据从A机房到B机房的迁移。大家可以发现,这个CrossNode的方 案,正好解决了难点2,因为有了CrossNode以后,数据的迁移变成了一个计算的前序操作,每天云梯系统会根据前一天的计算分布和其他业务情况来决定 哪些文件需要跨机房分布,哪些需要进行迁移,哪些需要去除跨机房分布。这样在计算真正运行时,绝大部分计算job需要的数据都会存在于计算调度所在的本机 房内,并不需要跨机房读取和跨机房写入,这样机房间的带宽并不会成为影响计算作业效率的瓶颈。即使只有少量需要跨机房读写的访问存在的情况下,机房间的带 宽也完全能够处理得过来。
总结
至此,经过上述一系列步骤, 便实现了云梯Hadoop集群的跨机房服务。目前,云梯集群规模已接近万台,跨越两个IDC机房,数据的分布和计算的调度每天都在根据实际情况进行实时调 整。跨机房云梯的更重要意义在于:在未来的时间里,阿里集团运行在Hadoop平台上的大数据业务不需要再为数据规模和性能而担惊受怕,在云梯现有的架构 下,已经基本看不到集群规模的上限,性能也可以根据实际情况和访问情况来进行动态的调整。
在过去五年中,阿里云梯一直都采取需求驱动的发展模式。直到2013年4月,这种模式再也无法继续下去:云梯集群所在的数据中心(IDC机房)的机位已满,无法继续扩充集群,不能满足阿里集团数据量增长的需求。此时,构建一个跨机房的Hadoop集群便刻不容缓。
作者罗李,花名鬼厉,阿里分布式团队创建之初的第一批员工,从事分布式计算、分布式存储和Hadoop系统的研发,目前负责分布式存储团队的所有技术和管理。