分布式系统过载治理:如何通过较小服务控制请求节奏

分布式系统过载治理:如何通过较小服务控制请求节奏 简介控制平面与数据平面的规模不匹配在海外某些大型科技公司团队会构建由许多独立小型服务组成的大规模分布式系统。每个服务都承担特定职责并通过定义清晰的 API 与其他服务交互。这种架构让团队能够独立扩展、演进和运行各个服务。在云服务架构中一种常见模式是将系统拆分为两类服务一类负责执行客户请求通常称为数据平面另一类负责管理和提供客户配置通常称为控制平面。本文将讨论数据平面和控制平面之间的多种交互方式以及如何通过这些方式避免分布式系统过载。在许多架构中规模较大的数据平面服务会调用规模较小的控制平面服务。但本文也会介绍另一种实践让较小的服务掌控系统变化和请求流转的节奏从而提升系统在过载场景下的稳定性。在企业内部推进这类分布式架构治理时相关工作往往贯穿目标制定、需求评审、开发、测试、发布上线和复盘沉淀等多个环节。团队可以借助PINGCODE这类智能化研发管理工具将控制平面与数据平面改造相关的需求、任务、缺陷、测试结果、发布记录和 Wiki 知识沉淀串联起来让系统过载治理过程更加自动化、数据化和可追踪。某些弹性计算服务就是包含数据平面和控制平面的典型架构示例。数据平面由运行客户计算实例的物理服务器组成。控制平面则由多个与数据平面交互的服务组成负责执行以下功能告诉每台服务器需要运行哪些计算实例让正在运行的计算实例与虚拟私有云配置保持同步接收服务器上报的计量数据、日志和指标将新软件部署到服务器。虽然在类似架构中具体名称和职责可能有所不同但这些系统通常有两个共同点数据平面和控制平面需要保持同步。数据平面需要从控制平面接收配置更新控制平面也需要从数据平面接收运行状态。数据平面机群的规模通常比控制平面机群大 100 倍甚至更多。数据平面调用控制平面的过载风险在构建这类架构时团队需要仔细考虑一个关键决策API 调用方向应该如何设计是让规模较大的数据平面服务器群调用控制平面服务器群还是让规模较小的控制平面服务器群调用数据平面服务器群对于许多系统来说让数据平面服务器群调用控制平面服务器群通常是更简单的做法。在这类系统中控制平面会公开一些 API用于检索配置更新和推送运行状态。数据平面服务器可以定期或按需调用这些 API。这种方式的好处是每个数据平面服务器都会主动发起 API 请求因此控制平面不需要主动跟踪整个数据平面服务器群。对于每个请求控制平面服务器只需要返回更新后的配置通常是通过查询持久化数据存储实现然后就可以结束本次请求处理。为了提供容错性和水平扩展能力控制平面服务器通常会放在负载均衡器之后。这是构建起来相对简单的一类分布式系统。这种架构的简洁性带来了天然的高可用优势。然而在实践中也发现当数据平面服务器群的规模比控制平面服务器群大 100 倍甚至更多时这类分布式系统需要经过精细调优才能避免过载风险。在稳定状态下规模较大的数据平面服务器群会定期调用控制平面 API。由于各个服务器发起请求的时间彼此不相关控制平面的负载会相对均匀地分布系统也能平稳运行。但是一旦数据平面的调用模式发生意外变化控制平面的负载就可能突然增加甚至导致控制平面过载。以下情况都可能引发这种变化数据平面中的代码或配置错误导致 API 调用频率升高系统从故障中恢复时所有数据平面服务器同时尝试调用控制平面 API少量 API 调用失败后触发重试进一步增加控制平面负载导致更多失败和更多重试形成循环。通过分析这些情况可以发现它们有一个共同点环境中的一个小变化会让大量客户端以相关联的方式运行并开始同时发起并发请求。如果系统没有经过精细调优这种行为变化就可能压垮规模较小的控制平面。当并发请求量发生剧烈变化时系统负载可能超过自身极限。一旦越过极限系统的有效吞吐量也就是系统真正完成的有效工作量可能会迅速下降甚至接近于零。为了避免系统过载控制平面和数据平面都需要仔细调优确保控制平面负载不会超过极限。在控制平面侧负载均衡等机制可以帮助系统在面对意外负载变化时继续运行。在数据平面侧合理调整请求频率并在重试过程中加入退避和抖动可以降低请求速率相关性上升所带来的风险。解决规模不匹配使用对象存储作为中介这种架构最大的挑战是数据平面与控制平面之间的规模不匹配。控制平面服务器数量远少于数据平面服务器数量。在这类场景中团队通常会考虑借助存储服务来解耦双方。在大规模内容分发和配置下发场景中常见做法是使用对象存储服务作为中介。控制平面不再直接向数据平面暴露 API而是定期将更新后的配置写入对象存储桶。数据平面服务器随后轮询这个存储桶获取最新配置并将其缓存到本地。同样为了掌握数据平面的运行状态控制平面也可以轮询对象存储桶。数据平面服务器会定期将相关状态信息写入该存储桶。这种架构具有多项优势。它易于实现而且对象存储服务通常具备很强的可扩展性即使面对规模非常大的客户端集群也能轻松承载。此外随着数据平面集群规模增长控制平面集群仍然可以保持相对较小的规模。即使控制平面发生故障数据平面也能继续使用上一次已知的配置运行。即便服务器陆续启动或停止服务系统仍然可以维持基本运行。这种特性被称为静态稳定性是分布式系统中非常理想的属性。由于这些优势这种架构在大型技术系统中非常常见。某些内部网络功能虚拟化系统就采用了类似架构。这类系统的数据平面包含处理客户流量的设备这些设备需要了解网络负载均衡、NAT 网关、私有连接等资源的配置信息。在这类架构中控制平面中的定期任务会扫描包含客户配置的托管 NoSQL 数据库表并将这些配置写入多个对象存储文件。随后数据平面会定期下载这些文件并使用其中内容更新内部路由配置。不过尽管这种架构很受欢迎它并不适用于所有场景。对于包含大量动态配置的系统来说持续重新计算完整配置并将其写入对象存储可能并不现实。弹性计算服务就是一个典型例子。在这类系统中大量数据平面服务器需要了解自己正在运行的实例配置而这些配置会不断变化包括虚拟私有云配置、身份与权限角色、块存储卷等。在另一些系统中控制平面配置变更必须在几秒甚至更短时间内反映到数据平面这一点非常关键。例如容器服务需要快速通知服务器应该运行哪些新容器。对于这类系统轮询对象存储中定期更新的文件可能无法满足可接受的传播延迟要求。反转 API 调用方向让较小控制平面掌控节奏当遇到这些场景时团队会寻找其他方法让规模较小的服务器集群能够控制请求在系统中的流转速度。其中一种方法是反转 API 调用流程由规模较小的控制平面服务器集群将配置变更推送给规模较大的数据平面服务器集群。在这种控制平面集群规模较小、且工作节奏由控制平面掌握的架构中系统在过载时会更有弹性。即使控制平面过载或容量不足它仍然可以继续运行只是运行速度会变慢。这类规模反转方案通常涉及架构评审、任务拆解、上线排期、风险审批、跨团队沟通和故障演练等协作工作。团队可以使用通用项目协作系统统一管理任务、项目、文档、IM 沟通、目标、日历、甘特图和审批流程降低复杂架构改造中的沟通成本和信息遗漏风险。不过这种方法也存在一些缺点实施难度更高控制平面需要维护所有可用数据平面服务器的最新清单以便把配置更新推送到每台服务器任意时刻都可能有部分数据平面服务器不可访问控制平面必须能够处理这种情况控制平面需要一种机制确保每台数据平面服务器都能从至少一个控制平面服务器接收配置更新同时还要确保没有任何一个控制平面服务器负责过多数据平面服务器。最后一点尤其具有挑战性因为控制平面服务器也会不时启动或停止服务。一种常见做法是让每个控制平面服务器负责一部分数据平面服务器具体职责由一致性哈希算法决定。这种方法与分布式系统中的领导者选举机制有许多相似之处。在这种设计中每个控制平面服务器都会向共享数据存储发送心跳表明自己处于活跃状态。每个控制平面服务器也会独立扫描这个数据存储查找最近发送过心跳的其他控制平面服务器列表。随后它会以确定性的方式执行一致性哈希算法计算出自己负责的数据平面服务器子集。解耦发现方向与控制流方向为了避免前面提到的复杂问题还可以将“发现方向”和“控制流方向”解耦。在这种设计中不再由控制平面服务器主动发起连接而是让规模较大的数据平面集群中的每台服务器与某个控制平面服务器建立长期连接。这可以通过将控制平面服务器放在网络负载均衡器之后来实现。连接建立后控制平面服务器会接管连接并通过该连接发起 API 调用。这种方式类似于 HTTP 服务器推送机制。如果控制平面服务器不可用或繁忙它可以拒绝连接。随后数据平面服务器会尝试连接另一个控制平面服务器。如果第一个连接因为任何原因终止数据平面服务器也会尝试连接另一个控制平面服务器。由于 API 调用速率由控制平面服务器决定这种方法具备与“控制平面向数据平面推送配置变更”类似的过载恢复能力。不同的是这种方法不需要控制平面维护所有数据平面服务器的最新清单也不需要实现一致性哈希或其他分布式算法。所有决策都可以基于本地状态完成。不过这种方法的缺点是控制平面服务器和数据平面服务器之间需要使用更复杂的通信协议。在规模极度不匹配的情况下例如数据平面服务器数量比控制平面服务器数量多 1000 倍甚至更多还可以进一步调整连接方式。数据平面服务器可以不主动发起完整连接而是先发送一个小型 UDP 请求然后等待控制平面发起连接。由于处理这类 UDP 请求的成本远低于执行 TCP 和 TLS 握手这种模式可以避免仅仅因为连接请求突然涌入就导致控制平面过载。某些弹性计算服务控制平面就采用了这种方法的变体将配置更新推送到底层虚拟化系统。结论通过规模反转降低分布式系统过载风险如果不关注服务及其客户端之间的相对规模分布式系统就可能面临过载风险。在客户端数量远远超过服务实例数量的情况下客户端行为中的细微变化就可能让服务负载迅速升高甚至超出其承受能力。这类行为变化通常难以预测因此需要对服务及其客户端进行精细调优才能有效应对。在大型系统设计中团队会特别关注客户端数量超过服务数量 100 倍甚至更多的情况并寻找能够让规模较小的服务集群控制系统变化速度的方法。最简单的方法是使用对象存储这类大规模服务作为小型服务集群与其客户端之间的中介。如果这种方法不可行团队可以考虑规模反转也就是由规模较小的服务集群调用规模较大的服务集群。虽然这些方法会增加实现复杂性但实践表明即使规模较大的服务集群持续增长它们也能让规模较小的服务集群更容易扩展和运维并降低系统因规模不匹配而过载的风险。