微服务+异步工作流+Serverless,Netflix 决定弃用稳定运行7年的旧平台
Cosmos 是一个计算平台,它将微服务的最佳特性与异步工作流以及 Serverless(无服务器)结合在一起。它的最佳应用是用于涉及到资源密集型算法的应用程序中,这些算法通过复杂的层次化工作流进行协调,可以持续几分钟到几年。它既支持一次消耗数十万个 CPU 的高吞吐量服务,也支持需要等待计算结果对延迟敏感的工作负载。
一个 Cosmos 服务
本文将会解释我们建造 Cosmos 的原因以及它的工作原理,同时也会分享一些我们在此过程中学到的知识。
Netflix 的媒体云工程和编码技术团队共同运营一个系统,处理来自我们的合作伙伴以及工作室的输入媒体文件,使它们可以在所有设备上播放。该系统的第一代于 2007 年推出了流媒体技术。第二代增加了扩缩容,但操作极为困难。第三代被称为 Reloaded,也已经上线 7 年了,并且已被证明是稳定的且可进行大规模扩缩容的。
在设计 Reloaded 时,我们是一个由开发人员组成的小团队,操作一个受限的计算集群,并专注于唯一的用例:视频 / 音频处理管道。随着时间的推移,开发人员的数量增加了三倍多,我们的用例广度和深度也都扩大了,我们的规模增长了十多倍。单体架构大大降低了新特性的交付速度。我们不能再期望每个人都拥有构建和部署新特性所必需的专业知识了。由于基础设施代码和应用程序代码都混在了一起,导致处理生产问题成为一项繁重的琐事,这给所有开发人员都带来了负担。当我们还是一个小团队的时候,集中式数据模型能很好地服务于我们,但现在它成了我们的累赘。
我们的响应是创建 Cosmos,这是一个由工作流驱动、以媒体为中心的微服务平台。我们的首要目标是在保留我们当前能力的同时提供以下功能:
可观察性——通过内置的日志、跟踪、监控、报警以及错误分类来实现。
模块化——用于构造服务并支持编译时和运行时模块化的一个“自以为是”的框架。
生产力——本地开发工具,包括专门的测试运行程序、代码生成器和命令行界面。
交付——一个被全面管理的管道、持续集成作业和端到端测试的持续交付系统。当你合并 pull 请求时,它可以在没有人干预的情况下将其投入到生产环境中。
在此期间,我们还对可伸缩性、可靠性、安全性和其他系统质量进行了改进。
Cosmos 服务不是微服务,但是它们有很多相似之处。典型的微服务是一个具有无状态业务逻辑的 API,它可以根据请求负载自动扩缩容。该 API 提供了与对等方之间的强契约,同时将应用数据和二进制依赖关系与其他系统隔离开来。
一个典型的微服务
Cosmos 服务保留了微服务的强契约和相隔离的数据 / 依赖关系,但添加了多步工作流和计算密集型异步 Serverless 函数。下图展示了一个典型的 Cosmos 服务,在该服务中,客户端将请求发送到视频编码器服务的 API 层。一组规则编排工作流步骤,一组 Serverless 函数执行特定领域的算法。函数被打包为 Docker 镜像,并带有它们自己特定于媒体的二进制依赖项(例如 debian 包)。它们根据队列的大小进行扩缩容,可以在成千上万的不同容器上运行。请求可能需要数小时或数天才能完成。
一个典型的 Cosmos 服务
Cosmos 有两条分离轴。一方面,逻辑被划分为 API、工作流和 Serverless 函数。另一方面,逻辑在应用程序和平台之间也是分离的。平台 API 为应用程序开发人员提供了特定于媒体的抽象,同时隐藏了分布式计算的细节。例如,视频编码服务是由与扩缩容无关的组件构建的:API、工作流和函数。他们对自己的扩缩容没有特别的了解。这些特定于领域的、扩缩容不可知的组件构建在三个 扩缩容可感知的 Cosmos 子系统上,这些子系统负责处理分发工作的细节:
Optimus,一个将外部请求映射到内部业务模型的 API 层。
Plato,一个用于业务规则建模的工作流层。
Stratum,一个 Serverless 层,用于运行无状态以及计算密集型函数。
所有这些子系统都通过 Timestone(一种大规模、低延迟的优先级队列系统)进行异步通信。每个子系统处理服务的不同关注点,并且可以通过专门构建的托管持续交付流程独立部署。这种关注点的分离使得编写、测试和操作 Cosmos 服务更加容易。
平台与应用程序分离
Cosmos 服务请求的跟踪图
上图是我们的观察门户网站 Nirvana 的截图。它展示了 Cosmos 中的一个典型服务请求(在本例中是一个视频编码器服务):
有一个用于编码的 API 调用,其中包括视频源和“配方”
视频被分成 31 个块,并且有 31 个编码函数并行运行
组装函数只被调用一次
索引函数只被调用一次
8 分钟后工作流完成
Cosmos 支持服务的分解和分层。由此产生的模块化架构允许团队专注于他们自己的专业领域,并控制自己的 API 和发布周期。
例如,上面提到的视频服务只是众多用于创建可在设备上播放的流的服务之一。这些服务还包括检查、音频、文本和包装,它们是用更高级别的服务精心编排的。其中最大、最复杂的是 Tapas,它负责从工作室获取资源,并使这些资源可以在 Netflix 服务上播放。另一个高级服务是 Sagan,它用于工作室的操作,如营销剪辑或日常制作编辑代理等。
Cosmos 服务分层
当制作工作室有新作品时,它会触发一个 Tapas 工作流,该工作流会编排执行检查的请求、编码视频(多种分辨率、质量、视频编解码器)、编码音频(多种质量、编解码器)、生成字幕(多种语言)并打包结果输出(多个播放器格式)。因此,对 Tapas 的单个请求可能导致对其他 Cosmos 服务的数百个请求以及数千个 Stratum 函数调用。
下面的跟踪示例展示了一个顶级服务中的请求是如何向下流到较低级别的服务中,从而导致许多不同操作的。在这种情况下,请求需要 24 分钟才能完成,数百个不同的行动涉及 8 个不同的 Cosmos 服务和 9 个不同的 Stratum 函数。
通过多个层的服务请求跟踪图
或者我们应该说是一些工作流规则?Plato 是一种粘合剂,它通过为服务开发人员提供一个定义领域逻辑和编排无状态函数 / 服务的框架来将 Cosmos 中的一切内容联系在一起。Optimus API 层具有内置的工具,可以调用工作流并检查它们的状态。Stratum 的 Serverless 层生成强类型的 RPC 客户端,使调用 Serverless 函数变得简单且直观。
Plato 是一个前向链接规则引擎,它有助于我们算法的异步性和计算密集性。与 Netflix 的 Conductor 这样的程序化工作流引擎不同,Plato 使创建“始终在线”(“always on”)的工作流变得更容易。例如,当我们开发出更好的编码算法时,我们基于规则的工作流会自动管理更新现有的视频,而无需触发和管理新的工作流。此外,任何工作流都可以调用另一个工作流,从而实现上述服务的分层。
Plato 是一个多租户系统(使用 Apache Karaf 实现),可以极大地减少操作工作流的操作负担。用户在自己的源代码库中编写和测试他们的规则,然后通过将编译后的代码上传到 Plato 服务端来部署工作流。
开发人员通过用 Emirax(一种基于 Groovy 构建的特定领域语言)编写的一组规则来指定他们的工作流,每条规则有 4 个部分:
匹配(match):指定要触发此规则必须满足的条件
动作(action):指定触发该规则时要执行的代码;在这里,你可以调用 Stratum 函数来处理请求。
响应(reaction):指定当动作代码成功完成时要执行的代码
错误(error):指定遇到错误时要执行的代码。
在这些部分中,你通常首先要记录工作流状态的变化,然后执行使工作流向前推移的步骤,例如,执行 Stratum 函数或返回执行结果。
像 Sagan 这样的 Cosmos 服务对延迟敏感,因为它们是面向用户的。例如,一位正在制作社交媒体帖子的艺术家,在剪辑最新一季的 《纸钞屋》(Money Heist)的视频时不想等太久。对于 Stratum,延迟是执行工作的时间加上获取计算资源的时间的函数。当工作非常繁忙时(通常是这样),“获取资源的时间”就成为了重要因素。举个例子,假设你购物时通常会买卫生纸。正常情况下,把它放进购物车,通过收银台是没有问题的,整个过程需要你花费 30 分钟。
资源稀缺
然后有一天,一种糟糕的病毒爆发了,并且每个人都决定要买更多的卫生纸。由于总需求超过了可用容量,你卫生纸的延迟时间现在从 30 分钟增加到了两周。Cosmos 应用程序(尤其是 Stratum 函数)在面对突发和不可预测的需求时也有同样的问题。Stratum 通过以下几种方式来管理函数的执行延迟:
资源池(Resource pools)。最终用户可以为自己的业务用例保留 Stratum 计算资源,并且资源池是分层的,允许用户组共享资源。
容量预热(Warm capacity)。最终用户可以提前请求计算资源(例如容器),以减少 Stratum 中的启动延迟。
微批次(Micro-batches)。Stratum 还使用微批次处理,这是在 Apache Spark 等平台上学到的一种减少启动延迟的技巧。其思想是将启动成本分摊到许多函数调用中。如果你调用函数 10000 次,那么该函数可能在 10000 个容器上运行一次,也可能在 1000 个容器上运行 10 次。
优先级(Priority)。当需要在成本和低延迟的需求之间进行平衡时,Cosmos 服务通常会落在中间的某个位置:有足够的资源来处理典型的突发事件,但没有足够的资源来处理延迟最低的最大突发事件。通过对工作进行优先级排序,即使在资源短缺的情况下,应用程序仍可以确保以较低的延迟处理最重要的工作。Cosmos 服务所有者可以允许最终用户设置优先级,或者在 API 层或工作流中自己设置优先级。
像 Tapas 这样的服务对吞吐量非常敏感,因为它们会消耗大量的计算资源(例如每天数百万个 CPU 小时),并且更关心一段时间内(数小时或数天内)完成的任务,而不是完成单个任务的时间。换句话说,服务等级目标(SLO)是以每天的任务和每项任务的成本来衡量的,而不是以每秒的任务来衡量的。
对于吞吐量敏感的工作负载,最重要的 SLO 是由 Stratum 的 Serverles 层提供的。构建在 Titus 容器平台之上的 Stratum 允许吞吐量敏感的工作负载通过灵活的资源调度使用“机会主义”计算资源。例如,如果一个 Serverles 函数调用愿意等待一个小时再执行,那么它的成本可能会更低。
我们知道,移动一个像 Reloaded 这样庞大而复杂的遗留系统将是一个跨越危险鸿沟的大跃进,这个鸿沟里到处都是失败的重新设计后的项目碎片,但毫无疑问,我们必须跳下去。为了降低风险,我们采用了 Strangler Fig(绞杀榕)模式,让新系统围绕旧系统扩展,并最终完全取代旧系统。
我们于 2018 年开始建造 Cosmos,并从 2019 年初开始投入生产。今天,我们大约有 40 个 Cosmos 服务,并且预计还会增长更多。我们仍处于中期阶段,但我们可以分享一些我们迄今为止所学到的重点知识:
众所周知,Netflix 的工程文化依赖于个人判断,而不是自上而下的控制。软件开发人员有承担风险和做出决策的自由和责任。我们中没有人有软件架构师的头衔;我们所有人都在扮演着这个角色。在这种背景下,Cosmos 从局部优化的不同尝试中脱颖而出。Optimus、Plato 和 Stratum 是独立构思的,并最终整合成一个单一的平台愿景。团队中的应用程序开发人员让每个人都专注于用户友好的 API 和开发人员的生产力。基础设施和媒体算法开发人员之间建立了强有力的合作关系,才将愿景变为现实。我们不可能在自上而下的工程环境中做到这一点。
我们发现,“触发编排 Serverless 函数工作流的微服务”的编程模型是一个强大的范式。它适用于我们的大多数用例,但有些应用程序非常简单,以至于由此而增加复杂性是不值得的。
从大型分布式应用程序迁移到“平台 + 应用程序”是一个重大的范式转变。每个人都必须改变他们的心态。应用程序开发人员必须放弃一定的灵活性,以换取一致性、可靠性等。平台开发人员必须培养更多的同理心,并优先考虑客户服务、用户生产力和服务等级。有时候,应用程序开发人员会感到平台团队没有适当地专注于他们的需求,而有时候,平台团队却因用户需求而感到负担过重。我们彼此坦诚相待,度过了这些难关。例如,在最近的一次回顾之后,我们加强了横切系统质量的开发跟踪,例如开发人员的经验、可靠性、可观察性和安全性。
我们创建 Cosmos 的目的是让开发人员能够更好更快地工作,将更多的时间花在解决业务问题上,而减少处理基础设施的时间。有时目标似乎遥不可及,但我们开始看到我们所希望的成果。在 Cosmos 中,开发人员最喜欢的一些系统特性是托管交付、模块化、可观察性和开发人员支持。我们正在努力使这些品质变得更好,同时也在致力于薄弱环节,如本地开发、弹性和可测试性。
2021 年对 Cosmos 来说将是重要的一年,因为我们会将大部分工作从 Reloaded 转移到 Cosmos 中,这将带来更多的开发人员和更高的负载。我们计划改进编程模型以适应新的用例。我们的目标是使 Cosmos 更易于使用,更具弹性,更快,更有效。请继续关注,以了解更多有关 Cosmos 是如何工作以及我们是如何使用它的细节。
原文链接:
https://netflixtechblog.com/the-netflix-cosmos-platform-35c14d9351ad
点个在看少个 bug 👇