作者:互联网大数据团队-Wang Zhiwen
本文主要介绍了 vivo 大数据架构的演进历程中 YARN 服务的升级事项,从整体方案出发剖析每个环节遇到的问题难点并逐一分析讲解,对于研究调度器性能和从事大数据运维工作的同学具有较大的参考借鉴价值。
1分钟看图掌握核心要点👇


图 1 VS 图 2,您更倾向于哪张图来辅助理解全文呢?欢迎在评论区留言~
一、背景
vivo 大数据平台早期引入的 Hadoop YARN 2.6.0 版本,在长期运行中已逐渐难以满足当前业务对资源管理效率与集群扩展能力的更高要求。由于该低版本缺乏对 Node Labels 资源隔离、Timeline Server 以及 YARN Federation 等高版本核心特性的支持,随着集群规模和数量持续增长,在现有基础能力上无法提升运维效率及资源利用率,这对我们万台规模的大集群、日均支撑近百万任务的平台是无法接受的。
此外,在计算引擎层面,除 Spark3 外的其他引擎均无法直接访问基于纠删码(Erasure Coding, EC)存储的冷备数据,严重制约了数据架构的完整性与计算效率。这不仅限制了多元计算场景对冷数据的灵活调用,也使我们无法充分释放 EC 冷备技术在降低存储成本方面的潜力。
通过将 YARN 升级至更高版本,我们得以重构集群整体架构:一方面,借助更精细的资源调度能力和增强的多场景隔离机制,能显著提升集群稳定性与资源利用效率;另一方面,统一各计算引擎底层的 Hadoop 依赖,实现对 EC 冷备数据的无缝读写支持。此次升级不仅打通了冷热数据一体化处理的链路,也为未来多样化的计算负载提供了更高效、可扩展、易维护的基础设施支撑。
二、vivo YARN 架构演进历程
vivo 大数据团队成立较早,在经历数据量与集群规模的迅猛增长后,我们深刻意识到:单纯依靠机器堆叠来满足不断扩张的大数据需求,是一种投入产出比极低的粗放模式。为此,自 2020 年起,团队开始系统性投入人力,探索大数据架构的深度演进,旨在提升集群整体资源利用率,更高效、灵活地支撑多样化的用户需求。以下是我们自 2020 年以来 Hadoop YARN 服务的关键演进历程:

这一演进历程,标志着 vivo 大数据平台从传统单体集群架构向现代化、精细化、高弹性的资源管理体系的深刻转型。我们不仅系统性解决了早期版本在资源隔离、调度公平、运维可扩展性等方面的固有缺陷,更通过存储与计算协同升级,实现了成本与效能的双重优化。如今,一个统一、高效、可扩展的大数据基础设施底座已然成型,为未来 AI 与大数据融合、多云调度、Serverless 计算等创新场景提供了坚实支撑。
三、升级流程及难点
为实现整个集群的平滑升级,并确保用户业务全程无感知,我们必须妥善应对升级过程中涉及的多项复杂变更。这些变更包括:将资源调度器由公平调度器(Fair Scheduler)切换为容量调度器(Capacity Scheduler),全面升级 ResourceManager 与 NodeManager 的 YARN 核心组件,以及同步更新所有计算引擎所依赖的 Hadoop 客户端版本等关键任务。
为确保上述高风险操作在万台规模的生产集群中安全、可控地落地,我们开展了多轮覆盖功能、性能、容灾及回滚能力的严格测试验证,并在此基础上制定了如下清晰、分阶段的升级流程:

