基于单元架构构建可扩展云系统:Netflix与Slack的高可用性秘诀

本文深入探讨了基于单元的架构如何通过将系统分解为独立隔离的单元来提升容错能力、确保高可用性,并详细介绍了Docker容器、路由策略、故障注入等关键技术实现。

基于单元架构构建可扩展云系统

引言:为什么弹性是架构性的

在现代云基础设施中,弹性不是附加功能,而是必须融入系统基础架构的核心要素。当应用程序扩展到数千万用户并跨越多个世界区域时,传统的高可用性假设在压力下失效。即使采用多可用区部署、复制和自动扩展,系统仍然脆弱,容易发生关联故障。

这些不仅仅是技术错误,而是系统级故障,会通过单体部署、集中控制平面和紧密耦合的微服务级联传播。一个地区的故障进程会导致连锁反应,淹没共享服务,击垮依赖节点,并模糊可观测性管道。

为了避免这种故障链,现代云基础设施正转向基于单元的架构(CBA)。这种方法借鉴了航空航天行业的故障隔离区和数据库的分布式隔离思想,通过将组件结构解耦为完全独立的实体集合,每个实体能够独立执行系统逻辑的一部分,而无需全局同步。

定义单元:稳定性的边界

单元是一个封闭环境,不是片段或副本。它容纳计算、存储、运行时、数据处理以及针对特定流量、地理区域或租户的控制逻辑。它不仅用于扩展性能,还用于限制故障的影响范围。

每个单元包含:

  • 无状态服务和API
  • 复制或专用状态存储
  • 路由网关和入口规则
  • 遥测代理和日志管道
  • 具有范围生命周期的自主控制平面

单元设计时假设相邻单元可能并将会失败。因此,它保持本地健康检查、回滚机制和最少量的出站依赖。当一个单元失败或隔离时,其他单元仍能处理请求,使整个系统更具生存能力和可预测性。

与简单的水平扩展(仅在相同服务的实例间共享负载)不同,CBA更进一步,提供独立的运行时单元,即隔离的集群。单元之间不是紧密协调的,不基于共享缓存或同步,而是最终一致、最小化且容忍延迟的协调。

传统扩展的局限性

垂直扩展易于部署,但很快受到硬件和预算限制,且不提供冗余。水平扩展提高了吞吐量,但未能有效确保故障隔离。大多数水平扩展方案仍依赖中央组件进行流量管理、身份验证和配置,当其中任何一个失败时,整个系统都会受到影响。

此外,扩展无法解决嘈杂邻居、重试风暴或配置错误的部署问题。如果基础设施共享,一次失败的发布可能影响所有客户。

基于单元的架构更注重弹性而非性能。它预期分布式系统会以意外方式失败,并默认设计为失败。它将故障范围限制在系统的一个紧密边界的小部分,防止小故障传播导致全局中断。

弹性单元的架构

每个单元设计为独立实体,是一个微观生产环境。服务在单元中打包在容器中,特别是Docker,实现了微服务的标准打包,并使暂存、暗部署和生产单元保持一致。

标准单元包含:

  • 自己的编排器或Kubernetes集群
  • 本地服务网格(如Istio或Linkerd)
  • 私有消息队列或事件总线
  • 区域特定的专用或复制数据库
  • 遥测收集器和警报规则
  • 边缘负载平衡或边缘DNS路由

单元通常按地理分区。例如,美国西部单元处理美国西部的所有客户,欧盟单元处理欧盟流量。一些供应商还按租户、用户群组或功能集进行分区。概念是逻辑、数据和服务都在单元内,单元可以在没有全局协调的情况下恢复。

Docker作为单元隔离的推动者

容器不仅是这种隔离级别的主要推动者,还是基本构建块。单元中的每个微服务都与其依赖项、环境变量和运行时配置一起打包。Docker镜像用作传输机制,简化了跨不同环境的测试、暂存和推广。

