Skip to content
xiaoyuuuuuupeng
Go back

1.1 服务韧性的基本原理

参考资料:

- Google SRE:Handling Overload

- Sentinel:基本原理

- Sentinel:系统自适应保护

- Sentinel:熔断降级

一、为什么需要服务韧性

服务韧性是指系统在流量突增、资源紧张、依赖变慢或局部故障时,仍能维持核心功能,并在故障消失后自动恢复的能力。

一个服务的处理能力不是恒定不变的。即使入口 QPS 没有增加,也可能因为以下因素进入过载状态:

过载通常会形成一条恶性循环:

依赖变慢或流量升高

请求排队、并发增加

线程池/连接池耗尽,RT 继续上升

超时和重试增多

系统负载进一步升高,最终发生级联故障

因此,韧性治理的目标并不是“让所有请求都成功”,而是:

  1. 只接收当前能够处理的请求,避免服务被拖垮;

  2. 优先保证核心流量,主动舍弃非核心流量;

  3. 将故障隔离在局部,防止沿调用链扩散;

  4. 在依赖或系统恢复后,安全、渐进地恢复流量。

二、核心概念辨析

机制主要解决的问题常见触发依据典型动作
限流进入系统或资源的流量过大QPS、并发数、系统负载拒绝、排队、匀速通过
自适应限流服务能力随环境动态变化,固定阈值失效成功率、RT、CPU、Load、并发数动态调整放行比例
熔断下游持续变慢或失败,继续调用会拖垮自身慢调用比例、异常比例、异常数暂时停止调用下游
降级完整功能无法稳定提供或成本过高限流、熔断、超时、资源不足返回兜底、缓存或简化结果
隔离一个资源耗尽全部共享资源线程池、信号量、连接池、资源配额将故障限制在独立资源池内

这些机制并非互相替代。入口限流保护本服务,下游熔断保护调用方,资源隔离限制故障影响范围,降级则决定无法正常处理时返回什么结果。

三、固定限流及其局限

3.1 什么是限流

限流是为服务设置可接受的流量边界。例如,压测得到服务单机可稳定处理 2,000 TPS,便可以在入口设置阈值;超过阈值的请求将被拒绝、排队或降级。

固定阈值实现简单、行为可预测,适合以下场景:

3.2 为什么仅靠固定 QPS 不够

QPS 只表示请求数量,不等于资源消耗。两个 QPS 相同的时间窗口,可能因为请求参数、缓存命中率、数据规模或下游 RT 不同,消耗完全不同。

假设服务的固定上限是 2,000 TPS:正常情况下单次调用耗时 20 ms,系统可以稳定运行;当下游耗时升高到 500 ms 时,即使入口只有 1,000 TPS,在途请求数也会显著增加,线程池和连接池仍可能被耗尽。此时固定阈值虽然没有被触发,却已经失去保护作用。

因此,容量判断应优先贴近真实瓶颈,综合关注:

四、客户端自适应限流

4.1 为什么服务端限流还不够

服务端应该在过载时快速拒绝请求,但仅依赖服务端拒绝仍有两个问题。

第一,失败请求可能触发客户端重试。 如果每个调用方都立即、无限制地重试,原始流量会被成倍放大,形成重试风暴。

第二,拒绝本身也有成本。 对 Redis、Memcached 一类内存访问服务而言,业务处理可能很轻,网络 I/O、连接维护和协议解析反而占据主要开销。大量请求即使最终被拒绝,也可能先耗尽服务端资源。

因此,当客户端发现近期大量请求被后端以“超出配额”或“过载”拒绝时,应主动降低出站流量,使超出预算的请求直接在本地失败,不再进入网络。

4.2 Google SRE 自适应节流算法

每个客户端实例维护一个滑动时间窗口,例如最近两分钟,并统计:

客户端拒绝新请求的概率为:

P(reject) = max(0, (requests - K × accepts) / (requests + 1))

这不是“拒绝请求的数量”,而是每个新请求被客户端本地拒绝的概率。

例如,最近窗口内 requests = 100accepts = 40K = 1.5

P(reject) = (100 - 1.5 × 40) / 101 ≈ 39.6%