该流程按照 ResourceManager → NodeManager → 计算引擎 → History Server 的顺序依次推进,最大限度降低组件间的兼容性风险,保障服务连续性。
那么,究竟是哪些关键测试与验证工作,支撑了如此大规模集群的有序、稳定升级?接下来,我们将深入剖析本次升级过程中遇到的核心技术难点,并详细介绍我们针对性设计的解决方案与实践经验。
3.1 调度器平滑切换
在低版本的YARN服务中我们是使用公平调度作为集群的资源分配调度器,为了能在升级后用上高版本的特性nodelabel资源隔离能力,我们期望在升级期间就完成调度器的切换,使用社区主流的容量调度器。在官方文档中并没有完善的替换操作文档,两个调度器间存在的一些差异会影响当前用户的使用,且原公平调度器的分配性能在我们内部是经过深度优化,为能在升级过程中无缝切换,我们团队做了大量的兼容适配和性能优化工作。
3.1.1 兼容与适配
1. 支持使用全路径队列名
在当前的在离线任务中,用户普遍通过全路径队列名(如 root.prod.etl)显式指定运行队列。然而,在升级至 YARN3.1 并启用容量调度器后,这一用法不再被支持------YARN 要求任务提交时仅使用叶子队列的简短名称(如 etl),且所有叶子队列在整个队列树中必须全局唯一,不允许重名。这一变更对现有业务影响巨大:大量历史任务因队列命名冲突或路径格式不兼容而无法正常提交,若不解决,将严重阻碍调度器的切换与升级。
为应对这一挑战,我们深入调研了更高版本的 YARN 社区进展,发现 YARN-9879(于 3.3 版本合入)恰好提供了对全路径队列名的支持,并放宽了叶子队列名称必须全局唯一的限制,从而能从根本上兼容现有任务的提交方式。然而,该功能所依赖的代码基线与我们当前使用的 YARN3.1 版本存在较大差异,无法直接移植。
为此,我们以 YARN-9879 的最终实现为目标,系统梳理其依赖的代码演进路径,逐个回迁并适配了中间共 35 个相关 PR,涵盖调度器解析逻辑、队列校验机制及配置加载流程等关键模块。通过这一系列精细化的代码整合与充分测试,我们成功在 YARN3.1 环境中实现了对全路径队列名的兼容支持,同时解除了叶子队列名称必须全局唯一的硬性约束,为业务平滑迁移至容量调度器扫清了关键障碍。
2. 支持计划模式
为更高效地利用集群资源,我们此前借助 Cloudera Manager 提供的队列计划模式功能,按小时动态调整各队列的保障资源(minimum capacity)与共享资源(maximum capacity),确保各业务在高峰期能够获得充足的资源配额,从而稳定、高效地完成任务产出。
在 YARN 升级后,为在容量调度器上保留这一关键能力,我们在自研的内部运维管理工具中复现了类似的队列动态调整功能。升级完成后,只需将原有的队列计划配置从 Cloudera Manager 迁移至新管理平台,即可无缝延续原有的资源调度策略,无需业务侧进行任何改造。这不仅保障了资源调度策略的连续性,也显著降低了升级过程对业务运行的影响。

3. 自定义队列限制场景
在公平调度器(Fair Scheduler)中,支持通过配置 队列级最大运行应用数(max running apps per queue)来限制并发任务数量。当队列中正在运行的应用数达到阈值时,后续提交的任务将进入等待状态,从而确保已运行的任务能够持续获得资源、顺利完成,避免因过度并发导致资源争抢和整体性能恶化。然而,在 YARN3.1 版本的容量调度器中,这一关键能力尚未实现。为保障业务调度行为的一致性与稳定性,我们主动合入了社区补丁 YARN-9930,在容量调度器中新增了对队列级最大运行应用数的限制支持,有效复现了公平调度器的排队控制机制。
此外,容量调度器通过 user-limit-factor 参数控制单个用户可使用的资源上限。例如,当该参数设为 1 时,单个用户最多只能使用等于队列保障容量的资源。但在我们的线上环境中,存在大量弹性调度场景------即队列的共享资源(maximum capacity)远高于其保障资源(如保障 10%,共享可达 80%)。在默认配置下,user-limit-factor 无法充分利用这部分弹性资源,限制了高优先级用户的突发资源需求。为此,我们合入了 YARN-10531,默认情况下关闭队列内用户资源使用的限制,从而更好地支撑弹性调度场景。
与此同时,容量调度器还通过 maximum-am-resource-percent 参数限制队列内所有 ApplicationMaster(AM)所能使用的资源总量。原生逻辑仅基于队列的保障容量计算 AM 资源上限,在高弹性场景下容易导致 AM 无法申请到足够资源而任务启动失败。针对此问题,我们对内部 AM 资源计算逻辑进行了改造,使其在评估 AM 最大可用资源时,能够依据队列的最大共享容量进行动态调整。这一优化显著提升了任务拉起的成功率,尤其在资源紧张或弹性扩缩容场景下效果显著。
通过上述三项关键改进"运行应用数限制、用户弹性资源扩展、AM 资源上限优化",我们不仅补齐了容量调度器在生产环境中的能力短板,更使其在复杂、高并发、弹性化的大规模集群中展现出更强的调度灵活性与资源利用效率。
3.1.2 性能优化
除了全面解决调度器的兼容性问题外,为支撑万台规模集群的高效调度,我们团队还在容量调度器的性能优化方面开展了大量深入实践。在具体介绍我们的优化措施之前,有必要先简要说明容量调度器的核心运行机制,其整体调度框架如下图所示:

