Netflix双Flink自动扩缩器:自研与开源之战的教训
DataHot 速览
Netflix工程师Samuel Yeboah、Francesco Di Chiara和Mingliang Liu撰文介绍了他们运行两个Flink自动扩缩器的经历。自2017年起,Netflix在Apache Flink上运行超过30000个流处理作业,平台自动生成大部分作业。Netflix在2019年自研了基于外部指标的自动扩缩器,后来采用Apache Flink社区的自动扩缩器,现两者并存,正逐步过渡到开源方案。文章分享了关于指标、成本和维护自研基础设施的真实代价的深刻教训,对运行少量或大量Flink作业的团队均有参考价值。
为什么值得关注:对于在大型数据平台上运行流处理作业的团队,Netflix在自动扩缩器上的实战经验与权衡极具参考价值,涵盖了成本、指标和运维决策。
译文
AI 逐段翻译Samuel Yeboah,Francesco Di Chiara 和 Mingliang Liu
如今,Netflix 运行着两个 Flink 自动伸缩器。这正好比我们想要的多一个。我们在多年前内部构建了第一个,当时还没有适合我们平台的成熟方案。第二个来自 Apache Flink 社区,它能够扩展我们自研系统从未设计过的工作负载。我们现在在生产环境中同时运行两者,并正在稳步收敛到开源方案上。在此过程中,我们学到了一些关于指标、成本以及维护本可以采纳的基础设施的真正代价的艰难教训,我们希望这些教训对您有用,无论您是运行少量 Flink 作业还是数万个作业。
为什么在我们的规模下自动伸缩不是可选的
Netflix 自 2017 年起就在 Apache Flink 上运行流处理。截至 2026 年,我们在多个 AWS 区域运行超过 30,000 个 Flink 作业。大多数不是手动部署的;它们由我们的托管平台 Data Mesh 生成,因此大多数用户从不直接接触 Flink 作业。较小但不断增长的一部分是自定义作业,由公司各团队构建和运营,用于个性化、广告和直播活动等场景。它们范围从在 Kafka 主题之间穿梭记录的单算子作业,到具有分支、连接和数 TB 状态的有状态管道,其负载随每日周期、发布和区域故障转移而波动。
为每个作业按峰值配置是浪费的;按平均值配置会在峰值期间导致延迟。在我们的平台上,伸缩操作不是免费的:默认情况下,它意味着进行保存点、优雅停止作业并以新大小重启,对于大型有状态作业可能需要几分钟。这留下了一个真正棘手的问题:如何在不人工干预且不破坏任何东西的情况下,在需要时给每个作业提供所需的资源?
第一个自动伸缩器:从外部观察
我们的第一个答案,大约在 2019 年构建,是一个形状像流处理作业的自动伸缩器。它运行在 Mantis 上,从 Atlas(我们的遥测平台)消费集群级指标的实时流,包括每个作业的 CPU、网络、Kafka 滞后、输入速率和消费速率信号。该伸缩器结合了滞后派生的追赶时间、CPU/网络利用率阈值、观察到的性能历史以及近期输入速率的回归,以决定何时扩容或较小的集群是否能处理前瞻窗口。由于自动伸缩器独立于 Flink 平台运行,因此它不受 Flink 内部问题的影响。将其构建为流作业也使其易于扩展。每个自动伸缩器节点处理一部分 Flink 作业的指标,我们从未需要编写自定义分片或协调逻辑来跟上 Flink 集群的增长。它可靠地将数千个托管管道的资源使用量削减了 25–45%。请查看我们之前在 Flink Forward 2020 上的演讲。
但从外部观察有上限。该系统通过粗粒度的容器指标来推理整个集群,并且它只扩展一个旋钮——总的 TaskManager 数量,因此作业中的每个算子都一起移动。这适合它构建时针对的简单单算子管道,但不适合团队越来越多地为我们带来的用于广告、推荐和游戏的多算子有状态 DAG。这些正是它无法推理的作业,而支持每个新案例意味着更多自定义逻辑,而不是任何通用能力。
自动伸缩器的好坏取决于其下方外部系统提供的指标。这些指标可能错过真正的问题:一个作业可能完全繁忙,但没有任何迹象显示为 CPU 利用率,导致作业停留在伸缩器无法看到的降级状态。最近一次网络迁移悄然改变了一些流量的报告方式,伸缩器依赖的一部分 Atlas 指标停止准确捕获所有内容。这个差距一直隐形,直到很久以后在生产中浮出水面。
是时候重新考虑 构建 与 购买。
第二个自动伸缩器:从内部推理
当我们开始时,Flink 社区没有成熟的自动伸缩器可用。到我们重新评估时,它已经有了:Apache Flink Autoscaler。它不是从外部观察容器,而是从作业内部进行推理。
其关键思想是估算每个算子的 真实处理速率(TPR):即如果完全繁忙时它能维持的吞吐量。Flink 报告每个子任务每秒花在实际工作上的比例,与花在背压或空闲上的时间分开。将观察到的吞吐量除以该繁忙比例,可以外推到完全利用率下的容量:一个算子每秒处理 700 条记录,繁忙时间占 70%,其 TPR 为 700 / 0.7 = 1,000 条记录/秒。从源开始,自动伸缩器遍历作业图,使用每个算子的 TPR、其输入/输出比率和目标利用率来计算每个顶点所需的并行度,使任何算子都不会成为瓶颈,而不是将整个集群作为一个单元调整大小。
这两种方法做出了不同的约定,总结如下。
对我们来说决定性的差异是最后两行:OSS 自动伸缩器可以精确扩展我们有状态、多算子的作业,而我们的自研系统无法做到;它允许每个作业携带自己的配置——稳定期、阈值和其他针对工作负载调整的伸缩行为。这使其成为团队一直手动扩展的自定义作业的自然选择。
使其在 Netflix 规模下工作
采用 算法 很简单;社区已经完成了困难的部分。我们的工作是让它在自己的作业上可靠运行,这正是我们的系统与标准开源部署最不同的地方。
首先,OSS 自动缩放器最初被设计为驻留在 Flink 的 Kubernetes Operator 中,但我们的 Flink 平台运行在自己的控制平面上,而不是那个 operator(参见我们之前在 Current Conference 2024 的演讲)。社区后来做出了一个很棒的决定,将核心逻辑保留为独立库。他们重构了四个通用接口,使其易于直接插入我们的内部生态系统:一个携带作业元数据和 REST API 信息的上下文、一个状态存储、一个事件处理器,以及一个应用缩放决策的 realizer。
该服务是一个 Spring Boot 应用程序,其编排运行在 Temporal,即可靠的工作流引擎上。一个编排工作流大约每分钟轮询一次我们的 Flink 控制平面,查找启用了自动缩放的作业,并为每个作业启动一个长时间运行的工作流。每个作业工作流从其 Flink JobManager 拉取该作业的每个顶点的指标,运行 OSS 评估算法,当产生缩放决策时,将其交给一个 realizer,通过我们的 Flink 控制平面执行更改。
每个作业一个工作流的设计是对痛点的直接回应。我们最初在单个批处理循环中对整个作业集进行评估,但它很脆弱:一个缓慢或行为异常的作业可能会阻塞其后所有作业的指标收集和缩放。给每个作业自己的持久工作流隔离了爆炸半径,因此单个有问题的作业现在可以自行失败和重试,并且随着我们接入更多作业,运行时可以扩展。
其次,三个工程差距存在于“在社区中工作”和“在 Netflix 规模下工作”之间:
- 高并行度下的指标收集。 在大型作业上,从 JobManager 拉取指标成为瓶颈,部分原因在于 Flink 运行时。为了解决这个问题,我们修改了 JobManager 以缓存瞬态指标名称并一次性清理,而不是每次获取时重新扫描,并且我们添加了服务器端过滤,以便自动缩放器只请求它需要的指标。这使得自动缩放器可以在多达 3,000 个 Flink 子任务的作业上工作,而之前在大约 1,000 个以上时就会遇到困难。这些更改在我们的 Flink 版本内部分支中,其中一些已向上游贡献,例如 FLINK-36172。
- 保持前向链。 两个由 forward 连接连接的独立顶点必须以相同的并行度运行,因为记录在固定的本地信道上在内存中传递。单独缩放其中一个,Flink 不会失败;它会使该边静默转换为网络 shuffle。我们的分支检测前向连接子图,并将每个子图作为一个单元缩放。
- 尊重 sink 限制。 一些 sink 具有有限的写入能力,因此我们添加了异步 sink 背压检测(也是分支更改),以防止自动缩放器将作业扩展到无法吸收更多数据的 sink。
在 actuate 任何操作之前,realizer 会运行一系列安全检查。例如,它拒绝在公司范围内的区域故障转移期间在正在撤离的区域中缩小作业。它还验证新集群是否有足够的磁盘来保存作业的检查点状态,并为较大的集群添加少量备用缓冲区。
通往一个自动缩放器的道路
去年,基于 OSS 的自动缩放器在 Netflix 实现了自定义作业的正式可用性,取得了令人鼓舞的初步成果。例如,我们的客户端遥测和日志团队实现了年度 Flink 计算支出减少 58%,每年节省约 110 万美元。这一效率由三个关键因素驱动。首先,静态配置必须始终考虑峰值负载,而自动缩放器能够动态适应每日周期,捕获周末和夜间相对于工作日峰值的流量下降。其次,自动缩放器不断调整容量,而不是依赖团队在性能改进或假期后放缓后手动优化资源。最后,采用统一的容器尺寸可以实现更好的装箱和更精细的缩放增量。
此外,过于急切地缩小规模也是一个陷阱。削减过深会导致 CPU 饱和、延迟飙升,系统无法立即反应,因为每次重启后其指标窗口和稳定期都必须重建。我们现在运行目标利用率为 0.45,低于社区默认值 0.7,故意以少量效率换取稳定性。对于大型有状态作业,更少且更平缓的重新缩放值得付出边际成本。
虽然我们的缩放器为有状态 DAG 提供细粒度的信号和顶点级决策单元,但快速重新缩放仍然在很大程度上依赖于 Flink Core 的状态恢复性能。如今,缩放有状态作业的最大剩余成本不是缩放器的逻辑,而是重启和状态恢复过程本身。Flink 2 通过其 disaggregated state 架构解决了这个问题,将状态保存在外部存储中而不是本地磁盘上,这可以显著减少重新缩放或恢复对总状态大小的依赖。在 Netflix 开始支持 Flink 2.2 后,我们计划尝试这种新的状态后端,看看它是否有助于消除缩放大型有状态作业时的状态恢复瓶颈。
展望未来,我们旨在将所有内部缩放器用例迁移到基于 OSS 自动缩放器的新用例上,以简化我们的运维面。
关键要点
在此过程中,有三个经验教训可推广到 Flink 之外:
- 指标选择比算法精细度更重要。 我们最有用的调试很少是关于缩放数学;而是关于最信任哪个信号。在调整算法之前,先了解你的指标。
- 设置合理的默认值,但留出调整空间。我们的托管作业非常相似,以至于一个好的默认配置就能覆盖大多数作业而不需要改动,这正是平台的意义所在。但对每个作业强制使用单一配置会惩罚那些不适配的作业,因此我们将默认配置与每作业覆盖相结合,并故意隐藏需要深厚专业知识的旋钮。大多数团队绝不应该需要去考虑自动伸缩器。
- 先采用,再扩展。我们之所以自建,是因为在2019年,没有成熟的东西适合我们的平台。当出现一个强大的社区项目时,正确的做法既不是永远守护我们的投资,也不是一夜之间拆除它,而是将其用于新的工作负载,回馈修复,并规划一个有意的迁移。
感谢Flink和Data Mesh团队提供的控制平面更改,这项工作依赖于这些更改;感谢Temporal团队和我们早期的试点团队;以及感谢我们基于其构建的Apache Flink自动伸缩器维护者们。特别感谢Andy Zhang、Calvin Cheung、Daniel Trager、Guil Pires、Mark Cho、Matthew Kornitsky、Nikhil Sulegaon、Sujay Jain和Tom Lee。
两个Flink自动伸缩器的故事最初发表于Netflix技术博客在Medium上,在那里人们通过突出显示和回应这个故事来继续对话。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