2993 字
15 分钟
限流、熔断、降级:微服务高可用三把锁

1. 限流(Rate Limiting):先画红线,再谈弹性#

1.1 为什么要限流#

当上游流量突增 > 下游容量时,系统会出现级联恶化:线程池耗尽、CPU thrashing、TCP backlog 堆积,最终 OOM。限流的本质是提前丢弃或延迟过量请求,防止系统进入不可恢复状态。

1.2 指标:QPS、TPS 还是并发?#

指标定义适用场景与 TPS 差异
QPSQueries per Second读多写少的查询系统1 query ≠ 1 transaction
TPSTransactions per Second金融、电商下单一次事务可能含多次 query
HPSHTTP Requests per SecondAPI Gateway 计费包括静态资源
并发In-flight requests线程池/连接池瓶颈与 RT 成反比

经验:网关层用 HPS / QPS,服务内部用 并发线程数;TPS 仅在压测或账务场景使用。

1.3 算法对比与选型#

算法突发能力内存占用分布式实现适用场景
固定窗口容易日志收集、离线批处理
滑动窗口有(窗口内突发)中等通用,Sentinel 默认
漏桶困难(需要全局队列)流量整形、计费
令牌桶有(桶容量突发)容易(Redis + Lua)常规业务,Guava、Resilience4j

滑动窗口与令牌桶并非互斥:

  • 滑动窗口解决“时间边界”问题;

  • 令牌桶解决“突发/空闲补偿”问题。
    > 生产环境常见组合:网关层滑动窗口 + 服务内部令牌桶。

1.3.1 固定窗口计数器#

在固定时间窗口内计数,超过阈值则拒绝请求。这是最简单的方法,比如每秒接受100个请求,如果一秒内超过100个请求就抛弃。

但是这种方法存在两个问题:

  • 单位时间很难把控。如下图所示,从下边看每秒请求数没有超过100,但是从上边看每秒请求数超过了100。

固定窗口计数器

  • 有一段时间流量超了,但是也不一定需要限流。如下如所示,虽然前三秒看起来流量超了,但是如果接口的读超时时间是5秒的话就不需要限流。

固定窗口计数器

1.3.2 滑动时间窗口#

将时间划分为多个小窗口,滑动统计请求数,更精确地控制流量。滑动窗口是目前最主流的限流方法。

滑动窗口

开始的时候,我们把t1~t5看做一个时间窗口,每个窗口1s,如果我们定的限流目标是每秒50个请求,那t1~t5这个窗口的请求总和不能超过250个。

这个窗口是滑动的,下一秒的窗口成了t2~t6,这时把t1时间片的统计抛弃,加入t6时间片进行统计。这段时间内的请求数量也不能超过250个。

滑动时间窗口的优点是解决了固定窗口计数器算法的缺陷,但是也有2个问题:

  • 流量超过就必须抛弃或者走降级逻辑。
  • 对流量控制不够精细,不能限制集中在短时间内的流量,也不能削峰填谷。

1.3.3 漏桶算法(Leaky Bucket)#

以固定速率处理请求,超出部分排队或丢弃,适用于平滑处理流量。

漏桶算法

在客户端的请求发送到服务器之前,先用漏桶缓存起来,这个漏桶可以是一个长度固定的队列,这个队列中的请求均匀的发送到服务端。

如果客户端的请求速率太快,漏桶的队列满了,就会被拒绝掉,或者走降级处理逻辑。这样服务端就不会受到突发流量的冲击。

漏桶算法的优点是实现简单,可以使用消息队列来削峰填谷。

但是也有3个问题需要考虑:

  • 漏桶的大小,如果太大,可能给服务端带来较大处理压力,太小可能会有大量请求被丢弃。
  • 漏桶给服务端的请求发送速率。
  • 使用缓存请求的方式,会使请求响应时间变长。

漏桶大小和发送速率这2个值在项目上线初期都会根据测试结果选择一个值,但是随着架构的改进和集群的伸缩,这2个值也会随之发生改变。