在容量调度器中,每一次 Container 的分配均由 NodeManager 的心跳上报触发。ResourceManager 接收到 NodeManager 的心跳信息后,会将该节点的资源可用状态传递给调度器,由调度器执行具体的容器分配逻辑。
调度过程遵循"逐级择优、公平分配"的原则:
- 首先,从根队列开始,调度器会按层级遍历队列树,在每一层中根据各子队列的当前资源使用比例(已用资源 / 配置容量)进行排序;
- 然后,选择使用比例最低的子队列,并递归进入下一层,直至定位到一个叶子队列;
- 最后,在该叶子队列内部,调度器会对所有活跃应用(Applications)按其已分配资源总量进行排序,优先为资源使用量最少的应用分配容器,以实现队列内应用间的公平调度。
这一机制虽能保障多租户环境下的资源公平性,但在大规模集群(如上万台节点、数万个并发任务)场景下,频繁的队列遍历与排序操作会带来显著的 CPU 开销和调度延迟。正因如此,我们围绕调度路径、数据结构和计算逻辑等关键环节,实施了一系列针对性优化,有效提升了调度吞吐与响应效率。
1. 排序缓存
在容量调度器的基本分配流程中,每次容器分配都需要对队列及其内部的应用进行排序。以典型的三级队列结构为例,若系统需在1 分钟内完成 50 万次容器分配尝试,而每次排序耗时约 0.01 毫秒(实际耗时受队列数量和应用并发数影响),仅排序操作就将累计消耗约 15 秒 CPU 时间。这一开销在高并发、大规模集群场景下会显著拖累整体调度性能,成为性能瓶颈。
为缓解这一问题,我们引入了队列与应用排序结果的缓存机制,在保证调度公平性的前提下,大幅降低重复排序带来的计算开销。其核心思路是:在一定时间窗口内复用已排序的结果,避免每次分配都重新计算。