Docker还确保不可变性。当应用程序崩溃需要重启或回滚时,可以从注册表中拉取在暂存期间通过所有测试的相同镜像并重新部署,而不会出现意外。部署风险和平均恢复时间都最小化。

Docker还促进了大多数架构类型之间的操作对等性。用于构建生产镜像的相同CI/CD管道也用于混沌测试、暗单元和金丝雀发布。这种对等性简化了中断调试、根本原因检测和自动化修复。

Docker没有施加任何限制,必须与适当的路由和编排逻辑结合,使每个单元独立运行。Docker是计算单位,单元是弹性单位。

分区策略:地理、租户和功能

通过将系统分解为单元来进行分割既是组织决策也是技术决策。一些公司按地理分割,每个单元服务来自特定地理区域的用户,并通过地理感知DNS或边缘路由器路由流量。

其他按客户或租户分区,非常大的客户可以有自己隔离的基础设施单元。这种方法增强了安全性,支持自定义SLA,并提供租户级可扩展性。

第三种是按功能划分,例如在一个单元中处理支付,在另一个单元中处理内容交付。基于领域的模型易于转换为团队所有权,并减少服务间依赖。

每种方法都有其权衡。区域分区更易于路由,但需要更多重复信息。租户分区支持隔离,但资源预算成本更高。功能分区适合领域驱动设计,但如果边界不正确,会导致单元间耦合。

强大的单元结构采用这些模型的组合。公司可以按地理单元和功能划分组织,一个地区有多个服务,具有独立扩展、独立回滚计划和可观测性仪表板。

路由和控制:保持单元自治

路由在基于单元的设计中起关键作用。边缘流量必须根据地理、用户身份或分区键路由到正确的单元。路由必须快速、确定,并能处理故障条件。

大多数实现多层路由。地理感知解析器在DNS层路由到区域负载均衡器。第7层代理(如Envoy或HAProxy)执行基于路径的路由到区域内的服务。路由逻辑倾向于外部化和无状态,以避免协调问题。

每个单元的控制平面仅限本地范围。部署、修补、配置更新和扩展按单元进行。正是这种解耦支持了弹性。当控制平面不可用或崩溃时,不会影响其他单元。

因此,大多数基于单元的系统具有每单元编排层,可以是每单元一个集群或跨多个账户的一组ECS服务。甚至配置管理工具(如Consul、AWS SSM或ArgoCD)也是每单元范围的。错误推出或错误配置的爆炸半径最小化。

单元还必须为自己的生命周期事件做好准备。单元A的部署不应影响单元B。可观测性和警报也必须限定范围,仪表板、指标和日志跨单元边界过滤。

可观测性和调试:每单元上下文是关键

基于单元的设计中最被忽视的问题之一是可观测性。由于每个单元是隔离的,集中监控工具无法获得适当的粒度。跟踪、指标和日志必须按单元提取,然后在边界内关联。

每个单元通常运行自己的可观测性代理。Fluent Bit或Logstash收集日志,Prometheus抓取指标,OpenTelemetry收集器收集跟踪。每个数据流都推送到集中存储(如Loki、Grafana或CloudWatch),但每次都包含单元ID。

仪表板必须为每个单元构建。仅意识到服务全局宕机是不够的,操作员必须通知哪个单元宕机以及其余单元是否正常。这实现了更快的事件分类和基于证据的回滚。

警报也不相同。单个单元错误率上升不应引起全局警报,必须由单元本地SLO处理警报。仅当多个单元受影响时才宣布系统范围事件。

单元故障还需要不同可观测性工具之间的合作。如果服务立即失败,可能根本不会记录任何内容,此时跟踪和指标介入。需要服务边界之间的分布式跟踪来确定故障是单元内部还是外部依赖引起的。

故障注入和弹性测试

基于单元的架构是混沌工程的完美候选。由于单元是自治的,可以将故障注入一个单元而不会使整个系统宕机。这允许测试故障场景、验证回退和测量恢复时间。

