写点什么

构建可扩展的流媒体基础设施:因为观众不会等到明天

作者:Daniele Frasca

  • 2025-12-25
    北京
  • 本文字数:6890 字

    阅读完需:约 23 分钟

大小:3.43M时长:19:59
构建可扩展的流媒体基础设施:因为观众不会等到明天

在流媒体领域,挑战是即时的:用户现在就在观看电视节目,而不是计划明天再看。当系统在黄金时段发生故障时,根本没有恢复窗口;观众会立即离开,并可能一去不返。一年半前,ProSiebenSat.1 Media SE 面临着为国际用户扩展流媒体应用的挑战。

 

这项任务落在了两名没有 AWS 经验的开发人员身上。通过持续迭代改进,该团队彻底重构了核心服务,消除了单点故障,并显著提升了系统的可用性与可扩展性。此类转型的现实情况是:并不存在现成的蓝图,唯有通过不断迭代和学习,才能让系统逐步变得更好。

 


最初的架构在高层面上非常简单:一个工作进程订阅 Kafka 上的主题,执行数据转换,并将数据存储到数据库中。

 

另一个组件,一个依靠 GraphQL 的 API,用于处理前端请求。如果正确实现,这种架构本身并无问题。

 

然而,这一特定实现未能随着业务发展而演进。服务器持续过载,数据库在压力下不堪重负,每次流量激增都会导致整个系统崩溃。

 

数据库运行在单个节点上,没有缓存机制,且各服务之间的数据始终不一致。更复杂的是,六个服务各自为政,缺乏统一标准。这种情况迫切需要从根本上改变方法。

 

解决方案是转向无服务器架构——并非因为无服务器是潮流,而是因为它能让团队专注于最重要的事情:代码本身。在十八个月的时间里,架构从最初的基础服务逐步演进为不同类型的多区域部署配置,包括根据服务重要程度不同而采用不同程度的主备架构。

 


这种无服务器架构解决了核心问题,因为它具备自动扩展能力。通过利用 AWS 托管服务,可用性和弹性方面的责任转移给了 AWS。团队还解决了一个重大痛点:部署时间从一个半小时缩短到了几分钟。这一基础架构为后续向更具成本效益的多区域能力演进铺平了道路。

 

背景

