熔断、限流与降级
服务雪崩效应
Section titled “服务雪崩效应”微服务架构中,服务链路的稳定性面临级联失败(Cascade Failure)的威胁:
正常调用链: A → B → C → D(外部数据库)
D 响应缓慢(如数据库慢查询): C 的线程池被 D 的慢响应占满 B 的线程池被 C 的超时积压占满 A 的所有请求全部超时 → 整个链路全部不可用(雪崩)
根本原因:同步调用 + 无超时限制 + 无隔离机制解决方案:超时控制 + 熔断 + 限流 + 降级 + 舱壁隔离限流(Rate Limiting)控制单位时间内通过的请求数量,防止系统被超出处理能力的请求压垮。
固定窗口计数器
Section titled “固定窗口计数器”时间分为固定大小的窗口(如每秒),每个窗口内维护计数器:
|--- 1秒 ---|--- 1秒 ---|--- 1秒 ---| | 0~500 | 0~500 | 0~500 | ← 最多500个请求
问题:临界值突刺 0.9s 到 1.0s:499个请求(0~499计数) 1.0s 到 1.1s:499个请求(新窗口 0~499计数) → 0.1秒内通过了 998 个请求!是限制的 2 倍滑动窗口计数器
Section titled “滑动窗口计数器”解决固定窗口的临界值问题: 时间窗口随时间推移,始终统计最近 N 秒内的请求数
实现: • 按细粒度桶(如100ms一个桶)存储计数,总窗口是多个桶的和 • 新请求到来时,丢弃窗口外的旧桶,累加窗口内所有桶的计数
Nginx 的 limit_req 模块使用滑动窗口漏桶算法(Leaky Bucket)
Section titled “漏桶算法(Leaky Bucket)”漏桶比喻: 请求是水滴,以任意速率流入桶 桶以固定速率漏出(处理请求) 桶满则丢弃(限流)
请求 →→→→→→ [ 桶 (容量N) ] → 固定速率 → 系统处理 桶满 → 丢弃
特点: • 输出速率始终固定(平滑流量) • 无法应对突发流量(桶满立即丢弃,即使系统有能力处理)
适用:对下游服务的保护(防止下游被突发流量压垮)
实现:队列 + 定时消费 入队:请求到来,入队(队列满则丢弃) 出队:定时任务固定速率出队处理令牌桶算法(Token Bucket)★
Section titled “令牌桶算法(Token Bucket)★”令牌桶比喻: 令牌以固定速率生成,放入桶中(桶有最大容量) 请求到来时消耗一个令牌,有令牌则放行,无令牌则拒绝/等待
令牌生成 → [ 令牌桶 (容量N) ] 请求 → 消耗令牌 → 系统处理 / 无令牌 → 拒绝
特点: • 允许突发流量(桶内积累的令牌可以一次性消耗) • 长期速率受令牌生成速率限制 • 既平滑了速率,又支持合理的突发
适用:接口限流(如 API QPS 限制)
Google Guava 的 RateLimiter、Sentinel 均使用令牌桶思想漏桶 vs 令牌桶
Section titled “漏桶 vs 令牌桶”| 对比项 | 漏桶 | 令牌桶 |
|---|---|---|
| 输出速率 | 严格固定 | 有令牌积累时允许突发 |
| 突发流量 | 严格限制(桶满丢弃) | 支持(桶内令牌一次消耗) |
| 适用场景 | 保护下游(严格限速) | 接口限流(允许合理突发) |
熔断器模式(Circuit Breaker)
Section titled “熔断器模式(Circuit Breaker)”熔断器防止系统在依赖服务不可用时持续尝试,保护自身资源。
熔断器状态机:
CLOSED(关闭,正常状态) ↓ 失败率/慢调用率超过阈值OPEN(打开,熔断状态) ↓ 等待 waitDurationInOpenState(如 60s)HALF-OPEN(半开,探测状态) ↓ 允许少量请求通过 ├── 请求成功率达到阈值 → 回到 CLOSED └── 请求失败 → 回到 OPEN
CLOSED 状态(正常): 所有请求正常通过 统计失败率(最近 N 次 或 N 秒内) 失败率 > threshold(如50%)→ 切换到 OPEN
OPEN 状态(熔断): 直接拒绝所有请求(fast fail),立即返回降级结果 等待一段时间(给依赖服务恢复的机会) 超时后切换到 HALF-OPEN
HALF-OPEN 状态(探测): 允许有限数量的请求通过(探针请求) 成功率够高 → 切换回 CLOSED,恢复正常 失败 → 切换回 OPEN,继续等待Sentinel vs Hystrix
Section titled “Sentinel vs Hystrix”| 对比项 | Hystrix | Sentinel |
|---|---|---|
| 维护状态 | 维护模式(仅修复 bug,不新增功能) | 活跃开发 |
| 隔离策略 | 线程池隔离 / 信号量隔离 | 信号量隔离 |
| 限流 | 不支持(只有熔断) | 支持(QPS、并发线程数) |
| 熔断策略 | 基于失败率 | 支持失败率、慢调用比例、异常数 |
| 规则动态配置 | 代码配置,不支持动态 | 支持多种数据源(Nacos、ZK 等) |
| 控制台 | Hystrix Dashboard(功能弱) | Sentinel Dashboard(实时监控,功能完整) |
| 扩展性 | 较弱 | Slot Chain 机制,高度可扩展 |
| 适用场景 | 老项目维护 | 新项目推荐 |
Sentinel 核心概念
Section titled “Sentinel 核心概念”资源(Resource): 需要保护的目标(方法调用、URL、服务调用等)
规则(Rule): 流量控制规则、熔断降级规则、系统保护规则
Slot Chain(处理链): NodeSelectorSlot → ClusterBuilderSlot → StatisticSlot → FlowSlot → DegradeSlot → AuthoritySlot → SystemSlot
每个 Slot 负责一种功能: StatisticSlot:统计 QPS、线程数、响应时间 FlowSlot:流量控制(令牌桶/漏桶) DegradeSlot:熔断降级(失败率/慢调用率) SystemSlot:系统保护(CPU、内存、Load 阈值)降级(Fallback)是当服务不可用时提供替代响应,保证用户体验。
常见降级策略:
1. 返回默认值/缓存数据 用户信息不可用 → 返回 {name: "用户", avatar: "默认头像"} 价格服务不可用 → 返回上次缓存的价格
2. 静态化 / CDN 兜底 动态页面服务不可用 → 返回预先生成的静态 HTML
3. 限制功能 推荐系统不可用 → 展示热门商品(不个性化推荐) 支付服务过载 → 降级为人工处理
4. 友好错误提示 非核心功能不可用 → 提示"该功能暂时不可用,请稍后重试" 核心功能不可用 → 引导用户到备用入口
设计原则: • 降级的默认值必须对用户有意义(不能是乱数据) • 记录降级事件(用于监控和告警) • 核心链路不能被降级掉(支付流程不能因非核心服务降级而失败)舱壁隔离(Bulkhead Pattern)
Section titled “舱壁隔离(Bulkhead Pattern)”舱壁隔离来自船舶设计的”防水舱壁”:将不同服务的资源(线程、连接)隔离,防止一个服务的故障耗尽全部资源。
线程池隔离(Hystrix)
Section titled “线程池隔离(Hystrix)”为每个依赖服务分配独立的线程池:
服务A 线程池(10个线程)→ 调用服务A 服务B 线程池(10个线程)→ 调用服务B 服务C 线程池(10个线程)→ 调用服务C
服务A 超时,耗尽了服务A 的10个线程 → 不影响服务B、C 的线程池 → B、C 调用继续正常
代价:线程数量 = 服务数量 × 每个服务的线程池大小,线程开销较大信号量隔离(Sentinel 默认)
Section titled “信号量隔离(Sentinel 默认)”为每个资源设置最大并发数(信号量): 当并发请求数 > 信号量值 → 拒绝多余请求
服务A:最大并发 20 服务B:最大并发 10
特点: • 不创建额外线程,开销小 • 不支持异步调用(调用在当前线程内完成) • 粒度比线程池隔离粗
优点:轻量;缺点:无法真正隔离(调用仍然在业务线程)限流算法的实现
Section titled “限流算法的实现”基于 Redis 的分布式限流
Section titled “基于 Redis 的分布式限流”-- Redis + Lua 实现令牌桶(原子操作)local key = KEYS[1] -- 限流 keylocal capacity = ARGV[1] -- 桶容量local rate = ARGV[2] -- 每秒生成令牌数local now = ARGV[3] -- 当前时间戳(毫秒)local requested = ARGV[4] -- 请求的令牌数
local last_tokens = tonumber(redis.call('hget', key, 'tokens') or capacity)local last_time = tonumber(redis.call('hget', key, 'time') or now)
local elapsed = (now - last_time) / 1000.0 -- 经过的秒数local new_tokens = math.min(capacity, last_tokens + elapsed * rate)
if new_tokens >= requested then redis.call('hset', key, 'tokens', new_tokens - requested, 'time', now) return 1 -- 允许else return 0 -- 拒绝endQ:令牌桶和漏桶算法有什么区别?
漏桶以固定速率漏出,输出严格平滑,无法应对突发(桶满直接丢弃);适合保护下游不被突发压垮。令牌桶以固定速率生成令牌,桶内可以积累令牌,允许突发流量(一次消耗积累的令牌);长期平均速率受令牌生成速率限制;适合接口限流场景。
Q:熔断器的三个状态是什么?如何转换?
CLOSED(正常)→ 失败率超阈值 → OPEN(熔断,直接拒绝)→ 等待 N 秒 → HALF-OPEN(探测,允许少量请求)→ 成功率高则回 CLOSED,失败则回 OPEN。
Q:Sentinel 和 Hystrix 有什么区别?
Hystrix 已进入维护模式,功能相对单一(主要是熔断+线程池隔离),不支持限流,规则不能动态配置。Sentinel 功能更丰富(限流+熔断+系统保护),规则支持动态配置(接入 Nacos 等),有完整的控制台实时监控,且仍在活跃开发,是 Spring Cloud Alibaba 的推荐选择。
Q:什么是服务雪崩?如何防止?
服务雪崩是一个服务的故障通过同步调用链级联扩散,导致整个系统不可用。防止手段:①超时设置(防止线程长时间阻塞);②熔断(检测到依赖不可用时快速失败);③限流(控制请求量,防止超载);④舱壁隔离(不同服务使用独立资源池,故障不扩散);⑤降级(提供替代响应,保证用户体验)。