故障注入可以包括:

  • 禁用单元中的主要服务
  • 诱导延迟或丢包
  • 模拟依赖中断
  • 阻止对数据库或存储的网络访问

Docker容器使过程可重复。可以初始化一个带有注入故障的服务测试实例,并观察单元在压力下的响应。由于每个单元都有可观测性堆栈,影响易于测量。

这些必须自动化并与发布管道集成。新版本可以在暗单元中暂存,并在推送到所有单元之前针对混沌场景执行。如果存活,则传播到其他单元;否则快速且隔离地回滚。

高级部署和暗单元:通过隔离实现安全

在当代场景中,每次部署都伴随风险,能够在类似生产场景中测试更改而不影响用户至关重要。基于单元的设计通过所谓的暗单元或影子环境原生支持这一点。

暗单元是一个没有实时流量的活动单元,模拟生产单元的形状和结构,包括服务、遥测堆栈和路由逻辑。然而,流量是人工生成或从实际用户复制的,不会导致双重处理。它有助于在生产环境中部署、监控和测试新构建,而不会让用户受到回归影响。

大规模运营的组织使用暗单元测试配置修改、基础设施补丁和新发布,然后再部署供一般使用。通过混沌测试和模拟流量,暗单元是识别未知故障点的有效方法。

部署过程的成熟度通常可以通过是否使用暗单元来衡量。在大多数生产场景中,暗单元用作分阶段推出的第一阶段。一旦单元在合成流量和镜像流量下按预期行为,相应的更改将推送到区域或低流量单元。这是一个增量过程,受自动化回滚程序控制,监控错误率、延迟和CPU使用指标的显著回归。

这种单元部署模型有助于立即检测和隔离错误更改。由于每个单元是独立的,有错误的部署仅影响受影响的单元,并且可以轻松回退。系统的其余部分不受影响。

故障转移和恢复:单元作为冗余执行域

弹性最好不是由系统很少失败来定义,而是由恢复的速度和优雅程度来定义。最成功的措施之一是使用基于单元的系统。由于每个单元都是独立的封闭系统,它可以单独失败而不影响整个系统。

最值得注意的是,恢复是本地化的。如果某个地区的单元因基础设施丢失而失败,请求可以转移到相同功能的不同单元。了解健康恶化的路由层通过基于策略的重定向规则促进这一点。

其他配置甚至更进一步,在其他地方维护热备用。这些是活动实例的精确副本,始终同步状态或复制流量,可以在主节点失败时接管。一些使用热故障转移,保留容量但仅在中断时启动服务。

除了策略之外,使这一切发生的是单元的独立性。单元在运行时不在执行状态共享,信息异步复制并容忍延迟。从一个单元切换到另一个单元不是全局协调或分布式事务静默的练习。

这种故障转移方法特别适合支持动态边缘路由控制的流量管理解决方案。无论使用基于DNS、基于API网关还是基于服务网格的策略,一旦检测到事件,流量可以在几秒钟内路由到可用单元。

不仅是故障转移,恢复还包括可观测性、诊断和回滚。由于单元单独报告遥测,事件响应者能够立即识别影响范围,以及影响是系统性的还是局部的,并采取行动。回滚也隔离到失败的单元,减少了复杂性和平均解决时间。

组织对齐:映射到单元的团队

基于单元的架构不完全是技术模式,它还塑造组织结构。团队与其单元对齐,负责其领域的基础设施、服务和用户体验。

这种结构消除了跨团队依赖,加速了决策,并最大化部署速度。由于每个团队是其单元的所有者,它可以更快地迭代,在隔离中测试新功能,并根据其流量模式进行优化。

在大型组织中,这种模式是巨大的文化胜利。团队对结果负责,而不仅仅是代码。他们维护自己的可观测性堆栈,处理自己的事件,并根据区域限制定义其架构。