具体实现如下:
通过两个新增配置参数控制缓存有效期:
- yarn.resourcemanager.capacity.child-queue-cache-refresh-interval-ms:控制子队列排序结果的缓存时间;
- yarn.resourcemanager.capacity.child-app-cache-refresh-interval-ms:控制叶子队列内应用排序结果的缓存时间。
在每次容器分配前,调度器会检查对应缓存的上次更新时间是否已超时:
- 若未超时,则直接使用缓存中的排序结果;
- 若已超时,则重新执行排序逻辑,并更新缓存。
通过该机制,可有效限制单位时间内的排序调用频次。仍以每分钟 50 万次分配为例,若将缓存有效期设为 10 毫秒,则每分钟最多仅需执行约 6,000 次应用排序 + 12,000 次队列排序 = 总计约 1.8 万次排序操作,相比原始方案的 50 万次排序,减少超过 96%。
排序次数的大幅下降显著降低了 ResourceManager 的 CPU 负载,提升了调度吞吐能力与响应速度,为万台规模集群的高效资源调度提供了关键性能保障。该优化在真实生产环境中验证有效,已成为我们调度器高性能运行的核心支撑之一。
2. 避免无效分配
在为 Application分配资源的过程中,某些情况下调度器会执行"无效分配"------即触发了分配逻辑,但最终无法成功分配任何 Container。这类操作不仅浪费调度 CPU 资源,还会降低整体集群调度效率。基于线上大规模集群的运行实践,我们总结出两类主要的无效分配场景:任务自身无资源需求 和 队列资源限制导致分配失败。
(1) 任务无资源需求导致的无效分配
在包含大量小任务的队列中,此类问题尤为突出。容量调度器在分配前会对叶子队列中的所有 App 按已分配资源量进行公平排序,优先选择资源使用最少的任务。小任务通常能快速获取其所需全部资源,但若未能立即完成(如等待外部依赖或处于空闲状态),在后续调度轮次中仍会因其"低资源使用量"而被优先选中。然而,此时该任务已无新的资源请求,导致调度器执行了一次"空分配"------即无效分配。
优化方案:
我们在排序前增加资源需求过滤逻辑,仅将当前仍有 pending 资源请求的 App 纳入排序范围,并将过滤后的结果缓存。通过此方式,彻底避免了对无需求任务的无效调度尝试,显著提升了分配效率。
(2)队列资源限制引发的无效分配
此类问题更为复杂,主要源于两个层面:
① 主导资源计算器(DominantResource-
Calculator, DRC)的比较逻辑缺陷
YARN 使用 DRC 进行多维资源(内存 + vCores)比较。例如,比较资源 <1GB, 729 vCores> 与 <50GB, 5 vCores> 时,DRC 会根据各资源在集群总容量中的占比确定"主导资源"。若 CPU 占比更高,则以 vCores 为主导维度,从而判定前者"更大"。
然而,在队列最大资源限制校验中,若直接使用 DRC.greaterThanOrEqual(available, request) 判断是否满足分配条件,可能得出"可用资源 ≥ 请求资源"的错误结论。实际分配时,因另一维度(如内存)不足,仍会失败,造成无效分配。
社区已在 YARN-11083 和 YARN-10903 中指出该问题。我们的解决方案是:弃用 DRC 的大小比较方法,转而采用更精确的资源匹配判断:
- 使用 Resources.fitsIn(request, available) 判断请求是否完全可被满足;
- 或使用 Resources.componentwiseMin 进行逐维度最小值计算,确保各资源维度均不超限。
该修复已覆盖容量调度器中所有相关校验点,从根本上规避了因 DRC 比较偏差导致的无效分配。
② 多级队列嵌套下的父队列资源耗尽
在典型的多级队列架构中,多个叶子队列共享父队列的弹性资源池。即使某个叶子队列自身未达最大容量限制,其父队列可能已因其他子队列的高负载而耗尽全部共享资源。此时,调度器在叶子队列层级校验通过,但在实际向父队列申请资源时失败,再次引发无效分配。
优化方案:
我们在资源分配前引入递归式父队列资源校验机制。即在确认叶子队列满足条件后,继续向上遍历其所有父队列,逐级验证是否有足够可用资源容纳本次分配。只有当从叶子到根路径上的每一级队列均满足资源要求时,才真正执行分配。这一机制有效杜绝了因父队列资源不足导致的分配回滚,大幅减少无效调度开销。
(3)指标替代日志输出
在容量调度器的分配逻辑实现中,为便于调试和追踪每次资源分配的细节,原始代码嵌入了大量细粒度日志(log)。然而在大规模集群的压力测试中,这些日志迅速暴露出严重的性能问题:每几分钟就会生成一个高达 200MB 的日志文件,不仅占用大量磁盘 I/O 和存储空间,更因频繁的日志写入显著拖慢了调度器的处理速度,直接影响容器分配性能。

而在实际生产环境中,我们通常并不需要关注每一次分配的具体细节(例如某个任务本次申请了多少内存或 CPU),而是更聚焦于关键性能指标,如:
- 队列与 Application 的排序耗时;
- 单次容器分配尝试的处理时延;
- NodeManager 心跳上报到完成调度响应的端到端耗时等。
基于这一观察,我们对日志体系进行了深度优化,一方面,大幅精简并屏蔽了非必要的调试级日志,仅保留异常路径和关键决策点的必要信息; 另一方面,系统性地引入了多项精细化性能指标(Metrics),通过 YARN 的 Metrics 系统实时采集调度核心流程的耗时与吞吐数据,并接入监控告警平台。

