Dan Sullivan 2020-04-15
从基础设施层到应用前端,你的系统都有其极限。一旦超出这些阈值,系统行为就会变得不可预测:服务性能下降、中断发生。虽然我们通常将事故响应理解为定位并修复根本原因,但在许多情况下,并不存在单一的故障根源。例如,服务请求突然激增可能导致消息队列中积压大量请求——这本身通常不是问题;但如果同时存在一个配置错误,导致消息队列集群无法自动扩容,就可能引发服务严重降级甚至中断。
尽管具体的行为难以准确预测,但 SRE(站点可靠性工程)和 DevOps 团队可以针对宏观层面的问题(如资源饱和、高延迟、过载等)提前规划。其中一项关键措施就是设计支持优雅降级的服务架构,即在系统承压时提供有限但可用的功能,避免灾难性崩溃。例如,网络工程师会设计网络在某条路径失效时自动重路由流量——虽然这可能导致其他路径负载升高、延迟增加,但数据最终仍能抵达目的地。大型企业级应用同样可以从这种“优雅降级”的设计理念中获益。
在为系统设计弹性和优雅降级能力时,请考虑以下四种成熟实践,并按对用户影响从大到小的优先级进行选择:
- 卸载工作负载(Shedding workload)
- 延后处理工作负载(Time shifting workloads)
- 降低服务质量(Reducing quality of service)
- 增加容量(Adding more capacity)
下面逐一详解:
1. 卸载工作负载(Shed the workload)
当电力设施的用电需求超过其承载能力、可能危及整个电网时,工程师会主动切断部分区域的供电——这被称为“负载卸载”(load shedding),也是应对分布式系统过载的一个恰当类比。例如,当 API 调用、数据库连接或持久化存储的请求量超过当前系统容量时,可主动丢弃部分请求。
关键设计考量在于:决定丢弃哪些请求。
最简单的做法是在达到阈值时直接拒绝所有请求。虽然实现简单,但无法区分不同服务等级或请求优先级。例如,服务健康检查请求理应比普通业务请求拥有更高优先级,不应被同等对待。
2. 延后处理工作负载(Time shift the workload)
对于某些服务而言,直接丢弃请求可能不可接受。例如,如果你发起了一场极其成功的营销活动,大量新客户涌入电商平台下单,此时绝不能丢弃订单交易请求。更好的做法是将超额工作负载的处理时间延后,即解耦“请求生成”与“请求处理”。
消息队列(如 Apache Kafka、Google Cloud Pub/Sub)常被用于此类异步处理场景,以缓冲突发流量。但采用此策略时,需仔细考虑事务范围:虽然每个独立服务可能支持本地事务,但如果一系列服务调用必须作为一个整体成功或失败,则需要额外逻辑来确保跨服务事务能正确回滚。
3. 降低服务质量(Reduce the quality of service)
当你既不想丢弃请求,也不愿延后处理,但仍需减轻系统负载时,可考虑临时降低服务质量。例如,在系统压力较大时,可暂时关闭部分非核心功能,或将数据库查询从精确结果切换为近似结果(approximate queries)。
这种方法的优势在于:仍能处理所有请求,避免了丢弃;同时不会引入延后处理带来的时间延迟。
4. 增加容量(Add more capacity)
从用户体验角度出发,理想的做法是既不丢弃请求、也不延后处理、更不降低服务质量。因此,增加容量通常是应对负载激增的最佳选择。云服务商支持虚拟机等基础设施的自动扩缩容,Kubernetes 也能根据负载变化自动调整 Pod 数量。这些操作通常无需人工干预——除非可用资源池已耗尽。例如,公有云中某个可用区发生故障,会导致区域内其他可用区资源需求骤增,可能触发资源配额限制。
最终,通过前瞻性容量规划与主动监控,团队能够构建更具韧性的服务。
预期“可预期”的问题
现代软件承诺的是 100% 的可用性,但 SRE 和 DevOps 团队必须为那些不可避免却又可预见的负载模式与复杂故障模式做好准备。如果在应用设计阶段就充分考虑这些因素,你将更有能力构建出具备弹性、能够优雅降级的系统——理想情况下,甚至不会影响到用户的实际体验。