1.3.4 令牌桶算法(Token Bucket)#

以固定速率生成令牌,请求需获取令牌后才能被处理,适用于允许突发流量的场景。 令牌桶算法就跟病人去医院看病一样,找医生之前需要先挂号,而医院每天放的号是有限的。当天的号用完了,第二天又会放一批号。

算法的基本思想就是周期性的执行下面的流程:

令牌桶算法流程

客户端在发送请求时,都需要先从令牌桶中获取令牌,如果取到了,就可以把请求发送给服务端,取不到令牌,就只能被拒绝或者走服务降级的逻辑。如下图:

令牌桶算法

令牌桶算法解决了漏桶算法的问题,而且实现并不复杂,使用信号量就可以实现。在实际限流场景中使用最多,比如google的guava中就实现了令牌桶算法限流。

1.3.5 分布式限流#

如果在分布式系统场景下,上面介绍的4种限流算法是否还适用呢?

以令牌桶算法为例,假如在电商系统中客户下了一笔订单,如下图:

分布式场景下令牌桶算法

如果我们把令牌桶单独保存在一个地方(比如redis中)供整个分布式系统用,那客户端在调用组合服务,组合服务调用订单、库存和账户服务都需要跟令牌桶交互,交互次数明显增加了很多。

有一种改进就是客户端调用组合服务之前首先获取四个令牌,调用组合服务时减去一个令牌并且传递给组合服务三个令牌,组合服务调用下面三个服务时依次消耗一个令牌。

1.3.6 hystrix限流#

hystrix可以使用信号量和线程池来进行限流。但是Netflix在2018年宣布部分组件进入维护状态,只修改bug不在更新,后续spring cloud宣布,spring cloud Netflix项目进入维护状态,并在2020年移除相关的Netflix OSS组件,因此不在推荐使用。

1.3.7 Sentinel限流#

Sentinel 的限流机制主要基于滑动窗口算法,通过监控资源的实时请求速率(如 QPS)或并发线程数,当达到设定的阈值时,对超出部分的请求进行限制,以防止系统过载。

其工作流程如下:

  1. 请求拦截:在业务方法执行前,通过 SphU.entry(resource) 方法对资源进行拦截。
  2. 规则校验:根据预设的限流规则,判断当前请求是否超过阈值。
  3. 处理结果: 若未超过阈值,允许请求通过。 若超过阈值,抛出 BlockException,可通过定义 blockHandler 方法进行处理。
  4. 资源释放:请求处理完成后,调用 entry.exit() 释放资源。

这种设计体现了快速失败原则(Fail-Fast Principle),强调在面对错误或异常情况时,系统应该尽早地检测并快速失败,避免故障进一步扩大,从而提高系统的稳定性和可靠性。

限流模式

Sentinel 提供了多种限流模式,以满足不同的业务需求:

  • QPS 模式:限制每秒钟的请求数量。
  • 线程数模式:限制同时处理的线程数。
  • 关联限流:当关联资源达到阈值时,对当前资源进行限流。
  • 链路限流:根据调用链路进行限流,避免某条链路上的资源过载。
  • 热点参数限流:对特定参数值进行限流,防止某些热点参数导致系统过载。
  • 集群限流:在分布式环境下,对多个节点进行统一的限流控制。

限流算法

Sentinel 的限流实现主要基于滑动窗口算法,同时也支持漏桶和令牌桶等算法,以适应不同的流量控制需求:

  • 滑动窗口算法:将时间划分为多个小窗口,实时统计请求数量,适用于平滑处理流量波动。
  • 漏桶算法:以固定速率处理请求,超出部分排队或丢弃,适用于平滑处理流量。
  • 令牌桶算法:以固定速率生成令牌,请求需获取令牌后才能被处理,适用于允许突发流量的场景。

通过这些算法,Sentinel 能够灵活地应对各种流量控制场景,保障系统的稳定运行。