ProSiebenSat.1 Media SE 是欧洲最大的广播媒体集团之一,在多个国家运营电视频道和流媒体服务。其流媒体应用 Joyn 服务于德语区(DACH,包括奥地利、瑞士和德国,持续处理数百万次用户请求。管理层的期望在纸面上看似简单:一切内容随时可用、系统完全可扩展,而且成本低廉。但现实是,这些目标彼此冲突。基础设施的质量直接反映在用户体验上,而在流媒体业务中,任何停机都会立刻被用户察觉。高可用性和高可扩展性的服务不可能以最低成本实现,这一点必须让决策者清楚地认识到。

 

用户的期望非常直接:打开应用,就能播放视频。他们并不关心德国是否正在直播一场重要的足球联赛,或者世界其他地方是否有其他大型直播活动;无论流量如何激增,他们都期望获得无缝的访问体验。然而,当底层系统无法正常运行时,要满足这些期望就变得极其困难。

 

本文讨论了两个主要问题。首先是数据质量:用户可能在某一页看到某个视频,点击查看详细信息后,跳转到另一页却发现该视频已不可用。数据一致性问题长期困扰着整个系统。其次是提升整体系统的可扩展性与弹性。

 

数据一致性与质量



审视原有架构后,我们发现了一个关键问题:一个团队负责多个服务,却没有任何统一标准。每个服务都订阅了同一个 Kafka 主题,但执行的验证和转换逻辑却完全不同——有些数据被写入数据库,有些则没有;有些服务在出错时抛出异常,有些则静默忽略。这种不一致的行为导致同一个视频在不同页面上有时显示为可用,有时却显示为不可用。本应只需几分钟就能排查的问题,实际上却耗费了数小时。

 

另一个问题是企业总线架构带来的:内部服务之间通过企业总线相互通信,却将各自的内部状态暴露到了总线上——这是一种公认的反模式。之所以出现这种状况,是因为正确使用 Kafka 需要额外的基础设施支持,而团队往往选择走捷径,而不是从一开始就正确地实现解决方案。

 

中心辐射模式

为解决上述问题,我们引入了一种名为中心辐射(Hub and Spoke)的基础设施模式,我们内部称之为总线网格(bus mesh),以此建立清晰的服务边界。

 


该模式涉及三个参与者。Kafka 作为企业总线和事件存储,所有事件都存放于此;EventBridge 负责消息的扇出;EventBridge Pipe 充当点对点的中间层,用于拦截消息并执行验证、转换和处理。

 

这一模式的精妙之处在于:每个服务仅与其本地总线——即 EventBridge——进行交互。无论是微服务内部通信、与其他微服务通信,还是与企业总线通信,都只通过唯一接口:EventBridge。消息通过类似发布/订阅(Pub/Sub)的规则进行路由,而订阅者(无论是 SQS、SNS 还是其他服务)都被隐藏在这个抽象层之后。该模式还解决了单一事实来源的问题,确保所有数据在分发给各内部团队之前,都必须先经过 EventBridge。

 

稀疏消息与完整状态消息

在事件驱动的应用中,需要仔细权衡两种消息类型之间的取舍:稀疏消息和完整状态消息。稀疏消息仅包含基本信息,订阅者需要额外调用 API 来获取更多数据。这就要求构建能够承受海量请求的 API。完整状态消息则包含处理所需的所有信息,简化了下游处理逻辑。但缺点是:一旦消息结构中的属性发生变化,即使只有少数发布者和订阅者,也可能引发破坏性变更。此外,当消息从千字节级别增长到兆字节级别时,网络传输开销就变得不可忽视,必须纳入架构考量。

 


对于流媒体来说,选择是明确的。Kafka 支持高达三十到四十兆字节的消息,这对于媒体流式传输来说是正常的。而 EventBridge 仅能处理 256 千字节,这代表了完全不同的限制。

 

票据检查模式



票据检查模式解决了这一限制。借助 Amazon EventBridge Pipes 的数据增强功能,消息在传输过程中被拦截,进行转换和验证后存储到 S3 中。随后,S3 对象的键被传递到 EventBridge,EventBridge 将该轻量级消息扇出给所有消费者,各消费者再根据该键从 S3 获取实际数据。这种方法提供了一种无需自定义开发和维护即可自动扩展的 API。

 

该解决方案的精妙之处在于其简洁性:S3 负责大负载数据的存储与检索,而 EventBridge 则专注于轻量级消息的路由。各个消费服务仅在需要时获取所需的数据,避免了对任何单一组件造成过载。这两种模式共同解决了困扰系统多年的首要数据一致性问题。

 

数据复制作为一种替代方案

另一种值得考虑的替代方法是采用数据复制,而非事件驱动架构。通过使用 Postgres 的 pglogical 复制功能,可将来自 Kafka 的数据在数据库中进行规范化处理,其他服务则通过表级复制仅接收部分数据——例如,在总共二十张表中只复制其中两张。这两种方法各有适用场景,具体选择取决于公司的实际需求。

 

两种方法之间存在权衡取舍。事件驱动架构提供了良好的解耦能力:事件一旦发出,订阅者便可接收并以任意格式重建数据,例如存入 Aurora、DynamoDB、文本文件,甚至电子表格。而数据复制则会形成一个统一的数据库来管理所有服务。如果从 Postgres 开始实施,则意味着所有服务都必须使用 Postgres。

 


源数据库会成为性能瓶颈;所有订阅者都需要同等规模甚至更大的数据库,而任何模式变更都会导致整个系统崩溃。团队不得不重新回到协调部署的老路上。

 

数据复制还会引入显著的运维复杂性:必须管理子网、安全组和 CIDR 地址块,因为各个数据库必须部署在相互隔离的网络中。

 

可扩展性与弹性

在解决了数据一致性问题之后,关注点转向了系统的弹性和可用性。当所有用户同时涌入时,只有两种可能的结果:架构优雅地自动扩展,或者灾难性地彻底失败。真正的问题并非出在 Lambda 或集群本身,而是缺失自动扩缩容规则、缺乏缓存机制,以及忽视了最佳实践。这一认知促使团队转向无服务器架构和托管服务。

 

服务架构与 SLA

该服务组合包括用于 DNS 解析的 Amazon Route 53,但仅依赖这种组合会使系统暴露在公共互联网上的 DNS 问题风险之中。相比之下,CloudFront 或 Global Accelerator 是更优的解决方案——它们通过边缘节点将请求路由至 AWS 的私有网络。CloudFront 充当具有边缘缓存能力的 CDN。系统的前端入口通常是一个应用负载均衡器或 API 网关,负责将请求路由至后端计算服务。

 

API 网关运行在网络栈更高层级,能自动处理 CORS 和压缩等功能。它可将请求路由至 Lambda 和 Fargate,两者均为无服务器选项。Lambda 能在毫秒级内从零实例自动扩展到一千个实例,只需编写正确的代码并配置合适的内存即可。Fargate 则提供更高的控制力,但也引入了更多潜在的故障模式。在这两类计算服务之后,是缓存层和数据库层:包括 Aurora、用于 NoSQL 场景的 DynamoDB,以及 RDS。

 

要构建高可用系统,必须深入理解服务等级协议(SLA)。令人意外的是,API 网关和 Lambda 并非提供最高可用性的组合。理论上最优的搭配是应用负载均衡器配合 Lambda。然而,这些数字只是理论基础——如果不实施最佳实践(如熔断器、重试机制、超时控制等),即便是像 Lambda 这样标称可用性高达 99.99%的服务也会发生故障。无论选择何种基础设施,最佳实践始终不可或缺。架构提供了基础,但最终能否在生产环境中实现承诺的可用性指标,取决于代码的质量与健壮性。

 

数据库选型

数据库的选择也面临类似的权衡。目前 AWS 上真正的完全无服务器数据库仅有 DynamoDB 和 Aurora DSQL。它们如同“即用即忘”的 API,无需关心 VPC 或子网配置,底层复杂性全部由 AWS 托管,使开发者能专注于数据建模和访问模式设计。而使用 Aurora 或 RDS 则需要自行管理 VPC 和子网,虽然增加了运维负担,但提供了更传统的完整关系型数据库功能。

 

数据库选型通常是架构设计中最困难的决策。关系型数据库几乎能解决任何问题,但代价是更高的运维复杂性;而在成本、可靠性与简洁性之间,也需要仔细权衡。每种服务都有其独特优势。例如,带只读副本的单节点 RDS 实例可以快速投入生产——直到它突然无法支撑为止。此时必须考虑复制延迟、故障切换以及脑裂等场景。归根结底,核心问题在于:当故障发生时,你能容忍多长时间的停机?

 


构建者必须将这些问题清晰地呈现给决策者,并要求他们做出明确抉择。高可用性与低成本的解决方案无法兼得。单区域与多区域架构之间的差异,可能直接决定用户是满意的还是在社交媒体上愤怒地投诉。在构建国际化应用时,这些理论上的可用性指标变得至关重要。

 

该服务模板整合了前述各项服务,并采用 Momento Cache 替代 Redis 或 Valkey,将缓存管理完全委托给第三方服务。整体基础设施的设计理念聚焦于永不宕机和优雅恢复。DynamoDB 的全局表或 Aurora 会将数据复制到另一个区域,以实现灾难恢复。所有区域性服务,包括应用负载均衡器、Lambda 和 Fargate,均为全托管服务,最大限度降低运维负担。

 


单元化架构

在实施多区域部署之前,系统经过多次迭代,以提升应用的可用性与可扩展性。单元化架构是其中一种关键模式。

 


在德国、奥地利和瑞士这三个国家,原本可以由单个 Fargate 服务或 Lambda 函数处理所有请求。然而,一旦该服务发生故障或达到其容量上限,整个应用将全面瘫痪,影响所有区域的所有用户。通过按国家和用户类型(付费用户与免费用户)拆分流量,一个 Lambda 函数被拆分为六个独立实例。由于 Lambda 能在毫秒级内从零扩展到一千个实例,这种拆分使并发处理能力从一千提升至六千。

 

进一步按平台拆分,包括 iOS、Android、Web 和智能电视等五个平台,可得到三十个 Lambda 函数,从而支持每毫秒三万次请求。代码只需编写一次,并通过 CI/CD 流水线自动部署,因此从开发角度看,Lambda 函数的数量是无关紧要的。

 

这种架构的核心优势在于缩小了故障爆炸半径:若某个单元出现问题,其他单元仍能正常运行。此外,它还支持精准灰度发布,例如先向德国的 iOS 免费用户发布新版本,在得到充分测试和监控后再逐步扩大范围。

 

单元化架构也可扩展至数据库层。对于 DynamoDB 和 Aurora Serverless 等完全无服务器的数据库,这种拆分不会带来额外成本;但若使用 RDS、Aurora(非无服务器)或 OpenSearch,成本会迅速倍增。在多数规模下,拆分数据库并不划算。相比之下,引入此前架构中缺失的缓存机制则更为有效。

 

多层缓存策略

流媒体应用的内容具有高度重复性。例如,某位演员的个人资料在数百万次请求中几乎不变。若将数据库当作昂贵的缓存使用,不仅浪费资源,也毫无必要。

 

为此,采用三层缓存架构:

 

CloudFront 在边缘处理重复的请求,无需回源;Lambda 或 Fargate 实例内的内存缓存短期高频访问的热键;Momento 专用缓存服务:以及 Momento 作为数据库前的专用缓存服务,用于处理其余所有请求。

 

这一分层策略将数据库在黄金时段的负载降至 10%以下,甚至低至 5%。因此,可以使用更小规模的数据库集群,而非为峰值请求量配置庞大集群。这不仅显著降低成本,还使无服务器架构的弹性扩展成为可能,并为原本因成本过高而不可行的多活架构打开了大门。

 

以 Aurora 全局表为例,其写操作仅限于单个区域,属于次优方案;而 DynamoDB 支持多区域写入的真正多活架构则更为理想。

 

数据平面自动化

随后,团队投入建设数据平面自动化能力。通过监控应用负载均衡器,系统可在区域故障时自动触发切换:Amazon Route 53 会将流量导向其他国家的可用区域。同时,对 CPU 和内存的告警会触发事件,动态在 Fargate 与 Lambda 之间转移流量。多个服务可同时承载流量,调度决策基于实时流量模式。

 

多区域战略

并非所有应用都需要多区域部署。前述实践已能显著提升可扩展性、可用性并缩小故障影响范围。但要实现真正的韧性,多区域部署必不可少——尽管并非每个服务都需要。

 

只有那些一旦失效会导致整个应用崩溃的服务才需要多区域多活架构。例如,书签功能失效影响甚微,无需多区域部署。但所有服务都应具备多区域就绪能力,并根据业务需求选择合适的容灾策略:备份恢复、预热灯、温备或多活。

 

多区域落地的主要障碍往往不是技术,而是组织思维定式。对基础设施复杂性的抗拒,容易催生一种被动等待问题自行消退的文化。如今,随着云厂商持续优化跨区域部署能力,多区域实施已变得相当直接。

 

构建者的责任是保持透明度。技术人员不决定业务优先级,而是清晰地向管理层呈现选项。当管理者理解并愿意承担黄金时段可能停机的风险时,基础设施决策的责任就落在他们身上。如果多区域部署成本为 X,而每年仅发生一次故障,那么 X 可能远超实际收入损失。

 

讨论的重点并非像 2021 年法兰克福区域长达八小时的灾难性故障这类极端事件,而是那些持续发生的小问题:例如 DNS 故障导致 CloudFront 无法连接源站、Lambda 实例回收引发冷启动并产生连锁反应,以及 Fargate 任务意外消失等。

 

虽然所有这些问题最终都能自动恢复,但每次事件都会触发紧急响应流程,牵涉多名工程师、副总裁(VP)甚至 CTO。这种应急响应所耗费的人力与时间成本日积月累,相当可观。因此,关键问题不在于是否应该采用多区域架构,而在于如何让多区域架构变得经济可行。

 

让多区域部署变得可负担

多区域部署本质上比单区域成本更高。数据库需要跨区域复制数据,而所有复制操作都会产生成本,既包括存储费用,也包括数据传输费用。要让多区域变得可负担,关键在于战略性演进服务架构,而非简单地将所有组件在每个区域完整复制一遍。例如,将 API 网关替换为应用负载均衡器,仅需在代码层面自行实现 CORS 头和压缩功能,就能在路由成本上节省 90%,带来显著回报。

 

另一项技术实现了 60%的成本降低:根据流量模式在 Fargate 和 Lambda 之间动态切换。尽管从表面定价看,人们普遍认为 Fargate 比 Lambda 更便宜,但实际上成本高度依赖于规模和使用模式。分析表明,在日请求量低于三千万至五千万次的情况下,Fargate 的成本反而更高,因为容器需要持续运行,存在固定的基线开销。

 

通过计算每个 Fargate 任务可处理的请求数量,系统能够智能地在两种计算服务之间动态调度流量。Lambda 始终处于待命状态,用于应对突发流量,且在空闲时零成本。当流量激增时,与其等待 5-6 分钟去扩容 Fargate 任务,不如立即将流量切到 Lambda。

 

Lambda 可能会遭遇少量冷启动,但其响应速度仍远快于等待容器扩容。在夜间观众稀少的时段,Fargate 可缩容至零,由 Lambda 高效处理剩余流量。这一策略在保障可用性的同时,实现了成本最优化。

 

自动化对于管理这种复杂性至关重要:通过指标和告警全面监控系统,并自动执行响应动作,从而构建出在事故发生时无需人工干预的基础设施。目标是打造自愈系统——在工程师收到告警通知之前,问题已被自动检测并修复。等到事故邮件送达、工程师登录排查时,故障早已悄然解决。

 

这种自动化彻底改变了多区域部署的经济模型:不再需要专职运维人员全天候盯守仪表盘,基础设施能够自我管理。结论很明确:成本并未消失,但相对于所获得的保护能力而言,已变得合理且可控。对自动化的投入,通过降低运维负担和加速故障恢复,很快就能收回成本。

 

结论

从脆弱的单体架构迈向高韧性的多区域系统,这一转型历程证明:即使资源有限、经验不足,变革依然可行。一个最初对 AWS 毫无经验的两人开发团队,成功消除了单点故障,实现了数据一致性,并构建出无需人工干预即可自动扩展的基础设施。关键洞见在于:无服务器和托管服务并非追逐技术潮流,而是一种战略性的责任委派——它解放了工程师,使其能专注于创造业务价值。

 

曾经被认为过于复杂且昂贵的多区域架构,如今通过渐进式方法变得触手可及:首先利用事件驱动模式解决数据一致性问题;接着实施单元化隔离与智能缓存;最后自动化计算服务间的流量调度。成本虽未完全消除,但已与所获得的韧性保障相称。最重要的是,技术决策必须对业务干系人保持透明,因为他们才是最终承担风险与收益权衡的一方。

 

原文链接:

https://www.infoq.com/articles/evolution-backend-streaming-application/

2025-12-25 15:181

评论

发布
暂无评论
发现更多内容
构建可扩展的流媒体基础设施:因为观众不会等到明天_架构_InfoQ精选文章