3.2 服务端升级过程中的业务连续性保障
在升级过程中,ResourceManager、NodeManager 以及各类计算引擎会不可避免地处于 Hadoop2 与 Hadoop3 共存的混合状态。这种异构环境对组件间的兼容性、通信协议及资源调度逻辑提出了严峻挑战。如何精准协调各角色在不同阶段的状态切换,确保服务连续、任务不中断、数据不丢失,是实现整体平滑升级的关键。接下来,我们将详细介绍在整个升级方案推进过程中,各阶段所面临的核心问题及其应对策略,涵盖组件依赖冲突、调度器切换、客户端兼容性等多个维度,全面还原这场大规模集群平滑演进的技术实践。
3.2.1 ResourceManager无缝升降级
为实现 ResourceManager 在 Hadoop2 与 Hadoop3 版本之间的无缝切换,我们不仅需要确保服务能按计划顺利完成升级,还必须具备在出现异常时快速、安全回退至原版本的能力,同时保障运行中的任务不受影响。为此,我们制定了一套系统性的保障方案。除了深度优化容量调度器自身的兼容性与性能外,一个关键前提是:新调度器的队列行为必须与原有公平调度器完全对齐。这包括队列的权限控制、资源分配策略、用户或应用级别的资源限制等核心属性。任何偏差都可能导致任务排队异常、资源争抢甚至作业失败。
为解决这一难题,我们团队自主研发了一款公平调度器到容量调度器的配置自动转换工具。该工具能够解析现有的 fair-scheduler.xml 配置文件,智能映射其队列结构、权重与资源容量的对应关系,并自动生成符合容量调度器语义的 capacity-scheduler.xml 配置。它精准处理了两类调度器在以下方面的差异:
- 队列层级与命名规范;
- 权重到容量的数学转换;
- 用户限制、AM 资源占比、最大并发应用数等策略参数的等效表达。
3.2.2 Nodemanager平稳升降级
NodeManager 从低版本直接升级至高版本时,服务可正常运行;然而,在尝试从高版本回退至低版本时,却会触发异常:

该问题的根源在于:高版本 NodeManager 在持久化 Container 状态信息时引入了多个新字段(例如 YARN-3998 中新增的容器重启关键元数据),而低版本代码缺乏对这些字段的解析能力。当低版本 NodeManager 在启动恢复(recovery)阶段读取由高版本写入的状态文件时,因无法识别新增字段而抛出解析异常,导致容器恢复失败,进而影响任务续跑。
针对此类向前兼容性问题,社区提出了两种典型解决方案:
- 在低版本中补充高版本新增的状态字段定义,使其具备解析能力;
- 在高版本中引入状态字段的开关控制机制,在升级窗口期内禁用新字段写入,待全集群完成升级后再启用。
经梳理高版本相较低版本共新增了 15 个状态字段,涵盖 Container 生命周期管理、安全 Token 机制、AMRMProxy 上下文等多个核心模块。无论采用上述哪种方案,均需进行大规模代码修改与回归测试,实施成本较高。考虑到以下关键事实:
- 正向升级过程本身无兼容性问题;
- 异常仅在回退场景下由低版本无法解析高版本字段触发;
- 低版本写入的状态信息在高版本中完全兼容(即高版本具备向下兼容能力)。
我们最终采用了更轻量、高效的第三种策略:在低版本 NodeManager 的状态恢复逻辑中增强异常容错机制。
具体而言,当反序列化过程中遇到无法识别的字段时,系统会主动捕获并安全忽略该异常,跳过未知字段,继续完成其余状态的加载。这一改进确保了即使状态文件包含高版本特有字段,低版本 NodeManager 仍能顺利完成 recovery 流程,保障容器正常恢复,从而实现安全、可控的版本回退能力,同时避免了大规模代码侵入和维护负担。
3.2.3 其他问题
在完成 ResourceManager 与 NodeManager 的平滑升降级改造后,我们发现:正在运行的任务在服务升级过程中仍可能因服务端版本变更而失败。具体表现为,任务在升级窗口期内因 Token 序列化格式不兼容而抛出异常,典型日志如下:

经深入分析,该问题的根源在于:高版本 YARN(如 3.1+)集成了 YARN-668、YARN-2615 和 YARN-8310 等特性,将 Token 的序列化方式由 Java 原生字节流全面迁移至 Protobuf 格式。当高版本 ResourceManager 下发 Protobuf 编码的 Token 给尚未升级的低版本 NodeManager 时,后者因仅支持旧版字节流反序列化逻辑,无法解析新格式,从而导致容器启动失败。针对这一跨版本兼容性问题,我们评估了两种主流方案:
方案一:调整升级顺序
先完成所有 NodeManager 的升级,再切换 ResourceManager 版本。但其存在以下风险点:
- 将 ResourceManager 升级这一高风险操作(涉及调度器切换、性能验证、回滚复杂度)后置,一旦失败,回退成本极高;
- 在"高版本 NM + 低版本 RM"共存阶段,可能存在未被充分验证的交互兼容性问题,需额外投入大量测试资源。
方案二:序列化方式降级
修改 ResourceManager 代码,强制其在升级期间继续使用低版本的 Java 字节流序列化 Token。其本质上是临时回退相关社区补丁,会导致部分关键字段(如容器类型 containerType )丢失。在启用 YARN Federation 等高级特性时,AppMaster 因缺失容器上下文信息,可能做出错误的资源分配决策,影响任务正确性。
最终方案:动态序列化开关机制
综合权衡稳定性、功能完整性与实施成本,我们采纳了一种改进型方案二:在 ResourceManager 中新增一个序列化方式控制开关支持在运行时动态切换 Token 序列化格式。整体实施流程如下:
- **升级初期:**关闭开关,强制 ResourceManager 使用低版本字节流格式下发 Token,确保与存量低版本 NodeManager 兼容;
- **NodeManager 全量升级完成后:**开启开关,切换至高版本 Protobuf 格式,启用完整新特性。
该方案兼具安全性与前瞻性:
- ✅ **保障升级期间任务连续性,**避免因序列化不兼容导致作业中断;
- ✅ **无需改变升级顺序,**降低整体协调复杂度与回退风险;
- ✅ **保留高版本全部功能能力,**避免因降级造成容器元数据缺失;
- ✅ **通过配置开关实现平滑过渡,**未来亦可用于灰度验证或紧急回切。
3.3 引擎侧的平滑升级流程
3.3.1 计算引擎兼容服务升级
计算引擎与YARN服务的版本兼容性是确保集群稳定运行的关键环节。接下来将从统一Hadoop依赖、提交节点配置规范、统一Hive客户端版本三个维度,深入剖析计算引擎兼容YARN服务升级的技术方案与实践经验。
首先,统一Hadoop版本依赖是避免运行时兼容性问题的基础。在多版本共存或依赖混乱的环境中,不同任务可能加载不同版本的 Hadoop客户端库,极易引发 ClassNotFound、NoSuchMethodError 等异常。为此,我们通过显式配置 yarn.application.classpath、mapreduce.
application.framework.path 和 mapreduce
.application.classpath 等参数,强制所有提交到 YARN 的任务 Container 使用统一的 Hadoop3 依赖路径。这些路径指向集群预置的、经过严格测试的 Hadoop3 客户端包,确保无论任务从哪个节点提交,其运行环境中的 Hadoop 核心类(如 FileSystem、Configuration、RPC 等)均来自同一版本。这种"依赖固化"策略有效杜绝了因客户端版本不一致导致的任务失败或行为偏差。
其次,禁止计算任务依赖服务端(NodeManager 所在节点)的本地 Hadoop 配置,转而统一使用规范的提交节点配置。这一问题在 Spark2 等早期版本中尤为突出:当未显式指定 classpath 时,Spark 会回退到 DEFAULT_YARN_APPLICATION
_CLASSPATH,该默认值通常包含 $HADOOP_CONF_DIR,即 NodeManager 本地的配置目录。若某些计算节点的 hdfs-site.xml 中配置了 dfs.replication=1(例如用于测试或特殊存储策略),那么在该节点上启动的 Container 写入 HDFS 的数据副本数将被设为 1。一旦存储该数据块的 DataNode 发生故障,数据将永久丢失,造成严重业务风险。通过强制所有任务从提交端继承配置(如低版本 spark 通过配置类似 spark.hadoop.{mapreduce|yarn}.application.classpath=./spark_conf/hadoop_conf 参数强制 Container 加载从提交节点上传的配置文件到classpath),并屏蔽 NM 节点本地配置的影响,我们实现了配置的"提交端一致性",从根本上规避了因节点配置差异引发的数据可靠性隐患。
最后,我们统一了所有计算引擎所依赖的 Hive 客户端版本。由于当前平台上的计算引擎(包括 Spark SQL、Presto、Hive 等)若继续使用原有的 Hive1.1 版本并加载 Hadoop3 依赖,在启动时会触发如下校验异常:
less
Caused by: java.lang.IllegalArgumentException: Unrecognized Hadoop major version number: 3.1.1.3.1.0.0-78
at org.apache.hadoop.hive.shims.ShimLoader.getMajorVersion(ShimLoader.java:169)
at org.apache.hadoop.hive.shims.ShimLoader.loadShims(ShimLoader.java:134)
at org.apache.hadoop.hive.shims.ShimLoader.getHadoopShims(ShimLoader.java:95)
at org.apache.hadoop.hive.ql.io.CombineHiveInputFormat$CombineHiveInputSplit.<init>(CombineHiveInputFormat.java:143)
该问题的根本原因在于:现有 Hive 客户端版本过低,尚未兼容 Hadoop3。当计算引擎启动后调用 Hive 元数据接口访问 Metastore 服务时,Hive 内部会校验当前运行的 Hadoop 版本。若检测到版本不在其支持列表中(如 Hadoop 3.x),便会抛出上述兼容性异常,导致任务无法正常初始化。针对此问题,社区已在HIVE-16081 中提供了修复方案。我们据此合入相关补丁,对 Hive 客户端进行兼容性增强,并将更新后的 JAR 包发布至内部私有仓库(Maven 私服),供所有计算引擎统一引用和升级。
综上所述,本次 YARN 服务升级通过依赖统一化、配置中心化、统一底层hive客户端依赖三大措施,系统性解决了计算引擎与 Hadoop3 的集成难题。这不仅为整个升级过程奠定了坚实基础,同时解耦了计算引擎与hadoop版本依赖关系,在保留原有的计算引擎下将服务端升级到预期版本。
3.3.2 任务平稳切换使用hadoop3依赖
在成功解决了计算引擎与 Hadoop 3 的集成难题之后,如何平稳地将所有任务过渡到使用 Hadoop 3 依赖成为了一个新的挑战。尽管上述措施能够解决标准化 SQL 类任务的兼容性问题,但对于 JAR 类型的任务来说,由于用户可能在其业务包中引入了不兼容的 Hadoop 版本或其他第三方 JAR 包,仍然可能会出现兼容性问题。由于这些业务包对我们而言是一个完全的"黑盒",并且需要处理的任务数量庞大,在有限的时间内无法逐一分析和测试验证。因此,我们基于调度平台(以下简称 BDSP)设计了一套统一的 JAR 类型任务灰度切换 Hadoop 依赖的流程。

