目 录CONTENT

文章目录

Pinterest的大规模Hadoop升级

醉酒的行者
2025-10-11 / 0 评论 / 0 点赞 / 4 阅读 / 0 字

Pinterest的批处理平台Monarch由30多个Hadoop YARN构建了17k + 个节点的集群完全在顶部AWS EC2。在2021年初,Monarch仍然在Hadoop 2.7.1上,已经有五年的历史了。由于反向移植上游更改 (功能和错误修复) 的复杂性越来越高,我们决定是时候投资版本升级了。我们选择了Hadoop 2.10.0,这是当时Hadoop 2的最新版本。

本文分享我们将Monarch升级到Hadoop 2.10.0的经验。为简单起见,我们使用Hadoop 2.10来指代Hadoop 2.10.0,使用Hadoop 2.7来指代Hadoop 2.7.1。

挑战

自从Pinterest的批处理平台开始 (大约在2016年) 以来,我们一直在使用Hadoop 2.7。随着时间的推移,我们平台处理的工作负载不断增长和发展,作为回应,我们进行了数百次内部更改以满足这些需求。这些内部补丁大多数是Pinterest特定的,需要大量的时间投入才能将它们移植到Hadoop 2.10。

业务最关键的批处理工作负载在Monarch上运行,因此我们的最高优先级是执行升级,以避免集群停机或对这些工作负载的性能/SLA影响。

升级策略

由于许多用户定义的应用程序与Hadoop 2.7紧密耦合,因此我们决定将升级过程分为两个独立的阶段。第一阶段是将平台本身从Hadoop 2.7升级到Hadoop 2.10,第二阶段是将用户定义的应用程序升级到2.10。

在升级的第一阶段,我们将允许用户的作业继续使用Hadoop 2.7依赖项,而我们专注于平台升级。这增加了额外的开销,因为我们需要使Hadoop 2.7作业与Hadoop 2.10平台兼容,但它将允许我们额外的时间在第二阶段工作。

由于我们拥有的平台和用户应用程序的规模,上述两个阶段都需要逐步完成:

  • 我们需要逐一升级君主集群

  • 我们需要升级用户应用程序,以批量捆绑2.10而不是2.7

当时,我们没有一个灵活的构建管道,允许我们构建两个单独版本的作业工件,分别使用单独的hadoop直接依赖和传递依赖。同样,我们要求所有用户 (公司的其他工程师) 单独验证从2.7到2.10的10,000多个单独的工作迁移也是不合理的。为了支持上面描述的增量升级,我们需要在迁移之前运行许多验证,以确保使用Hadoop 2.7构建的绝大多数应用程序将继续在2.10集群中运行。

我们为升级过程提出的高级步骤是:

  1. Hadoop 2.10发布准备: 将Hadoop 2.7内部分支上的所有补丁移植到香草Apache Hadoop 2.10

  2. 将Monarch集群增量升级到Hadoop 2.10 (逐个)

  3. 升级用户应用程序以增量方式 (批量) 使用Hadoop 2.10

Hadoop 2.10发布准备

Pinterest 2.7版本在开源Hadoop 2.7之上进行了许多内部更改,需要将其移植到Hadoop 2.10。但是,Hadoop 2.7和Hadoop 2.10之间发生了重大变化。因此,将Pinterest Hadoop 2.7更改应用到vanilla Hadoop 2.10是一项艰巨的任务。

以下是我们在Hadoop 2.7上制作的几个内部补丁示例,然后移植到Hadoop 2.10:

  • Monarch构建在EC2之上,并使用S3作为持久存储。任务的输入和输出通常在s3上。添加了directoutputfilemitter,以使任务能够将结果直接写入目标位置,从而避免在s3中复制结果文件的开销。

  • 添加application master和history server终结点,以便为给定作业的所有任务获取特定计数器的值。

  • 在提供容器日志时添加范围支持,这允许获取指定容器日志的一部分。

  • 为日志聚合添加Node-Id分区,使得集群的不同节点的日志可以写入不同的S3分区,这有助于避免达到S3访问速率限制。

  • 新创建的namenode可能具有不同的IP地址,这是在故障转移时解析NN RPC套接字地址的一项新增功能。

  • 如果分配的映射器数量与总映射器数量之比超过配置的阈值,则禁用抢占缩减器。

  • 将磁盘使用情况监视线程添加到AM,这样,如果磁盘使用情况超过配置的限制,应用程序将被终止。

将Monarch集群升级到Hadoop 2.10

集群升级方法探索

我们评估了将Monarch集群升级到Hadoop 2.10的多种方法。每种方法都有自己的优点和缺点,我们在下面概述。

方法一: 使用CCR

中提到的高效的资源管理文章中,我们开发了跨集群路由 (CCR) 来平衡各个集群之间的工作负载。为了最大限度地减少对现有2.7集群的影响,一种选择是构建新的Hadoop 2.10集群,并逐步将工作负载移动到新集群。如果出现任何问题,我们可以将工作负载路由回其原始集群,修复问题,然后再次路由回2.10集群。