而当请求数为 3、成功接受数为 2、K = 1.5 时:

P(reject) = max(0, (3 - 1.5 × 2) / 4) = 0

这类小样本中的偶发失败不会立即触发限流。工程实现中还应设置最小样本量,进一步过滤启动阶段和低流量场景的随机抖动。

4.3 K 值如何影响限流

4.4 与重试策略配合

自适应限流不能代替重试治理。合理的重试策略应同时包含:

4.5 客户端限流的适用边界

客户端限流依赖本地观察数据。对于请求非常稀疏的客户端,样本量太小,难以及时、准确判断后端状态。此时可结合服务端返回的 Retry-After、集中配额、服务发现健康状态或更长统计窗口,但需要权衡额外的依赖和延迟。

五、入口系统自适应保护

客户端自适应限流主要保护下游,入口系统自适应保护则用于防止当前服务自身被压垮。

5.1 为什么要从系统整体进行保护

一个服务可能同时承接 Web、Dubbo、消息消费和内部任务等多种流量。这些流量最终共享 CPU、内存、线程池和连接池,只对单个接口限流,无法保证应用整体稳定。

因此,系统保护规则应作用于应用级入口流量,而不是只作用于某一个接口或资源。接口级限流仍然有价值,但它解决的是局部配额和流量公平问题,两者需要配合使用。

5.2 Sentinel 支持的系统指标

Sentinel 系统自适应保护综合应用的整体指标,使入口流量与系统负载保持平衡。常见阈值类型包括:

不建议孤立地依赖单一指标。例如 CPU 很低并不代表系统健康,I/O 阻塞或下游变慢时,线程和连接可能已经大量堆积。

5.3 BBR 思想与系统容量估算

Sentinel 借鉴 TCP BBR 的思想,将请求处理过程类比为水管:

根据 Little’s Law(利特尔法则),稳定状态下:

并发数 ≈ QPS × 平均 RT(秒)

Sentinel 使用历史观测到的 maxQps × minRt 估算系统容量:

系统容量 ≈ maxQps × minRt

其中 minRt 应换算为秒后再参与计算。minRt 近似表示请求没有明显排队时的处理时延,maxQps 表示系统曾达到的最大处理速率。当 load1 已超过启发阈值,且当前并发数又超过估算容量时,系统开始限制入口流量。

这样做比“Load 一高就全部拒绝”更合理:Load 只作为启动保护的信号,实际容量由系统已观测到的吞吐和时延共同决定,从而尽量在稳定的前提下维持吞吐量。

5.4 系统规则触发后如何处理

触发系统保护后,不应让请求长时间阻塞等待,因为排队会继续占用线程、连接和内存。更合理的处理方式是:

  1. 快速拒绝超出容量的请求;

  2. 返回明确、可识别的过载状态,避免客户端盲目重试;

  3. 对可降级请求返回缓存、默认值或简化结果;

  4. 优先保留登录、支付、存档等核心链路;

  5. 记录触发规则、系统指标、调用来源和降级结果,便于告警与复盘。

5.5 系统自适应保护的限制

六、下游自适应熔断与降级

6.1 为什么要熔断下游

当下游服务响应变慢时,上游请求会持续占用线程、连接和内存。若上游仍不断发起调用和重试,下游的局部故障会逐层向上传播,最终形成服务雪崩。

熔断器的作用类似电路保险丝:当某个依赖在统计窗口内表现持续异常时,暂时切断调用,使请求快速失败或直接进入降级逻辑,为下游恢复和上游释放资源争取时间。

熔断通常配置在调用端,并针对具体依赖或资源生效,而不是针对应用所有入口统一生效。

6.2 熔断器的三个状态

达到熔断阈值
Closed(关闭) ───────────→ Open(打开)
    ↑                         │
    │ 探测成功                │ 熔断时间结束
    │                         ↓
    └──────────────── Half-Open(半开)

                       └─ 探测失败 → Open

半开状态必须限制探测流量,否则大量实例可能同时探测,形成恢复瞬间的流量冲击。可以加入随机抖动、限制探测并发,并采用渐进式恢复。

6.3 常见熔断策略