这种自治培养创新。团队可以自由尝试新的部署模型、服务网格或遥测系统,而无需全局协议。最佳实践随着时间的推移从下而上出现,基于实际结果而不是自上而下的指令。

它还支持不同的成熟度。一个团队可能运行最新的服务网格和基础设施即代码配置,另一个团队正在迁移或运行更成熟的SLA。这远非失败,而是对大型系统进化性质的拥抱,当一致性不如可靠性和移动方向重要时。

但这种模式需要纪律。没有明确的边界和通信约定,单元会以有害的方式漂移。版本偏差、配置差异和不兼容的可观测性模式可能导致。这种模式的有效实施通过采用跨单元治理流程和内部工具来强加一致性,在自治和共享约定之间权衡。

实际应用:领先公司在做什么

基于单元的架构不再是实验性的。几家知名公司已将其用于可扩展性和弹性问题。

最好的案例可能是Slack。他们从单体PHP后端过渡到单元架构,因为工作区需要隔离。每个工作区是一个独立的故障域,使Slack能够按客户独立扩展,避免嘈杂邻居问题,并使中断的爆炸半径更小。当一个工作区宕机时,其他工作区不受影响。

Netflix采用微服务和基于单元的架构组合。每个地区有多个单元,每个单元服务一部分客户,提供本地化视频、推荐和遥测。Netflix按地理和功能划分工作,以避免基础设施故障、流量峰值或应用程序错误在全球传播。他们从内部工具集中维护单元级指标、警报和部署管道。

DoorDash采用基于单元的原则来解决其交付网络的复杂性。服务网格中使用基于Envoy的路由,将请求路由到特定城市或市场区域的专家单元。这有助于延迟、成本和故障隔离。单元还用于实验,使产品团队可以在一个市场测试新功能而不影响整个用户群。

这些组织详细记录了他们的设计。他们共享的不是确切的技术栈,而是隔离、自治和优雅降级的概念。每个的成功突显了基于单元的思维在技术和组织上都能扩展。

局限性和权衡

没有免费的架构。基于单元的方法增加了复杂性、冗余资源消耗和开销。每个单元需要自己的基础设施、监控工具集、部署管道和团队专业知识。

只有当故障隔离的价值足以证明时,这种开销才有价值。对于小型网络或初创公司可能太多,仅在规模上才有价值,即使短暂停机也会对业务产生实质性影响。

另一个权衡是数据一致性。由于单元是独立的,共享状态更难以控制。系统需要采用最终一致性,实现数据协调,并不允许依赖它们的同步全局事务。

路由复杂性也是一个问题。制作和维护高级路由器,能够在不同情况下将用户引导到适当的单元并不容易。系统必须处理地理、用户身份、负载分布和故障类型,而不创建自己的单点故障。

分布式事件也使调试更加困难。当问题出现时,确定是单元特定、全局还是跨领域可能需要关联多个可观测性域。

最后,并非所有负载在分割时都是平等的。某些服务由于其性质始终具有全局需求。例如,全局搜索索引、跨租户报告或实时协作编辑不适合单元边界。在这些情况下,混合设计是常态。尽可能使用单元,全局服务被视为系统关键,具有冗余和特殊照顾。

战略价值:超越弹性

尽管主要依赖弹性,CBA的用途不止于此。它通过减少协调需求实现更快的实验速度,通过逐步添加新区域帮助地理扩展,尤其在多租户系统中,数据隔离至关重要时帮助强制执行安全边界。

它还为进一步的进化发展奠定了基础。随着事物变得越来越复杂,需要能够吸收变化、在失败中生存并在不确定性面前执行的架构。基于单元的设计为系统定位了那个未来。

通过分离资本,他们获得控制;通过分离故障区域,他们获得正常运行时间;通过拥有基础设施边界,他们获得以自己的速度进化的能力。

结论

基于单元的架构是强大

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计