作者互联网大数据团队-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 参数限制队列内所有 ApplicationMasterAM所能使用的资源总量。原生逻辑仅基于队列的保障容量计算 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.replication1例如用于测试或特殊存储策略那么在该节点上启动的 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 依赖在启动时会触发如下校验异常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 调度、资源隔离等能力的深入应用我们有望构建一个真正“按需供给、智能调度、全域协同”的数据基础设施为业务创新提供更坚实、更敏捷的底座。最后感谢大家的阅读同时也欢迎大家一起在多云融合架构、冷热存储一体架构及资源精细调度等话题深入交流共同探索未来更高效、更智能的数据基础设施。