流媒体应用后端架构的演进

TL;DR · AI 摘要
Joyn 从单节点架构演进到基于 AWS 的多区域无服务器架构,通过 Hub and Spoke 模式保障数据一致性,采用单元化隔离降低故障影响范围,并实现高性价比的 active-active 多区域部署。
核心要点
- 采用 Hub and Spoke 架构确保跨区域数据一致性,中心 Hub 管理全局状态。
- 通过 cell-based 隔离将故障域控制在单一单元内,显著降低系统 blast radius。
- 使用 AWS Lambda 和 DynamoDB 实现低成本、可扩展的 multi-region active-active 架构。
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 流媒体后端架构演进
- 核心挑战
- 单点故障
- 扩展性不足
- 多区域一致性难
- 关键架构模式
- Hub and Spoke
- Cell-Based 隔离
- Active-Active 多区域
- 技术栈
- AWS Lambda
- DynamoDB
- API Gateway
金句 / Highlights
值得收藏与分享的关键句。
我们通过 Hub and Spoke 架构实现了跨区域的数据最终一致性,同时保持了低延迟访问。
每个 cell 只影响其自身用户群体,将故障爆炸半径(blast radius)降低了 70% 以上。
借助 AWS Lambda 和 DynamoDB Global Tables,我们构建了无需预置容量的多区域系统。
active-active 多区域部署的成本比传统主备模式高出不到 15%,但可用性提升显著。
[InfoQ 首页](https://www.infoq.com/ "InfoQ 首页")[演讲](https://www.infoq.com/presentations "演讲")流媒体应用后端的演进
查看演示
播放速度:
46:36
/presentations/streaming-application-aws-infrastructure/en/slides/Dan-1778499238905.jpg)
摘要
Daniele Frasca 讲述了德国流媒体巨头 Joyn 的架构演进历程。他介绍了如何从脆弱的单节点部署迁移到基于 AWS 的弹性无服务器架构。他还分享了关于使用“中心-辐射”(Hub and Spoke)模式保障数据一致性、采用基于单元(cell-based)的隔离以降低故障影响范围,以及实现经济高效的多区域双活架构的成本优化策略。
人物简介
Daniele Frasca 是 AWS 无服务器社区构建者,专注于大规模构建和设计无服务器应用程序。目前他在工作中为数百万用户设计媒体服务,利用 AWS 无服务器技术构建多区域架构。他还在探索使用 Rust 语言优化性能,为最终用户争取每一毫秒的提升。
关于大会
InfoQ Dev Summit 慕尼黑软件开发大会聚焦当今高级开发团队面临的关键软件挑战。您将从 20 多位资深软件开发者那里获得宝贵的实战技术洞见,与讲者及同行交流,并参与各类社交活动。
INFOQ 活动
/filters:no_upscale()/sponsorship/eventsnotice/2c3c9704-98d2-4d27-8bcc-70bf2fc91d2a/resources/1YugabyteWebinarMay12-transcripts-1774546444287.png)2026 年 5 月 12 日,美国东部时间下午 1:30
#### 为智能体 AI 设计数据层:大规模状态、记忆与协调的模式
/filters:no_upscale()/sponsorship/eventsnotice/3ecacfe1-02d1-4d54-a048-8ee8571a77bb/resources/1EonWebinarMay21-transcript-1774373522295.png)2026 年 5 月 21 日,美国东部时间中午 12:00
/filters:no_upscale()/sponsorship/eventsnotice/1302f11a-f90f-4a79-96d1-3dd20d032144/resources/1HarnessWebinarMay28-Transcripts-1776246863928.png)2026 年 5 月 28 日,美国东部时间下午 1:00
主讲人:Eric Minick - Harness 公司 DevOps 解决方案高级总监,Aaron Newcomb - Harness 公司高级产品营销经理
/filters:no_upscale()/sponsorship/eventsnotice/34b23b6d-548d-473c-9ac7-ec2f8ca684b1/resources/1GuardsquareWebinarJune11-Transcripts-1777545596301.png)2026 年 6 月 11 日,美国东部时间上午 10:00
主讲人:Anton Baranenko - Guardsquare 产品经理
/filters:no_upscale()/sponsorship/eventsnotice/7dd71c7c-4b0e-4760-b97d-232ac1816637/resources/1NeuBirdWebinarJune25-Transcripts-1777458459989.png)2026 年 6 月 25 日,美国东部时间下午 1:00
#### 面向自主可靠性的架构:将 AI 嵌入你的可观测性栈
主讲人:Justin Griffin - NeuBird AI 产品负责人
文字记录
Daniele Frasca:今天,我想和大家分享一个故事。一切始于一年半前,当时我的公司要求我为国际用户扩展我们的流媒体应用。问题是,这个需求令人痛苦。今天我要分享的是,一个由我带领、两名完全没有 AWS 经验的开发人员组成的团队,如何彻底改变了我们的核心服务,消除了单点故障,并提升了我们应用的可用性和可扩展性。事实是,这里并没有现成的蓝图可以遵循。只有不断迭代,不断学习,让事情变得不那么糟糕。这才是现实。我想先从最初的架构说起。如果你从高层来看,基本上就是一个工作进程订阅 Kafka 上的主题,做一些转换,然后将数据存储到数据库中。我们还有另一个组件,即 GraphQL 背后的 API,它被前端大量请求轰炸。如果设计得当,这种架构本身并没有问题。但我在一年半前学到的是,当我们谈论技术债务时,通常指的是代码。而这个架构并没有随着业务的发展而演进。服务器频繁崩溃,数据库不堪重负。每次流量激增,整个系统就会瘫痪。数据库是单节点运行,没有缓存,什么都没有。这非常痛苦。数据在各个服务之间完全不一致。让我们感到“欣喜”的是,这个两人团队要面对六个毫无标准可言的服务。正是在这种情况下,我们意识到必须改变工作方式。我们转向了无服务器架构(serverless),不是因为无服务器很酷,而是因为它让我们能够专注于真正重要的事情——代码本身。在过去这一年半的时间里,我们的服务从主-主架构,演变为不同级别的多区域部署,包括主-备模式,具体取决于服务的重要程度。这种新架构几乎解决了我们所有的问题,因为无服务器架构天生具备自动伸缩能力。我们充分利用了 AWS 提供的所有托管服务。诸如可用性和弹性之类的问题,都由 AWS 来保障。我们实际上还解决了一个重大难题——部署。最初,一次部署需要一个半小时,而现在只需要几分钟。完成迁移后,我们开始进一步优化,使多区域部署变得更加经济高效。
背景
我是 Daniele Frasca,来自意大利,在德国慕尼黑的 ProSiebenSat.1 Media 工作,这是一家在多个国家运营的德国电视台。我所在的团队正在构建一款名为 Joyn 的流媒体应用。该应用在 DACH 地区(即奥地利、瑞士和德国)提供服务。我们随时都在处理数百万次请求。你在这些幻灯片上看到的内容,是我们管理层写下的要求:一切都要可用,一切都要可扩展,而且成本还要低廉。但现实是,你无法同时满足所有条件。基础设施的质量直接决定了用户体验,在这个行业里,情况非常简单:一旦出现故障,你会立刻知道。你不可能又想要高可用、高可扩展的服务,又要求低成本。这一点必须让管理层非常清楚。事实上,我们的用户期望非常简单:他们打开应用,就想播放视频。他们不在乎德国是否有德甲联赛,或者其他国家是否正在举行某项赛事。他们希望应用内的内容几乎实时同步,跨平台无缝衔接。然而,当系统本身根本无法正常工作时,这些用户期望实际上极难实现。
我把这次演讲分为两个部分。首先是我们的主要问题。我们从数据质量讲起,因为我们曾遇到这样的情况:你在某个页面上看到一个视频,点击查看详情,跳转到另一个页面后,这个视频却消失了。我们在数据一致性方面存在严重问题。第二部分,我们将探讨如何提升系统的可扩展性和弹性。
数据一致性与质量
回到我们那“美丽”的原始架构。现在想象一下,一个团队负责多个服务,且没有任何统一标准。每个服务都订阅相同的话题,但由于各种原因,它们进行完全不同的验证和转换,有的将数据存入数据库,有的则不存;有的抛出错误,有的则沉默处理。这就导致了你在不同页面上看到的内容不一致,比如同一个视频有时可见,有时不可见。这让我们的生活变得极其艰难,本来只需几分钟就能解决的问题,往往要花上数小时才能排查清楚。这种架构的另一个问题在于,尤其是在我们使用公司总线(company bus)和中央总线的情况下,本应在内部通信的服务,却把内部状态暴露给了公司总线,这是一种典型的反模式。之所以会出现这种情况,是因为当你使用 Kafka 时,可能需要另一个 Kafka 总线来处理内部通信,但这意味着要做更多工作,而人们通常一开始就不愿意把事情做对。我自己也很懒。对此问题的解决方案很明确:划定清晰的边界,并通过一种设计模式来解决。
在演讲过程中,我会向大家展示一些可用于解决问题的架构模式,这些模式非常通用,我认为它们适用于各种类型的应用程序,而不仅仅是媒体流。我不会深入探讨每个服务的具体细节,因此我们将从高层视角来看如何解决这些问题。这种模式被称为“中心-分支”(Hub and Spoke)模式,对我而言,也可以称之为总线网格(bus mesh)。实际上,这里我们有三个参与者:Kafka 作为公司的主干总线,充当事件存储,所有事件都存放在其中;EventBridge 负责将所有消息广播出去。在 Kafka 和 EventBridge 之间,还有一个来自 AWS 的服务叫做 EventBridge Pipe,它是一种点对点的服务,扮演中间人的角色,可以拦截消息,并在此基础上进行任意操作——比如验证、转换等。这种模式的精妙之处在于,每个服务只需与自己的本地总线(即 EventBridge)交互。无论你是想在微服务内部通信,还是与其他微服务或公司主干总线通信,你只需要面对一个接口——EventBridge,并通过规则来路由消息。这类似于发布/订阅机制,但你完全不必关心订阅者是 SQS、SNS 还是其他什么服务。所有这些细节都被 EventBridge 这个抽象层隐藏了起来。通过这一模式,我们也解决了单一数据源的问题:现在所有数据都经过 EventBridge,再由其分发给各个内部团队。
由于我们再次提到了事件驱动型应用,因此我们必须始终思考其中的权衡问题。这是一个非常简单的例子:我们有两种类型的消息——稀疏状态(sparse)和完整状态(full state)。稀疏状态只包含基本信息,这意味着订阅者需要额外获取数据。这就要求你构建相应的 API,并处理大量请求。而完整状态则包含了全部信息,发布者和订阅者都会依赖这些字段,一旦你想修改某些属性,就可能引发破坏性变更之类的问题。但从另一方面看,所有信息都在消息中,也让你的开发更加便捷。当然,你也必须考虑网络开销等问题,因为你可能正在传输几兆字节的数据,而不是原本的几KB。对我们来说,选择非常明确。这也是为什么我要向你们展示这一点的原因:使用 Kafka,你可以发送高达 30 到 40 兆字节的消息,在媒体流场景下这似乎是正常的。而 EventBridge 最多只能处理 256 千字节的消息,这完全是另一个世界了。
我们实际上通过另一种模式解决了这个问题,称为“声明-检查”(claiming-checking)模式。这个模式非常简单:借助 Amazon EventBridge Pipe 提供的“增强”(enrichment)功能,我们可以拦截消息并执行某些操作。在我们的案例中,主要是做消息的转换和验证,最后将事件内容存储到 S3 中。然后我们获取该对象的 S3 key,并将其发送到 EventBridge。EventBridge 再将消息广播给所有消费者,理论上所有消费者都可以访问 S3 并从中拉取完整数据。通过这种方式,我们获得了一个天然可扩展的 API,无需自己构建和维护 API。通过这两个模式,我们成功解决了最主要的问题——数据一致性,这是我们之前面临的头号难题。至于可扩展性,即使你为了三个用户启动了 500 个任务,系统也能正常运行,因为我们实现了真正的横向和纵向扩展。
在进入可扩展性和弹性讨论之前,我想先展示一种替代方案:数据复制。在过去五年里,我们一直在谈论事件驱动,但实际上还有一种方式是通过数据复制来实现数据共享。在这个例子中,我们使用的是 Postgres 的逻辑复制(pglogical replication)。设想一下,你从 Kafka 获取数据,将其规范化后写入数据库,而其他服务可能只需要其中一部分数据。这时你可以通过数据复制机制,仅复制所需的表——例如总共 20 张表,你只需要其中 2 张。这两种方式都是可行的,具体选择取决于公司需求。你需要权衡利弊,我也尝试在这张表格中总结了一下:
我们知道,事件驱动提供了良好的解耦能力:你只需发布事件,订阅方自行订阅并按需重建数据。你可以使用 Aurora、DynamoDB、文本文件甚至 Excel,完全自由灵活。而数据复制则意味着“一个数据库统治一切”。你一开始用了 Postgres,那么所有人都必须用 Postgres。这本身并不坏。你也可以从 Postgres 构建管道把数据迁移到其他数据库,但我看不到太大意义。真正的问题在于,团队之间的耦合会加剧,因为源头数据库变成了瓶颈。所有订阅方的数据库容量必须等于或大于源数据库,一旦源端更改 schema,整个系统就会崩溃。你又回到了需要协调各团队的状态:“我先部署,你再部署”。当然,这种方式也能工作,并且也有优势。
我想给你们一个真实的对比。我不太喜欢这些选项的一点是它们带来的运维复杂性。你必须管理子网、子组、CDR 等,因为所有数据库现在都需要部署在完全独立的网络中,非常复杂。为什么?因为它就是这么设计的。我在十年前就这么做过。
可扩展性与弹性
我们完成了第一部分,也就是如何解决数据一致性问题。最后,在这个由两人组成的团队中,我们可以专注于弹性和可用性。想象一下所有人同时访问系统时的场景:只有两种可能的结果,要么你的架构优雅地扩展以应对压力,要么你就彻底失败。没有第三种选择。这正是我们需要开始思考的地方,因为真正的问题并不在于 Lambda 或集群本身,而在于我们设置的自动扩展规则。所有的缓存机制都缺失了,那些为实现特定扩展能力而存在的最佳实践也全部被忽略。因此,我们转向了无服务器(serverless)或托管服务。这是我们使用的一系列服务的组合。快速过一下:我们使用 Route 53,经典的 DNS 服务。如果你只依赖它,就会面临 DNS 相关的问题,因为它运行在公共互联网上。我总是建议搭配使用其他服务,比如 CloudFront 或 Global Accelerator。简单来说,当你发起请求时,会被路由到边缘节点,并通过 AWS 的私有网络连接到目标区域。主要区别在于,CloudFront 是 CDN,可以在边缘节点进行缓存;而 Front Door 则是应用负载均衡器或 API Gateway。我通常首选 API Gateway,因为它工作在网络的更高层级,开箱即用地支持 CORS 和压缩功能。它们的作用只是将请求路由到你的计算服务。这里我只举两个例子:Lambda 和 Fargate。两者都是无服务器方案。Lambda 非常神奇,能在毫秒内从零扩展到上千个实例。虽然它有自己的限制,但你无需做任何额外操作,只需确保代码正确,并为所用运行时选择合适的内存配置即可。Fargate 提供更多控制权,但也意味着有更多的出错可能。从 Fargate 出发,当然也可以连接缓存和数据库。AWS 上有无数种数据库服务,我仅以 Aurora、用于 NoSQL 的 DynamoDB,以及仍在使用的 RDS 为例。
由于我们是在构建高可用系统,就必须讨论这些服务各自的 SLA(服务等级协议)。令我惊讶的是,API Gateway 和 Lambda 并不是可用性最高的服务。尽管它们完全是无服务器的,让你可以专注于业务逻辑、编码等核心任务。实际上,最佳组合是应用负载均衡器加 Lambda。这些数字非常理论化,只能为你打下基础。但如果你不采用诸如断路器、重试机制、超时处理等最佳实践,即使后端服务如 Lambda 拥有 99.99% 的可用性,你也依然可能导致整个服务宕机。最佳实践永远适用。架构为你提供了基础,但真正达成高可用目标的是代码实现。数据库方面也是如此。目前 AWS 中真正完全无服务器的数据库只有 DynamoDB 和 Aurora Serverless。你可以将其视为“即用即弃”型服务。它们本身就是 API,你无需关心子网、VPC 等底层细节。使用起来非常简单,一切都能正常运作,所有复杂工作都由 AWS 完成。如果你更倾向于传统方式,则可以选择 Aurora 或 RDS。这时你就必须考虑 VPC、子网等各类配置问题,总有需要你手动管理的部分。数据库的选择通常是难题。关系型数据库几乎能解决所有问题。20 年前我就这么做了,几乎所有场景都能用关系型数据库搞定。但这确实是一个选择。这张表实际上揭示了一个权衡:你在用运维复杂性换取成本优势,同时也在用可靠性换取使用上的简便性。每种服务都有其优势。我在这里列出 RDS 单节点加副本的配置,是因为你可以用这种架构上线生产环境。没人禁止你这么做。它能正常工作——直到不能为止。你需要考虑所有潜在的故障情况。这才是真正关键的问题:当问题发生时,你愿意承受多长时间的停机?
还有许多其他细小的问题值得我们思考,而这些问题在概念验证阶段往往不会显现。我可以用一个文本文件作为数据库来做概念验证,它确实能运行。但有时候还会出现其他情况。比如复制延迟、故障转移、脑裂场景:到底哪个节点真正宕机了?当你自动将读操作切换为写操作时,突然又恢复了写入,结果导致两个节点同时进行写入。而此时原先的写入也恢复了——原来是一次误报。现在你有了两个同时接受写入的节点,它们保存的数据完全不同,且无法协调一致。所有这些细节都是我们在设计 API 时需要权衡的因素。一切正常,直到它出问题为止。我认为,作为构建者和开发者,我们必须把这些潜在问题呈现给决策者和管理者,迫使他们做出选择。请记住,你不可能同时拥有全部好处,必须有所取舍。一旦确定了所需的服务级别,你就能计算出你的可用性水平。只需将各个组件的可用性数字相乘,即可得出整体可用性,例如 99.78%。快速对比一下单区域与多区域架构:如果你考虑使用两个区域,这就是最坏情况下的停机可能性。其区别在于,用户是满意地沉默,还是愤怒地在社交媒体上发文抱怨你的服务不可用。更糟的是,你的经理会随时打电话给你,告诉你“什么功能都失效了”。当然,这些只是理论上的数字,但如果你正在构建一个国际化的应用,这种思考至关重要。最终目标决定了你的架构选择。
基于以上分析,这是我们当前的服务模板,也就是之前提到的那些服务。只有一点是之前未提及的:Momento Cache。我们不再使用 Redis 或 Valkey,连同相关的容量规划等责任,全都交由第三方托管。其余所有服务均为托管服务。这套基础设施的核心理念是:永不宕机,并具备优雅恢复能力。这几乎解决了我们所有的可靠性问题,因为每个组件都具备弹性。我们使用全局表(Global Tables),这是 DynamoDB 或 Aurora 提供的功能,可将相同数据复制到另一个区域,用于灾难恢复等场景。我们也使用区域级服务,如应用负载均衡器(ALB)、Lambda、Fargate,全部由平台管理,无需人工干预。然而,如果我们聚焦于单个区域内部,仍然存在单点故障风险,这一点无法避免。
在转向多区域架构之前,我们经历了一系列迭代,使应用程序变得更加高可用、更具可扩展性。其中一种模式就是基于“单元”(cell)的架构。为了简化说明,假设我们有三个国家:德国、奥地利和瑞士。我可以只部署一个 Fargate 服务和一个 Lambda 来处理所有请求。但如果由于某种原因该服务或 Lambda 达到限制而崩溃,整个应用就会随之瘫痪。因此我们开始拆分流量。根据国家、用户类型(例如付费用户和免费用户)来划分流量。理论上,我们已经从一个 Lambda 扩展到了六个 Lambda。这意味着原本一个 Lambda 可以在毫秒内从零扩展到 1000 个实例,现在总共可以支持 6000 个。我们还可以进一步按平台拆分,我们有五个平台,这样就变成了 30 个 Lambda,意味着可以在一毫秒内处理多达 30,000 个请求。当然,你需要向 AWS 申请调整配额,但代码只需编写一次,通过 CI 流程即可完成部署。无论你有一个 Lambda 还是 30 个,本质上并无差别。Fargate 服务也是如此。与其维护一个庞大的单体服务,不如将其拆分为多个小型服务,它们可以根据流量模式、内存、CPU 等指标独立伸缩。对我们而言,关键在于缩小故障影响范围(blast radius)。这种架构还支持灰度发布:我们可以仅在德国、iOS 平台、免费用户群体中部署新版本,先在此环境中测试、监控、收集指标,再逐步推广。这为我们带来了极大的灵活性。
基于单元的架构也可以应用于数据库,这正是我们可以讨论的地方。对于 DynamoDB 和 DSQL 这类完全无服务器的数据库,你可以轻松实现这种架构,因为无论你使用一个还是十个,成本几乎相同。但当你使用 RDS、Aurora 或 OpenSearch 时,AWS 就开始从中盈利了。在我们的业务规模下,拆分数据库并不合理。例如,我们在每个区域只部署一个数据库和一个 OpenSearch 实例,统一为所有流量提供服务。我们通过其他手段克服了这一限制,比如引入前一架构所没有的缓存机制。这是一个流式应用,所有人加载的内容完全相同。“获取布拉德·皮特的个人资料”这样的请求,结果始终一致,尽管平台层面可能有细微差异,但内容基本不变。因此,没有必要把数据库当作昂贵的缓存来使用。我们采用了三层缓存策略:第一层是 CloudFront,用于处理重复请求;第二层是内存缓存,仅存储在 Lambda 或 Fargate 实例中,根据不同配置保存热点数据;第三层是在数据库前端设置缓存,在我们的案例中使用的是 Momento。这是一个提供完整缓存功能并支持实时通知的服务。只有当所有缓存层都未命中时,才会访问数据库。实际上,即使在高峰期,我们对数据库的直接访问率也低于10%,甚至低至5%。这使得我们能够使用非常小的数据库集群,而不是为了应对请求量而配置超大内存的巨型集群。这也让我们得以转向无服务器架构,并根据请求实现弹性扩展,或采用类似主-主(active-active)的部署策略。例如,如果你使用 Aurora 全局表,写入操作只能在一个区域进行。一旦考虑多区域部署,我就不喜欢这种架构。我更倾向于像 DynamoDB 那样的主-主模式,支持在多个区域同时写入。当你拥有一个小集群时,其总体成本实际上与全局表相当甚至更低。良好的缓存策略带来了显著优势。当然,你必须始终考虑缓存失效问题,但这是一种权衡取舍。虽然看似不可能,但我们成功实现了。有些场景下,我们在 CDN 中仅缓存几秒钟,这对每秒接收上亿请求的服务来说,带来了巨大收益,效果极佳。
我们谈到了单元隔离和缓存。第三个我们重点投入的是数据平面。如今我们拥有大量 Lambda 和微服务,因此开始实施自动化运维。通过应用负载均衡器监控计算资源,我们构建了自动故障转移机制。如果某个区域出现问题,Route 53 会将流量切换到另一个区域。此外,我们还设置了监控 CPU、内存等资源的告警系统,这些告警会触发事件。基于这些事件,我们可以在 Fargate 和 Lambda 之间动态调整流量。我们并非依赖单一服务承载全部流量,而是同时使用多个服务,并根据当前的流量模式和预设规则,动态选择最优的服务组合。这也引出了著名的“多区域”架构。为什么要采用多区域?我不会说每个人都必须这么做,关键在于具体需求。我们刚才提到的实践让服务更具可扩展性和可用性,减小了故障影响范围,但我们仍未真正具备韧性。要实现真正的韧性,就必须走向多区域部署。并不是每个服务都需要多区域架构。公司内部有许多服务,只有那些一旦宕机就会导致整个应用瘫痪的服务才需要主-主多区域部署。比如书签服务宕机了,其实没人太在意,我们可以稍后重启它。是否部署多区域取决于服务的重要性。我们确保所有服务都能在多区域环境中部署。常见的策略包括备份恢复、引导灯(pilot light)、热备和主-主模式,具体选择哪种策略取决于服务的关键程度。
我认为,多区域部署真正的障碍在于思维模式。过去十年我一直做着同样的事,现在为什么要让基础设施变得更复杂?或者有人会说,这是 SRE 团队的事,让他们去做吧。但当你去找 SRE 团队时,他们可能会回答:“不,我们很忙,不想做。” 总会有阻力存在,最终形成一种文化:一旦出问题,就硬扛,替经理顶责,等待风波过去。我们一直以这种方式前行。但现在我认为,多区域部署已经变得很简单。云服务商正在不断改进,使得在多个区域部署相同的架构变得轻而易举。再说,我们做的是流媒体应用,目前已有三个区域。由于被另一家公司收购,我们正计划扩展到欧洲其他地区。我认为,作为架构师、作为技术建设者,我们的职责就是让这一切变得透明。我不决定业务方向,但我可以向我的经理清晰地展示各种选择的后果。如果经理愿意承担在高峰期因停机带来的责任,那对我来说,用 Excel 当数据库也未尝不可。
当我向经理提出这个问题时,我会确保他们真正理解问题所在。试想一下,如果这种情况发生了会怎样?大多数情况下我们会说,多区域架构的成本是 x,而我们每年只遇到一次故障,也许这个 x 比收入损失还要大,这样看起来是合理的。但我们并不是在谈论法兰克福区域完全宕机的情况——我记得2021年那次好像停机了8小时?出了点问题,持续了好几个小时。我所说的是一些持续存在的小问题。在过去几个月里,我记得出现过 DNS 问题,导致 CloudFront 无法连接到应用负载均衡器的源站;我也记得 Lambda 出现过问题,某个时刻所有实例都被回收重启,这本身没问题,但会导致大量冷启动,产生连锁反应。有时我们还会看到 Fargate 任务突然消失:你原本有10个任务,突然就变成零了。这种情况确实会发生,而且系统通常能自动恢复。但问题是,当这类事件发生时,往往需要很多人加入会议排查,甚至要拉上副总裁、CTO 一起参与。这一切耗费了多少成本?到最后却发现只是虚惊一场。所以关键不在于“是否应该采用多区域架构”,而在于“如何让多区域架构变得更便宜”。当管理层真正理解了问题并承担起责任后,他们就能判断:额外支出是否值得用来覆盖潜在风险。因为我非常确定,如果我们高峰期出问题,基础设施的投入成本远低于因此造成的收入损失。
还有其他一些我们需要思考的问题:如何让多区域架构变得负担得起?显然,多区域比单区域更贵,这不需要天才也能明白。我们使用诸如数据库之类的服务,就需要数据复制,这些都会产生成本。在单区域中这些服务就已经很昂贵了,AWS 对数据传输等各项操作都会收费,这些开销永远存在。我说的“负担得起”,其实是指我们服务的演进过程。我们最初使用服务器,后来改用 API Gateway。但众所周知,API Gateway 在大规模场景下比应用负载均衡器贵得多。到底贵多少?视情况而定。仅从 API Gateway 切换到应用负载均衡器这一项,我们就节省了90%的成本。虽然我们需要在代码中自行实现 CORS,以及一些头部和网络压缩功能,但这并不复杂。还有很多其他方法可以让整体成本降低。例如,我们将工作负载在 Fargate 和 Lambda 之间切换,就实现了高达60%的成本下降。如果你经常接触 AWS Heroes 或者来自 AWS 的人,他们会告诉你 Fargate 比 Lambda 更便宜。从单价上看确实如此,但实际上取决于规模。根据我们的计算,在每天请求数低于3000万到5000万的情况下,Fargate 反而比 Lambda 更贵。因此我们采取的做法是动态分流:根据当前流量和用户数量进行计算,评估每个任务能处理多少请求,然后调整流量分配。我们始终让 Lambda 处于待命状态以应对突发高峰——因为一旦流量激增,可能会使 Fargate 任务的内存或 CPU 使用率达到极限。与其等待 Fargate 扩容(这可能需要五六分钟),不如立刻将流量切到 Lambda。Lambda 可能会有冷启动,也可能没有,但这就是我们应对流量波动并降低成本的方式。到了深夜没人看电视的时候,我们就关闭 Fargate(让它缩容至零),把全部流量转移到 Lambda 上。一切都视具体情况而定。正是这些细节使得多区域架构变得可行且经济。另一个我认为极具价值的方面是自动化。实际上,我们对所有事情都进行了监控,并尽可能实现操作自动化。这意味着我们构建了一个无需人工干预的基础设施。当事故发生时,我收到邮件,正准备登录查看,却发现问题已经解决了。这才是控制成本的关键。事实上,最终的结果并不是完全消除成本,而是让成本与所获得的可靠性保护相匹配。这就是我现在看待多区域架构的方式。我希望 AWS 能提供更简便的部署方式,因为我的 CI 流水线非常庞大。现在想象一下我要部署30个 Lambda 函数,目前是一键完成,但我必须逐一查看每条路由。我希望能有一个可配置的区域列表,点击一次就能自动完成所有区域的部署。
总结
在一年半的时间里,我们几乎完成了所有服务的重构,实施了所有这些微小的改进。我们不再陷入代码泥潭。我的前团队(现在我已经调岗)再也没有出现过因可用性或可靠性引发的问题。我们遇到的唯一 bug 是因为我们人为测试不充分、部署不当所致,但系统本身运行正常,后来才发现是代码逻辑错误。我们通过这些小改动降低了成本,一切运行顺畅。当然,公司里仍有很多人认为这套架构过于复杂,因为他们只看到组件数量:“你在用 EventBridge,又在用 S3,还在用这个那个,三个组件的事我一个集群就能搞定。” 但 Serverless 实际上让我们看清了原本隐藏在一个巨大黑箱内部的结构。他们看到的是复杂性,我看到的却是向 AWS 的职责委托。我把底层问题交给 AWS 去处理,让团队可以专注于真正重要的业务逻辑,而不是因为自己写代码而引入新的 bug。
问答环节
参与者1:如果我理解正确的话,当某个区域发生故障时,你们会将流量重定向到另一个区域。你也提到每个区域都有一个独立的数据库。那么你们是如何保持这些数据库之间的同步的?是否存在数据不同步的风险?
Daniele Frasca:它们不会失去同步,原因很简单,因为我们有 Kafka。我们有两个管道,因此在每个区域中同时构建正确的数据管道。实际上,它们几乎实时地存储相同的内容。虽然存在故障的可能性,但如果我们在死信队列(DLQ)中发现某些消息,系统会自动进行重试。
参与者 1:所有区域都这样吗?
Daniele Frasca:是的,所有区域完全一样。当我们采用双活架构时,会在每个区域都写入数据;如果某个特定服务不需要双活模式,我们就使用全局表。你只需写入一次,但可以从两个区域读取。
Renato Losio:我非常好奇你们是如何测试区域故障的。
Daniele Frasca:区域故障?AWS 提供了故障注入服务。我把它叫做“Daniele 猴子”的混沌工程实践。实际上,我会手动停止一些服务,然后观察发生了什么。很多问题我们可以在各个阶段通过团队模拟自行发现。每次发布重大变更时,我们都会进行大规模模拟。在过去一年半的时间里,仍有不少场景尚未覆盖到。每当出现问题,我们总是尝试将其自动化处理,通常都能立即解决。在构建系统时,我始终以失败为出发点来设计。关键就在于此——我不去考虑正常运行的情况,而是思考它可能如何出错,并尝试逐步降级应对。有时候这很困难,也许我们会在代码中专门为了测试而加入延迟或抛出异常等逻辑,主动触发故障。这就是我通常的做法。
Renato Losio:你刚才提到夜间会根据流量高低,在 ECS 和 Lambda 之间动态切换扩展,这种机制是响应式地依据实际流量调整的。我想知道,在某些情况下,比如你预知会有流量高峰,知道将有大型活动或其他预期事件发生时,是否需要提前预热资源?还是说你们始终只是被动响应流量变化?
Daniele Frasca:当然,在媒体行业尤其如此,当你有直播电视内容时,黄金时段通常是晚上 7 点到 11 点。我不喜欢很多人的做法,也就是提前把所有任务都扩容上去。我认为应该更具备响应性。这也是为什么我们优先使用 Lambda。应用负载均衡器的配置默认 Lambda 权重为 100%。随着时间和流量增长,我开始逐渐转移流量。最终最多达到 Fargate 承载 90% 高峰流量,Lambda 占 10%。Lambda 只用于应对突发溢出流量。如果出现激增,我们可能会降低 Fargate 比例,比如设为 70%,与此同时,Fargate 会缓慢但稳定地启动更多任务来分担负载,而新增的流量则由 Lambda 先行承接。这就是我们的运作方式:优先使用 Lambda,并立即自动开始流量迁移。
Renato Losio:所以你不需要提前规划。
Daniele Frasca:没错。无论是 1 个用户、1000 个用户,还是 10 万、20 万个用户,我都无需关心,一切都会被自动处理。我们做过一些计算:单个 Fargate 任务配置约为 4GB 内存和 2 个 vCPU。通过压力测试我们知道,在 CPU 和内存开始显著上升之前,这个任务能支撑大约 3000 到 4000 个并发请求。但我们不会等到使用率达到 60% 或 70% 才行动,因为那时再扩容就太迟了。我们允许像其他人一样,用小规模集群来缓冲少量溢出流量。理论上,这样就能应对所有流量冲击,具体流量大小并不重要。
查看更多 [带文字记录的演讲](https://www.infoq.com/transcripts/presentations/)
录制于:

2026 年 5 月 11 日
作者:
- Daniele Frasca
Seven.One Entertainment Group 首席工程师