1.4 分布式限流的关键细节#

  1. 令牌桶放哪里?

    • Redis + Lua(原子自增 + TTL)是最轻量的方案,单分片 10W RPS 无压力。
    • 多分片场景可用Redisson + RRateLimiter 或 Sentinel Cluster Flow Rule。
  2. 网络放大问题 令牌桶放在远程节点,每次请求都跨网络。

    • 采用 批量预取(credit-based):客户端一次拉 100 个令牌,本地消费完再拉。
    • 或者使用 代理网关(Envoy、Nginx)统一限流,后端不再关心。

1.5 落地清单#

  • Java:Sentinel(热点参数、链路限流)、Bucket4j(JSR-107 兼容)。
  • Go:uber-go/ratelimit、GCRA 实现。
  • 网关:Nginx limit_req_zone、Envoy local_rate_limit / global_rate_limit

2. 熔断(Circuit Breaker):快速失败,避免雪崩#

2.1 状态机与参数#

熔断器遵循 Closed → Open → Half-Open 的三态机:

状态行为进入条件退出条件
Closed正常转发初始错误率 ≥ 阈值
Open直接拒绝错误率触发经过 sleepWindow
Half-Open放少量请求探测sleepWindow 到期探测成功 → Closed;失败 → Open

熔断器状态切换

 参数建议

  • 错误率阈值:50 %(电商)、80 %(内部工具)。
  • 统计窗口:10 s(RT 低)~60 s(RT 高)。
  • sleepWindow:5 s 起,按 99th RT 倍数递增。

2.2 实现对比#

组件语言最新版本维护状态特性
Resilience4jJava 8+2.2.0活跃函数式、Micrometer 指标、支持时间窗口和基于异常比例
SentinelJava/Go1.8.7活跃流量治理一体化,支持热点、链路、集群熔断
HystrixJava1.5.18停止维护仅修严重 bug,官方建议迁移

2.3 生产注意点#

  • 异常分类:只熔断 IOExceptionTimeoutException不熔断业务异常(如 4xx)。
  • 自适应熔断:Resilience4j 1.7+ 支持基于响应时间的自适应阈值。
  • 探测流量:Half-Open 阶段可配置“每 5 s 放 3 个请求”,避免惊群。
  • 事件通知:熔断状态变更推送到 Prometheus + Alertmanager,30 s 内完成告警。

3. 服务降级(Degradation):优雅地“减功能不减体验”#

3.1 与熔断、限流的区别#

  • 限流:控制 入口流量
  • 熔断:控制 下游故障蔓延
  • 降级:控制 自身资源占用,主动关闭非核心功能。

3.2 降级策略#

策略触发条件示例
静态默认值配置中心开关推荐服务返回空列表
缓存兜底接口超时商品详情返回 5 min 前的缓存
异步写写库压力大订单先写 MQ,后台异步落库
功能关闭手动 / 自动直播弹幕关闭,仅保留直播流

3.3 Sentinel 动态降级示例#

@SentinelResource(value = "hotItems",
blockHandler = "degradeHotItems",
fallback = "cachedHotItems")
public List<Item> hotItems(long categoryId) {
return itemDao.topN(categoryId);
}
public List<Item> cachedHotItems(long categoryId) {
return cache.get("hot:" + categoryId, Duration.ofMinutes(5));
}
  • blockHandler:被限流/熔断时调用。
  • fallback:任何异常(包括业务异常)触发,可做兜底数据。

4. 总结:三把锁如何协同#

场景第一优先级第二优先级第三优先级
突发流量限流熔断降级
下游故障熔断降级限流
大促高峰降级限流熔断

口诀:“先降级保核心,再限流保稳定,最后熔断防雪崩”

限流、熔断、降级:微服务高可用三把锁
https://zhanyin.site/posts/限流熔断降级微服务高可用三把锁/
作者
Leo
发布于
2025-08-06
许可协议
CC BY-NC-SA 4.0