我们从这种方法开始,并在一些小型生产和开发集群上对其进行了评估。没有任何重大问题,但我们发现了一些缺点:

  • 我们必须为每个集群迁移构建一个新的并行集群。这对于大型YARN集群 (多达数千个节点) 变得昂贵

  • 工作负载需要批量迁移,这非常耗时。因为Monarch是一个很大的平台,这个升级过程可能需要很长时间才能完成。

方法二: 滚动升级

理论上,我们可以尝试滚动升级工作节点,但滚动升级可能会影响集群上的所有工作负载。如果我们遇到任何问题,回滚将是昂贵的。

方法三: 就地升级

利用与将集群从一种实例类型就地升级到另一种实例类型类似的方法,我们:

  1. 将新实例类型的多个canary主机作为节点的新自动缩放组 (canary ASG) 插入到集群中

  2. 相对于 (现有实例类型的) 基本ASG评估canary ASG

  3. 横向扩展canary ASG

  4. 基本ASG中的比例

通常,这对于没有服务级别更改的小型基础架构级别更改非常有效。作为一个探索,我们想看看我们是否可以用Hadoop 2.10升级做同样的事情。我们必须做出的一个关键假设是,Hadoop 2.7和2.10组件之间的通信是兼容的。这种方法的步骤是:

  1. 将Hadoop 2.10 canary工作节点 (运行HDFS datanode和YARN NodeManager) 添加到Hadoop 2.7集群

  2. 识别并解决出现的问题

  3. 增加Hadoop 2.10 worker节点的数量,减少Hadoop 2.7 worker节点的数量,直到2.7节点完全替换为2.10节点

  4. 升级所有管理器节点 (Namenodes、JournalNodes、ResourceManagers、历史服务器等)。这个过程的工作原理与替换worker节点类似,方法是将它们替换为Hadoop 2.10节点。

在将这种有风险的方法应用于生产集群之前,我们对dev Monarch集群进行了广泛的评估。原来是一个无缝的升级体验,除了一些小问题,我们将在后面描述。

最终确定的升级方法

如前所述,作业工件最初是使用Hadoop 2.7依赖项构建的。这意味着它们可以将Hadoop 2.7 jar携带到分布式缓存中。然后在运行时,我们将用户类路径放在集群上存在的库路径之前。这可能会导致Hadoop 2.10金丝雀节点的依赖问题,因为Hadoop 2.7和2.10可能依赖于不同版本的第三方jar。

在使用方法I对一些小型集群进行升级之后,我们确定此方法将花费太长时间来完成所有Monarch集群的升级。此外,考虑到我们最大的monarch集群的规模 (最多3k个节点!),我们无法获得足够的EC2实例来替换这些集群。我们评估了利弊,并决定采用方法III,因为我们可能会大大加快升级过程,并且大多数依赖性问题都可以快速解决。如果我们无法快速解决某些作业的问题,我们可以使用CCR将作业路由到另一个Hadoop 2.7集群,然后花时间解决问题。

问题和解决方案

在我们最终确定方法III之后,我们的主要重点是确定任何问题并尽快解决这些问题。从广义上讲,我们遇到了三类问题: 由于Hadoop 2.7和Hadoop 2.10之间的不兼容而导致的服务级别问题,用户定义的应用程序中的依赖性问题以及其他杂项问题。

不相容的行为问题

  • 重新启动Hadoop 2.10 NM导致容器被杀死。我们发现Hadoop 2.10引入了一个新的配置纱线.nodemanager.恢复.监督默认为FALSE。为了防止容器在重新启动NMs时被杀死,我们需要设置TRUE。启用此配置后,正在运行的NodeManager不会尝试清理容器,因为它会立即重新启动并恢复容器。

  • 在2.10金丝雀节点上安排AM时,作业卡住: 添加了应用程序优先级MAPREDUCE-6515假定始终在PB响应中设置此字段。在拆分版本的集群 (2.7.1 ResourceManager + 2.10 worker) 中,情况并非如此,因为RM返回的PB响应将不包含appPriority字段。我们检查这个字段是否在protobuf中,如果不是,我们忽略更新applicationPriority。

  • HADOOP-13680制造fs.s3a.readahead.range从Hadoop 2.8开始使用getLongBytes,并支持格式为 “32m” 的值 (内存后缀K,M,G,T,P)。但是,Hadoop 2.7代码无法处理这种格式。这会中断混合Hadoop版本集群中的作业。我们为Hadoop 2.7添加了一个修复程序,使其与hadop2.10行为兼容。

  • Hadoop 2.10意外地在io.serialization config的多个值之间引入了空格,这导致了ClassNotFound错误。我们做了一个修复,以删除配置值中的空格。

依赖性问题

