导读
升级背景
HDFS升级
从2020年初开始,我们就开始推进HDFS的版本升级,并在2020年第二季度,在完成调研、代码整合和兼容性测试后,我们将HDFS从2.6.0-cdh5.4.4升级到了3.2.1。由于我们没有用来升级过渡的小集群,终直接将我们的离线大集群(5000+规模)HDFS升级到了3.2.1。
1、升级梳理
由于我们的现网Hadoop版本为2.6.0,和3.2.1版本之间存在较大的差距,升级HDFS存在较多的未知风险,需要调研梳理的工作比较多,我们从以下5个方面进行了重点梳理:
① NN元数据兼容:保证升级过程中,如果主服务升级失败后可以降级到旧版本,这里的元数据包括FSImage和EditLog。为了解决2.6.0到3.2.1的元数据兼容性问题,我们回滚了HDFS-14831中描述的问题。
② DN布局版本兼容:新旧版本DN之间布局版本不能有太大变化,需要保持兼容,这样才能保证DN在升级时如果遇到问题还能降级回旧版本。但是从2.6.0到3.2.1,DN上的数据块存储目录结构发生变化,从256x256变成了32x32个目录,以解决DN在Ext4文件系统中用256x256个目录存储数据块的性能问题。为了兼容,我们回滚了这个提交。
③Client接口兼容:由于HDFS被太多的组件(MR/Spark/Flink/Kylin/Flume/Druid等)使用,要同步升级每个组件的HDFS Client有太多工作要做,我们终只选择升级HDFS 后端服务,包括NN/JN/ZKFC/DN。所以需要保证2.6.0的客户端可以正常访问3.x的NN和DN服务。经过梳理测试,2.6.0版本的客户端访问3.2.1版本的服务端是完全兼容的。
④ 后端交互接口兼容:在升级过程中存在新旧NN/新旧JN/新旧ZKFC/新旧DN等中间状态,这就要保证新旧版本服务之间接口要兼容。经过测试,后端服务的交互接口都是兼容的。
⑤ 简单高效的升级流程:跨大版本升级也尽可能做到和小版本升级一样的简单,方便运维操作。梳理了HDFS自带的滚动升级流程,发现有一些不方便的地方,比如滚动升级期间会生成一个回滚的FSImage文件,DN端保存删除的数据块等,我们对HDFS自带的滚动升级流程进行了改造,使得HDFS从2.6.0到3.2.1的跨大版本升级流程可以和我们之前在2.6.0的小版本升级一样简单可控。
2、代码整合
在Hadoop 2.6.0版本中,我们针对HDFS开发了很多的特性,包括安全体系、NN性能优化,DN IO优化及定制化issue等,我们将这些定制的特性全部整合到了3.2.1版本中。
3、测试
由于HDFS跨大版本升级存在较大的难度和风险性,我们进行了尽可能全面的测试,包括:
① Client接口兼容性测试:包括现网全类型任务、全量SQL,以及部分业务核心任务测试;
② 滚动升级测试:进行多次滚动升级测试,保证了滚动升/降级中新旧版本NN元数据的兼容性、新旧DN布局版本的兼容性、以及后端服务接口在不同状态的兼容性;
③、3.2.1 NN性能测试:在测试环境保持现网配置和现网集群元数据压力情况下,3.2.1版本的NN读写元数据性能可以达到现网性能要求,保证了升级后NN性能不会成为瓶颈。
4、上线
我们的HDFS升级分为成了两个阶段:
① 主节点升级阶段:
升级流程为:JN-->NN-->ZKFC,4组NS升级花了1周多时间,升级整体相对顺利。
② DN升级阶段:
在主节点升级完成后,我们在一个月内完成了5000+ DN节点的升级。
由于我们简化了滚动升级流程,不需要太多的运维介入操作,使得我们的升级更加简单顺利。
在HDFS升级到3.2.1后,我们落地了新版本中的两个重大特性:RBF和EC,并都取得了很好的效果,接下来我们会重点介绍。
5、ViewFS切换到RBF
在Hadoop2.6.0版本中,我们使用基于客户端的ViewFS路由来实现多NS的访问,但是随着业务增长,集群规模越来越大,NS越拆越多,基于客户端ViewFS的方式存在以下几个明显缺点:
① 挂载表信息非集中式管理,变更维护时很难保证客户端的一致性②、挂载表信息变更时,部分依赖服务需重启才能生效,变更代价大③、ViewFs挂载点不支持挂载到多个集群,在线跨集群迁移数据困难
ViewFS架构如下:
从Hadoop 2.9.0版本开始,社区提供了新的解决方案,即RBF,并在新Hadoop 3.3.0发布版中变得稳定,RBF架构如下:
分析RBF的架构和原理,相对ViewFS存在的优势:
① 挂载表信息集中式管理,不存在不一致的情况
② 挂载表信息变更实时生效,对服务透明,不用重启服务
③ 挂载点支持挂载多个集群,数据跨集群在线迁移变得容易
④ 支持全局Quota,实现联邦模式下的全局Quota管理
想要切换到RBF,必须考虑我们的现网情况,社区并没有从ViewFS透明切换到RBF的解决方案,需要我们自己实现,我们从以下几个方面进行了优化:
(1)、用户透明访问RBF
为了实现用户访问透明的从ViewFS切换到RBF,我们自定义了两个类:ViewFsRedirectDistributedFileSystem.java类(继承DistributedFileSystem.java类)和ViewFsRedirectHdfs.java类(继承Hdfs.java类),保证了ViewFS请求透明切换到RBF,并通过配置让这两个类来生效:
(2)、自定义写集群策略
RBF其中一个强大能力是支持一个挂载点挂载到多个集群,这样使得业务数据能够跨集群存储和数据在线迁移,原生提供了多种写集群选择策略:
① RANDOM:随机选择
② LOCAL:本地优先选择
③ HASH_ALL:完全哈希方式选择
④ HASH:局部哈希方式选择
⑤ SPACE:基于可用空间选择
但是以上策略都不能满足我们现网和未来数据迁移的需求,所以我们自定义了两个策略:
① FIRST:挂载点挂载多个集群时,新数据写到个集群,数据同时从两个集群访问
② LAST:挂载点挂载多个集群时,新数据写到后一个集群,数据同时从两个集群访问
(3)、重点业务访问隔离
对重点业务我们需要做到访问隔离,在切换到RBF后,对所有联邦子集群的HDFS访问请求都会经过Router转发,如果某个子集群响应变慢,进而反压到Router端,会导致通过Router访问其他子集群的请求也受到影响,这增加了上线RBF的风险,我们针对这个风险点也做了一些工作,包括:
① 分组管理Router:重点业务使用单独的一组Router,在客户端将重点业务的HDFS请求自动转换到单独的一组Router。
客户端配置:
通过以上配置就可以将某个业务对子集群ns1/ns2/ns3,以及默认rbf-ns的请求切换到rbf-x-ns了,rbf-x-ns对应的Router为单独的一组Router。
② 对Router服务进行改造,实现和FCQ类似的功能,参考:HDFS-14090
(4)、其他优化
除了以上三点优化外,我们还对3.2.1版本的RBF进行了若干的优化和BUG修复,包括:
① 修复服务认证访问失败BUG
② 修复Router不能递客户端IP到NN的BUG(HDFS-16254)
③ 修复通过Router访问NN,导致NN白名单机制失效BUG
④ 修复通过Router来创建目录,出现目录权限有误的BUG
⑤ 挂载表修改时即时更新所有Router(HDFS-13443)
⑥ 安全模式下拒绝读请求
⑦ 去掉getDataNodeReport请求:避免对NN造成较大压力
⑧ 修复UI错误的统计信息等
6、EC落地
社区对EC的开发分为两个阶段,阶段支持条形布局,以更友好的支持小文件的EC转换,第二阶段支持连续布局。目前阶段的功能已经可用,第二阶段还处于规划阶段。我们基于条形布局来落地EC,基于条形布局的EC支持在线文件转换,比如对新写入文件直接转换为EC模式,也支持离线转换,但是考虑到读取EC数据时会有大量的跨节点带宽和EC数据损坏恢复时会消耗更多CPU,我们只使用了离线EC转换方式,也就是只对冷数据进行EC转换。
为了落地EC,我们做了以下几项准备工作:
(1)、客户端支持EC数据读/写
在落地EC时,我们的Hadoop客户端和Yarn集群都是2.6.0版本,需要改造HDFS客户端以支持EC数据读/写。
(2)、distcp支持EC数据转换
EC一期的实现是基于条形布局的,普通数据块转换为EC模式,需要进行一次数据拷贝,为了快速高效进行数据EC模式转换,我们改造了distcp的实现。
(3)、EC文件检验和支持
Hadoop 2.6.0版本中没有实现普通文件和EC文件的校验和验证方式,但是数据作为公司重要的资产,我们在对普通文件转EC时,必须要进行转换文件的校验,以免EC后的数据存在问题。我们使用了社区中HDFS-13056实现的COMPOSITE_CRC方式来对EC模式文件和原文件进行校验和验证,从而来保证EC数据的正确性。
(4)、EC自动转换管理平台
我们开发了冷数据自动转换EC的管理平台,实现冷数据自动识别和自动执行转换为EC模式文件。
EC落地效果:
历史累计冷数据:50P,EC转换后为25P,节省存储空间25P;
每季新增冷数据:5P,EC转换后为2.5P,每季度可节省存储空间2.5P。
说明:我们冷数据定义为1年以上未访问的数据。
Yarn升级
由于是跨大版本升级,技术挑战大,存在很多未知风险,我们希望严格遵循以下几个原则来进行相关Yarn升级准备工作:
① 对业务完全透明:升级不影响用户现有任务,不影响用户提交新任务;
② 兼容所有计算框架:升级保证现有计算框架都能准确执行;
③ 支持滚动升级/降级:升级有问题,相关服务可以从3.2.1版本降级到2.6.0版本。
以下是Yarn个组件的简单架构图:
1、升级梳理
我们按以上5个方面对Yarn升级进行了梳理,保证Yarn升级工作的全面性和准确性。以降低升级风险。
①、RM状态数据兼容
Yarn本身有一些状态数据,比如app及其相关的attempt信息、token信息、标签和container等信息,状态信息RM端是存储到ZK中的,NM是存储到本地的LevelDB中的,我们升级时要求做到能升级还能降级,所以升级时旧版本状态能否被新版本RM/NM准确识别,降级时新版本RM/NM生成的状态能否被旧版本RM/NM准确识别就至关重要了。Yarn的状态数据兼容要考虑两个方面:数据结构,存储方式(组织方式)。
梳理RM状态对应的协议发现,3.2.1新增了很多信息,但是在ProtocolBuffer层面都做了兼容。但是RM状态在ZK中的存储方式发生了变化,3.x版本中app和token状态信息在ZK中使用了更深节点层级关系来存储,社区对应issue为YARN-2962和YARN-7262,为了向前兼容,需要设置参数yarn.resourcemanager.zk-appid-node.split-index和yarn.resourcemanager.zk-delegation-token-node.split-index为0来关闭该功能,好在这两个参数默认值都为0,不需要特殊设置。
② NM状态数据兼容
NM中的状态数据以ProtocolBuffer的形式存储到本地的LevelDB中的,3.2.1新增了很多信息,但是在ProtocolBuffer层面也做了兼容。不过由于3.2.1版本NM新增的状态信息,在回滚到2.6.0时会出现NM不识别的key,报错如下:
这部分需要从代码层面做一下兼容,我们修改代码将3.2.1 NM新增的状态信息不保存到LevelDB中,并通过开关控制,这样就避免了该问题,待NM都滚动升级到3.2.1版本并稳定后,再打开开关保存新增状态。
③ Client接口兼容
考虑到客户端的复杂性,升级Yarn时,我们也只会升级Yarn的服务端,客户端还是保留为2.6.0版本。所以需要保证2.6.0的客户端可以正常访问3.2.1的Yarn后端服务。经过梳理和测试,2.6.0版本的客户端访问3.2.1版本的Yarn服务端是完全兼容的。
④ 后端交互接口兼容
在升级过程中存在新旧RM/新旧NM等中间状态,这就要保证新旧版本服务之间接口兼容。经过测试,后端服务的交互接口都是兼容的。不过2.8.0版本中NM新增了一个注销接口,在停止NM进程时,会导致NM本地的任务也被停掉,该功能是在YARN-41引入的,需要通过配置yarn.nodemanager.recovery.enabled=true和yarn.nodemanager.recovery.supervised=true来避免触发NM的停止注销。
同时从2.8.0版本开始,CGroup在计算NM可用内核数时,默认计算的是物理核数,而非按超线程数计算,出现可用核数偏少,导致任务执行时不能充分利用CPU资源,任务执行会变慢,对应为YARN-160,需要将参数yarn.nodemanager.resource.count-logical-processors-as-cores设置为true,这样才能按逻辑内核计算,也就是按超线程来计算NM可用内核数。
同时从YARN-668开始,Yarn相关Token的序列化反序列化开始支持ProtocolBuffer,NM从2.6.0滚动升级到3.2.1过程中,会出现兼容性问题,需要回滚该issue的修改,才能保持兼容。
⑤ MR接口兼容
梳理了MR相关的接口,大部分都是兼容的,但是TaskUmbilicalProtocol协议接口存在兼容性问题,该接口是Map/Reduce子任务和MRAppMaster进程的通信接口,该通信接口的协议基于Writable实现,在滚动升级NM期间,MR的任务在3.2.1版本和2.6.0版本之间通信时存在兼容性问题。如果从服务端做兼容需要进行一次集群升级,而且存在不可控风险,我们终通过修改Hadoop 2.6.0客户端来保证MR任务classpath在集群中的一致性来解决该问题。
对应fad9d7e85b1ba0934ab592daa9d3c9550b2bb501提交和YARN-41中,RM新增了两种NM状态:NS_DECOMMISSIONING/NS_SHUTDOWN,用于维护NM的下线和停止注销状态,这两个状态在RM和MR的MRAppMaster心跳交互时会下发到MRAppMaster端,之前为了解决NM滚动升级期间MR接口兼容问题,我们的MR任务在NM上用的都是2.6.0版本的jar包,导致NM新增的两种状态不能被识别,如果NM出现下线和停止注销的情况,MR任务的MRAppMaster会出现空指针异常并进行任务重试,导致任务重新调度,为了解决这个问题,我们修改了RM端代码,暂时不下发这两种新增的NM状态信息。
2、代码整合
在Hadoop 2.6.0版本中,我们针对Yarn进行了很多的优化和改进,包括公平调度的性能优化、公平调度支持标签、调度吞吐CPS指标、任务失败诊断、坏盘检查、任务优先级等,我们将这些重要patch迁移到了3.2.1版本。
3、测试
Yarn跨大版本升级存在较大的难度和风险性,虽然进行了全面的梳理,但是也需要进行尽可能全面的测试,测试内容包括:
① 计算框架测试:在客户端为2.6.0版本,Yarn服务端为3.2.1版本情况下,MR/Hive/Spark/Flink,经过测试都是可以正常执行的。
② 自研框架测试:我们对公司其他团队自研计算框架也进行了兼容测试,发现存在一些问题,大都在Yarn侧修改兼容,部分需要业务侧调整的,也推进业务进行了调整。
③ 滚动升级测试:由于我们是从2.6.0版本滚动升级到3.2.1,NM节点会同时存在两个不同的版本,所以需要重点测试NM滚动升级过程中各个计算框架的兼容性。经过测试发现MR/Hive/Spark/Flink都可以正常执行。
④ 调度性能测试:RM的调度性能至关重要,我们将2.6.0版本中的多个调度性能优化点迁移到了3.2.1版本,保证了3.2.1版本RM的调度性能不会低于2.6.0版本的性能。并经过压测后发现调度性能相比2.6.0有比较大的提升。
4、上线
我们的Yarn集群升级也分为成了两个阶段:
① 主节点升级阶段:
我们先将3个离线集群的RM升级到了3.2.1,到目前已经稳定运行了一段时间。
② NM升级阶段:
目前已经将其中一个近千个节点Yarn集群的NM升级到了3.2.1,其他集群正在灰度中。
MR3升级
MR在3.x版本有很多重大的改进,比如NativeTask,FileOutputCommitter优化等,可以有效提升MR的执行性能。但我们发现要直接使用MR3也存在一些问题。
1、NativeTask不支持Hive任务的序列号/反序列化
由于历史原因,我们集群中很大一部分还是Hive任务,为了利用上MR3的NativeTask特性,NativeTask需要支持Hive的序列号/反序列化,为此我们自定义了HivePlatform类来支持,并且修改了相关的本地库代码。
2、MR3透明升级
为了可以及时利用上MR3的各种重大特性和改进,我们希望在Yarn服务端没有完全升级到3.2.1版本的情况下也能用上线MR3,基于这个想法,我们采用了和前面滚动升级NM期间MR解决兼容类似的解决方案,在Hadoop 2.6.0客户端实现将MR3相关的jar包作为classpath在NM中执行,同时将NativeTask中的本地库以分布式缓存方式传递到NM端来实现MR3启用NativeTask特性。
通过我们测试对比,基于MR3(开启NativeTask)Hive SQL,平均性能比基于MR2提升了15%以上。
展望未来
在完成HDFS架构升级到3.2.1后,我们已经成功落地了RBF和EC,并且取得了很好的效果,今年在逐步完成Yarn架构升级后,我们将基于HDFS的内核改造和Yarn的Federation特性来落地Hadoop单集群的跨机房部署,我们也将在未来一段时间逐步开启Hadoop 3.x版本HDFS和Yarn的更多重大特性,享受Hadoop架构升级带来的好处,为公司各业务部门提供更加高效的离线数据存储和资源调度平台,达到降本增效的目标。
良均,鹤铭,丹琦,家成,数据平台部Hadoop方向研发工程师。