Sentinel 支持三类主要策略:

  1. 慢调用比例:请求 RT 超过“最大允许 RT”即视为慢调用;统计窗口内请求达到最小样本量,且慢调用比例超过阈值时熔断;

  2. 异常比例:统计窗口内请求达到最小样本量,且异常比例超过阈值时熔断;

  3. 异常数:统计窗口内异常数量超过阈值时熔断。

建议优先使用“慢调用比例”或“异常比例”,并配置最小请求数。只使用异常数,在流量规模变化较大时容易过于敏感或过于迟钝。

6.4 熔断参数设计

关键参数包括:

阈值应通过历史监控、容量压测和故障演练校准,而不是只凭经验设定。

6.5 降级策略

熔断只决定“是否继续调用”,降级决定“不调用后返回什么”。常见降级方式包括:

降级结果必须符合业务语义。支付结果、库存扣减、账号资产等强一致场景不能用模糊默认值伪装成功,应明确失败、查询最终状态或进入补偿流程。

七、流量优先级与资源隔离

当系统进入过载状态时,平均地拒绝所有请求通常不是最佳选择。应根据业务重要程度对请求分类,例如:

低优先级流量应更早被限流或降级,高优先级流量使用独立配额。优先级还应沿调用链传播,否则下游无法判断应该优先保留哪些请求。

同时,可通过独立线程池、信号量、连接池、队列和租户配额进行资源隔离,避免一个慢依赖、异常接口或大客户耗尽整个应用的共享资源。

八、完整的韧性保护链路

一次请求可以依次经过以下保护:

调用方重试预算/客户端节流

网关配额、来源限流、流量整形

应用入口系统自适应保护

接口级限流与资源隔离

下游调用超时、熔断与半开探测

缓存、默认值、异步化等业务降级

各层职责应明确:网关处理全局和租户流量,应用入口保护单实例,接口规则保护局部资源,客户端熔断保护调用方,业务降级保证用户获得可解释的结果。

九、实施与观测建议

9.1 实施顺序

  1. 明确核心链路、非核心链路和可接受的降级结果;

  2. 为所有远程调用设置连接超时、请求超时和总体截止时间;

  3. 建立接口级限流和资源隔离,设置静态安全上限;

  4. 为不稳定依赖配置慢调用或异常比例熔断;

  5. 增加客户端自适应节流和全局重试预算;

  6. 基于 CPU、RT、并发等指标启用系统自适应保护;

  7. 通过压测、混沌演练和故障复盘持续校准参数。

9.2 必须监控的指标

规则触发必须可观测,否则“保护生效”很容易被误判为“服务故障”。日志和告警至少应说明:哪条规则被触发、触发时的指标值、被影响的资源、执行了什么降级逻辑。

十、总结

  1. 固定 QPS 只是基础防线。 服务容量会随请求成本、系统资源和下游状态变化,不能只依赖固定阈值。

  2. 入口保护与下游保护解决不同问题。 入口自适应限流防止本服务过载;客户端节流和熔断防止不稳定依赖拖垮调用方。

  3. 快速失败优于无限排队。 过载时继续堆积请求只会消耗更多线程、连接和内存,并放大长尾延迟。

  4. 重试必须有预算。 限制次数、指数退避、随机抖动,并且只在紧邻失败依赖的上一层重试。

  5. 熔断必须能够自动恢复。 通过 Open、Half-Open、Closed 状态转换,以少量探测请求验证下游是否恢复。

  6. 降级必须符合业务语义。 非核心信息可以缓存或简化,资金、库存、账号资产等关键操作必须保持明确且可追踪的结果。

  7. 核心流量应获得优先保障。 结合请求优先级、独立配额和资源隔离,避免所有流量一起失败。

  8. 所有阈值都需要验证。 以真实监控、容量压测和故障演练为依据,并保留静态安全上限作为兜底。

最终目标不是消灭所有错误,而是在容量不足或依赖故障时,让系统以可控方式拒绝、降级和恢复,避免局部问题演变为整体不可用。


Share this post:

Previous Post
一次 Sandbox 下 Task Record 维护失败 PR 的排查与 Review 实录
Next Post
AgentScope Java Review 系列