整个灰度流程主要分为三个步骤:
第一步:标签标记
在迁移开始前我们会为集群中的所有 Shell 和 JAR 类型任务打上一个 hadoop2 标签。带有此标签的任务在执行 spark-submit 前会自动加入一个环境变量 VIVO_HADOOP_VERSION。当该变量的值为 2 时,Spark 启动入口会选择 Hadoop2 的依赖和配置来运行任务;同理,当该变量的值为 3 时,则选择 Hadoop3 的依赖和配置。这样做的目的是为了确保在迁移过程中,任务可以根据实际需求灵活选择合适的 Hadoop 版本进行运行。
第二步:任务灰度
在这个阶段,我们将分批次剔除任务的 hadoop2 标签。此时,BDSP 不会添加 VIVO_HADOOP
_VERSION 变量,因此 Spark 默认会选择 Hadoop3 的依赖和配置来运行任务。如果任务在运行期间发生失败,BDSP 会重新为该任务加上 hadoop2 标签,使其在后续重试阶段能够在 Hadoop2 环境下继续运行,从而避免因半夜任务失败而无人处理的情况。
第三步:兼容整改
针对灰度过程中失败的任务,我们会进入兼容整改阶段。业务侧对这些问题进行整改后,可以再次进入第二步继续进行灰度。在这个过程中,我们团队协助业务解决了不少兼容性问题,并将一些共性问题直接应用到计算引擎侧,以防止其他业务遇到类似问题,从而减少整体的兼容性问题发生率。
通过这一系列精心设计的灰度切换流程,我们不仅实现了从 Hadoop2 到 Hadoop3 的平滑过渡,还显著提升了任务的稳定性和数据安全性。同时,通过对共性问题的总结和优化,进一步减少了未来可能出现的兼容性风险,为大数据平台的持续演进奠定了坚实的基础。
四、总结及展望
此次 Hadoop YARN 大版本升级,是一次从兼容性攻坚到架构重塑的深度实践。它不仅解决了历史技术债,更打开了通向高弹性、低成本、强治理的新一代大数据平台的大门。未来随着 Federation、GPU 调度、资源隔离等能力的深入应用,我们有望构建一个真正"按需供给、智能调度、全域协同"的数据基础设施,为业务创新提供更坚实、更敏捷的底座。最后感谢大家的阅读,同时也欢迎大家一起在多云融合架构、冷热存储一体架构及资源精细调度等话题深入交流,共同探索未来更高效、更智能的数据基础设施。