当我们执行inplace Hadoop 2.7到2.10升级时,我们面临的大多数依赖问题是由于Hadoop服务和用户应用程序之间共享的依赖关系版本不同。解决方案是修改用户的作业以与Hadoop平台依赖项兼容,或者在我们的作业工件或Hadoop平台分发中着色版本。这里有一些例子:

  • Hadoop 2.7 jar被放入分布式缓存中,并导致Hadoop 2.10 canary节点上的依赖问题。我们在Hadoop 2.7版本中实现了一个解决方案,以防止将这些jar添加到分布式缓存中,以便所有主机都使用已经部署到主机的Hadoop jar。

  • Woodstox核心包。Hadoop-2.10.0依赖于woodstox-core-5.0.3.jar,而一些应用程序依赖于另一个依赖于wstx-asl-3.2.7.jar的模块。woodstox-core-5.0.3.jar和wstx-asl-3.2.7.jar之间的不兼容导致作业失败。我们的解决方案是在Hadoop 2.10中对woodstox-core-5.0.3.jar进行着色。

  • 我们有一些基于Hadoop 2.7实现的内部库或类。它们不能在Hadoop 2.10上运行。例如,我们有一个名为S3DoubleWrite的类,它同时将输出写入两个s3位置。它的开发是为了帮助我们在3个桶之间迁移日志。由于我们不再需要这个类,我们弃用它来解决依赖问题。

  • 一些Hadoop 2.7库被打包到用户的bazel jar中,并在运行时导致一些依赖问题。我们采取的解决方案是将用户应用程序与Hadoop jar分离。更多细节可以在后面的相关章节中找到。

杂项其他问题

  • 我们在开发集群上执行的验证之一是确保我们可以在升级过程中途回滚。当我们尝试将NameNode回滚到Hadoop 2.7时,出现了一个还原问题。我们发现NameNode没有从升级的datanode收到块报告。我们确定的解决方法是手动触发块报告。我们后来发现潜在的问题HDFS-12749 (NN重新启动后,DN可能不会向NN发送块报告),并将其反向移植。

  • 当与Hadoop 2.7 jar捆绑在一起的Hadoop流作业部署到Hadoop 2.10节点时,预期的2.7 jar不可用。这是因为我们使用集群提供的jar来满足大多数用户工件分布的依赖关系,以减少工件的大小。但是,所有Hadoop依赖项都有在jar名称中编码的版本。解决方案是使Hadoop流作业捆绑Hadoop jar没有版本字符串,因此提供的Hadoop依赖项始终在运行时的类路径中,而不管它运行的节点是Hadoop 2.7或2.10。

将用户应用程序升级到Hadoop 2.10

要将用户应用程序升级到Hadoop 2.10,我们需要确保在编译时和运行时都使用Hadoop 2.10。第一步是确保Hadoop 2.7 jar没有随用户jar一起提供,以便在运行时使用部署到集群的Hadoop jar (2.7节点中的2.7 jar,和2.10节点中的2.10)。然后我们改变了用户应用程序构建环境,使用Hadoop 2.10而不是2.7。

将用户应用程序与Hadoop jar分离

在Pinterest,大多数数据管道都使用Bazel构建的fat jar。这些jar包含所有依赖项,包括升级前的Hadoop 2.7客户端库。我们总是尊重来自那些胖jar的类,而不是来自本地环境的类,这意味着在使用Hadoop 2.10的集群上运行那些胖jar时,我们仍然会使用Hadoop 2.7类。

为了永久解决这个问题 (在2.10集群中使用2.7 jar),我们决定将用户的Bazel jar与Hadoop库分离; 也就是说,我们不再在fat用户Bazel jar中提供Hadoop jar,并且已经部署到集群节点的Hadoop jar将在运行时使用。

Bazel java_binary规则有一个名为deploy_env的参数,其值是表示此二进制文件的部署环境的其他java_binary目标的列表。我们设置此属性以从用户jar中排除所有Hadoop依赖项及其子依赖项。这里面临的挑战是,许多用户应用程序都依赖于Hadoop所依赖的库。这些公共库很难识别,因为它们没有被明确指定,因为它们已经作为NodeManager部署的一部分在Hadoop workers中提供。在测试过程中,我们付出了很多努力来识别这类情况,并修改了用户的bazel规则,以明确添加那些隐藏的依赖关系。

将Hadoop bazel目标从2.7升级到2.10

在将用户应用程序与Hadoop jar分离之后,我们需要将Hadoop bazel目标从2.7升级到2.10,以便我们可以确保构建和运行时环境中使用的Hadoop版本是一致的。在这个过程中,Hadoop 2.7和Hadoop 2.10之间存在一些依赖冲突。我们通过构建测试确定了这些依赖项,并相应地将它们升级到正确的版本。

摘要

将17k + 节点从一个Hadoop版本升级到另一个版本,同时又不会对应用程序造成重大中断,这是一个挑战。我们设法做到了质量,合理的速度和成本效益。我们希望以上分享的经验对社区有益。

确认书

感谢来自批处理平台团队的张昂、郭恒哲、Sandeep Kumar、Bogdan Pisica、Connell Donaghy,他们在整个升级过程中给予了很大的帮助。感谢Soam Acharya,Keith Regier致力于解决问题FGAC集群。感谢Jooseong Kim,Evan Li和Chunyan Wang一路走来的支持。感谢工作流团队、查询团队和我们的平台用户团队的支持。

翻译自 Large Scale Hadoop Upgrade At Pinterest

0

评论区