eBay大数据安全合规系列 - EB级集群升级挑战和实践
作者|林意群
编辑|陈乐
供稿|DI Hadoop team
本文共5209字,预计阅读时间13分钟
更多干货请关注“eBay技术荟”公众号
前言
在上篇分享的文章【eBay大数据安全合规系列 - 系统篇】中,介绍了目前eBay大数据平台在安全合规方面遇到的挑战和困难,以及我们如何在系统平台的层面对此进行改进,最终实现了在保证升级速率的情况下达到集群平滑升级的效果。在安全合规升级的过程中,系统应用的高可用性毫无疑问是放在第一优先级的。我们宁可升级的速度慢一点,也要保证集群服务的正常运行。在大数据如此多的应用中,数据存储服务又是其中最为敏感的一部分。因为这里面涉及到的是海量规模的数据存储,目前eBay大数据集群存储数据量远远超过PB,已经接近EB级别。在这么大体量的数据规模下,我们如何保证在不影响数据可用性的同时,又做到1个月内的平滑升级(数据存储机器全部重启升级一遍)。本文将从HDFS存储系统升级的挑战、HDFS系统服务改进以及未来展望三方面来对此进行阐述。
HDFS存储系统升级的挑战
首先本文讨论的存储系统指的是HDFS(Hadoop Distribute File System)集群,我们的升级过程会将HDFS里的数据存储节点上的DataNode服务进行停止并进行节点升级,然后再重新恢复DataNode服务。也就是说,那些处于升级过程中的节点是不提供数据服务能力的。从升级的过程我们能够看到,升级对于存储系统来说最大的一个挑战是数据可用性的问题。对此我们理想达到的一个目标是在多节点因为升级导致DataNode服务停止期间,集群数据服务不能受到丝毫影响。
了解过HDFS内部设计的同学可能觉得这不是一个大问题,因为HDFS本身实现了三副本的设计来保证数据可用性。假设1台机器挂了,副本所在的另外2台机器依然能提供数据的访问能力。这难道不就解决了上面的问题了吗?答案是否定的,它忽略了另外一个关键的影响因素:升级的速率。如果我们集群升级的速率是按照逐个机器升级情况下的话,三副本设计当然能解决我们数据可用性的问题。但是逐台机器升级的速率远远无法达到我们预期达到的1个月内升级万台以上机器的规模。所以我们实际升级速率是按照rack粒度的,每次升级停止的是一个rack上的DataNode服务。而停止一个rack数量的DataNode服务,对于集群的影响就大很多了,因为HDFS三副本策略是将3副本分布在2个rack上来实现rack awareness的。这时倘若一个rack上的2副本已经不能访问了,这将大大增加block missing的风险。因为我们无法保证最后一台副本所在的机器不会出现硬件故障或者坏盘的情况,从而导致数据无法访问。
另外一个同样是由停止整个rack服务引起的问题。当HDFS NameNode服务发现大量DataNode out of service状态后,会对其上的副本进行重新replication操作,使其副本数重新达到期望副本数。这个过程会触发上千万的block replication数,我们如何避免这里大量的replication对集群造成的影响,这点也是另外一个需要考虑和解决的问题。
综上两点所述,HDFS存储系统升级的挑战有以下两点:
1.数据可用性挑战:在停止多DataNode服务下,如何保证数据可用性。
2.服务稳定性挑战:在停止多DataNode服务后,如何保证系统服务稳定性。
接下来,我们来看一下我们是如何在HDFS系统层面来解决这两个难题的。
HDFS系统服务改进
1.三副本多Rack部署
第一个是数据可用性的问题,刚刚上面已经介绍过,鉴于HDFS采用的是三副本分布在2个rack的部署模式下,部署效果如下所示:
在以rack为升级操作粒度的情况下,这种部署方式会带来极高丢数据的风险。那么是否我们能够通过改变上面副本部署方式来降低这种风险呢?继续按照这个思路,是否我们能将副本分散在更多的rack上而不是仅仅保存在2个rack里面?
于是,我们初步设计了如下新的副本部署模式(placement)。
在有了这么一个初步的新placement想法之后,我们继续调研了与此相关的一些改动,这里面主要有以下几点:
1. 需要实现一个新的placement policy,支持placement可插拔式地使用
2. 新placement里面对于excess block的处理
3. 新placement能够进行fallback处理,假设当前集群无法满足此policy的条件时
4. Balancer工具能够支持按placement policy的block搬迁
5. Decommission过程也同样应当能够支持按照placement policy的block replication
6. 新placement的block写入性能监控
7. 新placement对于rack带宽使用的影响
对于上面的几点,由于本文篇幅有限,主要就新placement的实现逻辑做一个简单的介绍。为了不更改默认placement的逻辑,我们继承了BlockPlacementPolicyDefault类,override了其中chooseLocalRack方法,使其实际语义转变为了选择另外一个remote rack的效果,代码如下:
另外我们改写了判断选到的target是否是一个good candidate DataNode的逻辑,在里面增加了rack数的判断。但是如果是在choose target fallback条件下时,rack数的检查会被跳过,这个时候整个选择逻辑会变成随机选择location。
另外一部分与上面choose rack相关的必要改动是底层NetworkTopology类的改动。在目前NetworkTopology实现中,它只支持传入一个exclude scope做location的选择。但是在我们多rack placement下,我们需要传入2个rack让其exclude,以此才能准确选出第三个符合要求rack下的location。因此我们做了下面NetworkTopology#chooseRandom方法的扩展实现来支持新placement的实现:
在实现完上面新placement相关代码后,我们为了对其性能进行监控,另外加上了相关的细粒度metric,这样能够方便知道其block location选择的效率,监控指标如下图所示:
对于新placement的副本实际分布效果,通过HDFS自带的fsck命令就能看到其副本分布模式。上线新placement到生产环境后,除了placement policy效率的问题之外,我们担心的另外一点是它对于集群带宽的影响。因为相比较2个rack的模式,3个rack的数据写pipeline会涉及到多一倍的跨rack间的流量。因此在实际上线后,我们也在密切关注着集群内带宽的实际使用情况,从目前观察的情况来看,新placement并没有造成带宽的异常。
2.存量数据placement迁移
新placement是实现完成了,并且保证了新写入的数据能够按照三副本三rack的方式部署。但这里还有一个更为棘手的问题需要解决,集群里那些规模庞大的存量数据依然采用的是两rack的placement方式。我们还需要把所有存量数据做一遍placement的迁移转换,这样才算彻底地完成三副本多rack的部署。
在正式做block placement迁移之前,我们需要对待迁移block数做一个估算,以此确定采用哪种合适的方式来做这个迁移。待迁移的block数其实就是集群当前总block数,将各个大小集群block数相加,已经高达数十亿规模量级。在如此大体量block数的情况下,placement迁移工作将会是一个逐渐迁移的过程,而不是一次性的快速迁移操作。再结合实际迁移场景的特点,通过工具命令的方式触发迁移过程的执行是一种比较好的方案,这样能够保证整个迁移过程的可控性。
迁移计划的大方向确定好之后,后面是如何来实现这样一套专门做placement迁移的工具。首先,我们要关闭所有可能存在的HDFS自带的placement检查的逻辑,防止其内部检查逻辑触发大量的placement迁移。在这部分的检查逻辑中,我们disable了NameNode启动过程后的placement检查和DataNode启动后的block report的placement检查。
上面提到的NameNode里的placement检查本质上是server端这边的自动检查行为,而我们的方案偏向于是一种外部client触发的行为。我们总共设计了以下三种client-side的备选方案:
方案一:
利用现有Balancer工具的block balance行为。HDFS的Balancer工具是用来做数据块的balance的,在数据balance过程中它在数据节点的选择上已经能够支持按照集群设置的placement policy来选择新的目标location(相关JIRA:HDFS-9007)。这点恰恰符合我们的迁移场景。
方案二:
基于Fsck path的block扫描检查行为。HDFS的Fsck工具能够按照给定path进行block的检查,在检查block的过程中同样可以查看数据的block placement。如果有发现哪个block placement不对的话,能够触发此block placement的迁移行为(相关JIRA:HDFS-14053)。此方案相当于是在namespace层做扫描来触发底层storage的数据迁移,方案原理如下图所示:
方案三:
按照node级别的block placement迁移。简单来说就是我们再做一个类似于Balancer的工具,不过它能够指定DataNode的block迁移。然后我们按照这种方式,逐个迁移完集群中所有的节点。
综合比较上面三套方案,方案一的缺点是其强依赖于数据balance行为,如果集群本身已经达到数据均衡状态,将不会有大量block被迁移。方案二Fsck方式的实际可操作性比较弱,它是一个one time的行为,一次触发将会扫描指定路径下的所有block,这将会在短时间内触发大量的replication。方案三的缺点是需要额外开发一套能够按照node级别的数据迁移工具,在做法上类似于Balancer工具目前的操作。综合比较上述3个方案的优缺点,我们最终选择了方案二Fsck的方式。
但是现有Fsck工具的可操作性还远远无法达到我们预期的要求,总结下来它还需要满足以下几点要求:
能够随时中断迁移过程,当发现迁移过程影响到了集群正常服务的时候。
能够支持断点继续迁移,我们不希望每次从头开始重复迁移已经完成的副本数据。
能够进行迁移速度的控制,这样能够灵活调整整体迁移的进度,不至于过快或过慢。
这里解释一下什么叫做断点继续迁移,在实际placement迁移场景中,迁移过程是一个漫长的行为。这中间随时可能因为NameNode重启或者数据搬迁异常需要在某个时间点重新搬迁数据,那么这时按理来说我们只需要从上次迁移成功的最后一个文件开始迁移即可,而不是必须又得从头开始迁移文件。
随后我们对Fsck工具进行了扩展改造,对于上面断点继续迁移的要求,我们在Fsck工具里面做了一个startpath的功能来满足这个要求。对于迁移速度的控制,我们让Fsck block扫描操作按照指定batch size的方式进行扫描并且进行定期sleep等待,以此进行迁移速度的控制。另外我们增加了一个标记位开关能够随时中止正在运行的Fsck操作。整个Fsck的基本工作原理如下图所示:
我们做的定制化改动主要集中在Fsck Tool本身以及部分NameNode里的path扫描逻辑,因为最终触发placement迁移的操作还是得由NameNode发送指令让DataNode来做。
下面是一个简单使用样例,我们输入下面的fsck参数,指定startpath为/B,batch size为10000,sleep时间是1秒。
Sample command:
bin/hdfs fsck / -startpath /B -batch 10000 -sleep 1000
这个命令的意思是让Fsck从/B目录起,每次扫描、迁移10000个block,然后睡眠等待1秒,接着继续下一批10000个block迁移,startpath的语义效果图如下:
对于上面batch size扫描块数量的确定,这个要取决于实际生产集群replication的速度,我们参考了线上集群处于忙碌状态时7.5w block/分钟的replication速度,设置了略小于此值的fsck扫描速率。最终我们利用这个新版本Fsck工具,花了2个多月时间成功地迁移了近20亿个block,完成了集群全部block placement的转换。在三副本三rack部署模式下,集群数据的可用性得到了很好的保证。
3.DataNode maintenance模式
在上面提到的困难挑战中,还有一点是来自于升级过程造成的replication过多导致的潜在NameNode性能影响问题。但仔细来看这里面的副本replication过程,其实它并不是必要的,理由如下:
1. 处于out of service状态的DataNode是预期升级行为产生的,只是处于短暂服务停止状态。等升级过程完成,DataNode将会重新恢复服务,此时又会导致NameNode去删除之前replication出去的过量副本数的副本。
2. 目前我们采用的副本多rack部署模式能够比较好地容忍单rack机器完全处于停服状态的情况。
基于上述两点理由,我们想要探索尝试一种新的不会导致replication的新方案。后来我们参考了社区HDFS Maintenance State的原型(相关JIRA:HDFS-7877),在此基础上进行了定制化的改动。
社区Maintenance State功能引入了一个新的名为Maintenance的状态,并且支持Maintenance状态和其它状态的相互转换。但是在我们的场景里,我们只需要支持从In Service状态到Maintenance状态的互相转换即可。因为这里我们只会让一个正在服务的DataNode置为Maintenance状态,然后进行升级,升级完后再恢复成In Service状态。
另外一个比较大的问题是社区版本功能实用性不好,目前社区Maintenance功能需要依赖外部文件的修改来控制DataNode节点状态变化,这在一定程度上增加了管理员对此的维护成本。鉴于我们生产集群本身已经采用include/exclude host的方式管理DataNode机器,一旦要引入社区Maintenance功能,就必须得进行外部host内容的改造来支持此特性。这对于需要管理上万台机器的管理员来说,这无疑又增加了一定的维护成本。因此我们决定采用一种更加轻量级的做法,通过admin命令的方式来动态管理DataNode maintenance模式。样例命令如下:
# 设置dn节点进入maintenance状态,过期时间180秒(180秒后dn重新变成in service状态)
$hdfs dfsadmin -maintenanceState -enter dn_hostname#180
# 设置dn节点离开maintenance状态
$hdfs dfsadmin -maintenanceState -leave dn_hostname
上述命令行的方式本质上是在NameNode内存里对DataNode状态进行了更新,这里面难免会有一个状态信息持久化的问题。一旦NameNode发生重启或者failover,我们能保证里面状态的一致性吗?目前我们只做到了保证failover的行为是能够达到状态一致的,这样保证了至少有一个NameNode能够拥有正确的DataNode状态。至于重启丢掉状态信息的NameNode,需要再次执行admin命令来恢复DataNode maintenance状态信息。这么做的原因前面已经提过,是出于实际功能维护成本的考虑,没有做到完全持久化处理。本身maintenance window往往是比较短的一个时间区间。
综上所述,经过我们定制化改造的DataNode maintenance模式的状态机转换如下图所示,总共有4个状态:
经过上述HDFS系统层面的改进后,我们将maintenance模式结合三副本多rack部署完美地应用到了我们的升级场景里面,升级过程大致如下图所示:
在集群升级开始前,我们会通过Delos系统(eBay Hadoop大数据集群运维平台)将待升级rack里的DataNode全部置成maintenance模式。然后有另外的系统对maintenance的rack进行升级,对于处在maintenance机器上的数据,用户是无法访问的,此时要依托于其它rack上的副本来提供数据的访问。
未来展望
对于未来的展望,我们希望继续提升当前集群数据的高可用性,以此能够做到在更加极端条件下的数据服务能力。在未来我们计划能够做到集群数据跨rack同时跨区域的部署(一个集群部署在少数几个域内,每个域之间相互独立)。这样我们能够做到在一个域内所有机器都不可用的情况下时,用户的数据访问依然不会受到影响。
参考:
1.https://issues.apache.org/jira/browse/HDFS-7877
2.https://issues.apache.org/jira/browse/HDFS-9007
3.https://issues.apache.org/jira/browse/HDFS-14053
往期推荐
点击“阅读原文”, 一键投递
eBay大量优质职位虚席以待
我们的身边,还缺一个你