服务治理核心:Sentinel 限流熔断降级——生产级配置实战

引言

大促零点,商品详情页开始变慢。排查发现不是详情服务自己的问题——它依赖的评价服务有个慢 SQL,每个请求卡 3 秒。详情服务用默认的 Tomcat 200 线程池,请求一个接一个堵在评价服务调用上,200 个线程 30 秒内全部占满,详情服务整个挂掉;网关又把流量继续往这个已经挂掉的实例转,雪崩从评价服务蔓延到详情页,再到下单链路。

雪崩的本质是"故障传导没有刹车":下游慢 → 上游线程被占满 → 上游也慢 → 更上游继续堆。Sentinel 就是这个刹车——在调用入口实时统计 QPS、响应时间、异常比例,超过阈值立刻拦截或熔断,不让故障穿透到下一跳。这篇文章从三种核心能力的原理讲起,完成 Spring Cloud Alibaba 集成、控制台规则、@SentinelResource 兜底、Nacos 持久化的完整落地,最后给生产级规则配置清单。


一、三种核心能力:限流、熔断、降级的关系

很多人把这三个词混着用,它们其实是三个独立环节:

┌─────────────────────────────────────┐
请求进来 ─────→  │ ① 限流:流量超过阈值,直接拒绝(防过载) │
                └──────────────┬──────────────────────┘
                               ↓ 通过
                ┌─────────────────────────────────────┐
                │ ② 熔断:下游持续慢/报错,断路器打开    │
                │   一段时间内不再真正调用,快速失败      │
                └──────────────┬──────────────────────┘
                               ↓ 被限流/被熔断/调用异常
                ┌─────────────────────────────────────┐
                │ ③ 降级:返回兜底数据/默认值/友好提示    │
                └─────────────────────────────────────┘
能力触发依据动作解决的问题
限流 Flow入口流量本身(QPS/线程数)超出阈值直接拒绝防自己被流量打垮
熔断 Degrade下游的表现(慢/异常)断路器打开,快速失败防被慢下游拖垮,切断传导链
系统自适应整机负载(CPU/Load/RT)整机入口限流防机器整体过载
降级 Fallback被拦/异常后的处理返回兜底结果用户体验 + 保护业务语义

一句话记忆:限流管"进来的量",熔断管"下游的烂",降级管"拦住之后给什么"。


二、限流:三种统计维度

2.1 QPS 限流(最常用)

每秒请求数超过阈值直接拒绝。Sentinel 用滑动窗口统计(默认 1 秒窗口切成 2 个 500ms 小格子),比固定窗口准确,避免"窗口交界处 2 倍流量"问题。

2.2 并发线程数限流(防慢依赖的利器)

统计的是"当前正在执行该资源的线程数",不统计已完成的请求——专门对付慢调用

评价接口平时 RT 50ms,200 线程能扛 4000 QPS
评价接口故障 RT 变 3s,线程数迅速涨到阈值 10
→ 第 11 个请求直接拒绝,不再往下游发
→ Tomcat 剩下的 190 个线程保持空闲,详情服务活着

QPS 限流防不住慢依赖:QPS 只有 50 看起来不高,但每个请求占线程 3 秒,照样把线程池吃光。线程数限流是慢调用雪崩的对症药

2.3 热点参数限流

同一个接口,不同参数值分别限流——如商品详情接口,普通商品 1000 QPS,爆款商品单独 100 QPS:

@GetMapping("/detail/{skuId}")
@SentinelResource(value = "skuDetail", blockHandler = "detailBlocked")
public R<DetailVO> detail(@PathVariable Long skuId) {
    return R.ok(detailService.get(skuId));
}

控制台配置热点规则:参数索引 0(skuId),单机阈值 1000;例外项中 skuId=88888(爆款)阈值 100。还支持集群限流(token server 全局发放令牌),解决单机阈值在实例扩缩容时总量漂移的问题。

2.4 流控效果(被限后怎么处理)

效果行为适用
快速失败(默认)立即抛 FlowException通用
Warm Up(冷启动)阈值从低逐步升到目标值,历时预热时长秒杀开始瞬间保护刚启动/有缓存预热的服务
排队等待请求匀速通过,超出超时拒绝(漏桶)突发流量削峰填谷,如 MQ 发送

三、熔断:断路器的三种策略与状态机

3.1 断路器状态机

慢调用比例/异常比例/异常数 达到阈值
  CLOSED ─────────────────────────────→ OPEN
    ↑                                   │
    │ 探测成功(慢调用比例恢复)           │ 熔断时长到期,放一个请求试探
    │                                   ↓
    └────────────────────────────── HALF_OPEN
  • CLOSED(关闭):正常放行,持续统计
  • OPEN(打开):所有请求快速失败,不再调用下游,给下游喘息时间
  • HALF_OPEN(半开):熔断时长到期放行一个探测请求,成功则关闭,失败则重新打开

3.2 三种熔断策略

策略触发条件关键参数典型场景
慢调用比例RT 超过"最大 RT"的请求占比超阈值maxRt、比例阈值、最小请求数、统计时长、熔断时长下游变慢(最常用)
异常比例异常请求占比超阈值比例阈值(如 0.5)、最小请求数下游报错多
异常数异常请求数超阈值(绝对值)异常数阈值请求量小但不能容忍错误

示例配置:评价服务接口,统计窗口 10s 内至少 5 个请求,RT > 500ms 的比例超过 50% → 熔断 10s:

最大 RT: 500ms
比例阈值: 0.5
熔断时长: 10s
最小请求数: 5        ← 防止 1 个慢请求(100% 比例)误触发
统计时长: 10000ms

最小请求数是防误触的关键:窗口内只有 2 个请求且都慢,比例 100% 但样本太小不代表下游真故障——设最小请求数 5 才统计熔断。

3.3 熔断与限流的配合

评价服务变慢:
  线程数限流(阈值 10)→ 挡住新增请求,保护本服务线程池
  慢调用比例熔断(50%,500ms)→ 断路器打开 10s,期间零调用
  → 评价服务获得 10s 无压恢复期(可能是 Full GC/慢查询过去了)
  → 半开探测 → 恢复 → 断路器关闭,流量自动回来

两层保护:限流是"挡"(保护自己),熔断是"断"(保护下游+快速失败不堆积)。


四、Spring Cloud Alibaba 集成

4.1 依赖

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2023.0.1.2</version>   <!-- 与 Spring Cloud 2023.x/Boot 3.2 对齐 -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- @SentinelResource 注解切面依赖 AOP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- 规则持久化到 Nacos(第六章) -->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
</dependencies>

4.2 应用配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: sentinel-dashboard:8858    # 控制台地址
        port: 8719                            # 客户端与控制台通信端口(默认 8719,占用自动递增)
        client-ip: ${POD_IP:}                 # K8s 多网卡时显式指定注册 IP
      eager: true                             # 饥饿初始化:启动即连接控制台
      web-context-unify: true                 # 收敛 URL 上下文(默认 true)
      http-method-specify: false              # true 时 GET/POST 同路径算不同资源

两个经典排障点(来自真实踩坑):① 簇点链路看不到资源——先看启动日志有没有 "Sentinel Transport Server running at 8719" 的心跳日志,没有就是依赖/版本问题(必须由 SCA BOM 统一管理版本,不要手写 sentinel 版本号);② 用了 @SentinelResource 但不生效——必须有 spring-boot-starter-aop,注解靠切面织入。不要手动注册 SentinelWebInterceptor,starter 已自动装配,重复注册反而冲突。


五、@SentinelResource:定义资源与兜底

5.1 blockHandler 与 fallback 的区别(面试高频)

方法处理什么典型用途
blockHandler只处理 Sentinel 的拦截:限流/熔断/系统规则触发的 BlockException返回"系统繁忙"、排队提示
fallback处理业务方法自己抛的所有异常(Java 异常)业务异常时返回兜底数据
defaultFallback兜底中的兜底,同签名的全局降级方法多个资源共用

两者可以同时配:被 Sentinel 拦走 blockHandler,方法内部抛异常走 fallback——职责分离。

5.2 完整代码

@Service
public class ProductDetailService {

    /**
     * value:资源名(控制台按这个名字配规则)
     * blockHandler:限流/熔断时的兜底方法(同类或 blockHandlerClass 指定)
     * fallback:业务异常兜底
     */
    @SentinelResource(
            value = "queryProductDetail",
            blockHandler = "detailBlockHandler",
            blockHandlerClass = DetailBlockHandlers.class,
            fallback = "detailFallback"
    )
    public DetailVO queryDetail(Long skuId) {
        Product p = productApi.get(skuId);          // 可能慢/抛异常的调用
        List<Review> reviews = reviewApi.list(skuId);
        return DetailVO.assemble(p, reviews);
    }

    /** fallback 签名:与原方法参数一致,末尾可加 Throwable(可选) */
    public DetailVO detailFallback(Long skuId, Throwable t) {
        log.warn("[DetailFallback] skuId={} 业务异常,走兜底: {}", skuId, t.getMessage());
        // 降级策略 1:返回缓存中的旧数据(允许短暂脏读)
        DetailVO cached = redisCache.get("detail:" + skuId, DetailVO.class);
        if (cached != null) {
            cached.setDegraded(true);               // 标记是降级数据,前端可提示
            return cached;
        }
        // 降级策略 2:返回静态兜底
        return DetailVO.basic(skuId);
    }
}

/** blockHandler 放到独立类:方法必须 static,参数 = 原参数 + BlockException */
public final class DetailBlockHandlers {
    public static DetailVO detailBlockHandler(Long skuId, BlockException ex) {
        log.warn("[DetailBlocked] skuId={} rule={}", skuId, ex.getClass().getSimpleName());
        throw new BizException("SYSTEM_BUSY", "当前访问人数过多,请稍后重试");
        // 或返回排队页/兜底数据,按业务决定
    }
}

5.3 方法签名四条铁律

  1. blockHandler / fallback 返回类型必须与原方法完全一致
  2. 参数列表与原方法一致,blockHandler 末尾加 BlockException,fallback 末尾可加 Throwable
  3. 默认在同类中找方法;用 blockHandlerClass/fallbackClass 时,目标类中的方法必须是 static
  4. 资源名(value)全局唯一,建议用"业务域:方法"格式,如 product:queryDetail

5.4 Feign 整合:调用其他服务时的熔断

feign:
  sentinel:
    enabled: true          # Feign 调用也纳入 Sentinel 资源统计
@FeignClient(name = "review-service", fallback = ReviewClientFallback.class)
public interface ReviewClient {
    @GetMapping("/reviews/{skuId}")
    List<Review> list(@PathVariable Long skuId);
}

@Component
public class ReviewClientFallback implements ReviewClient {
    @Override
    public List<Review> list(Long skuId) {
        log.warn("[ReviewFeignFallback] skuId={} 返回空评价列表", skuId);
        return Collections.emptyList();    // 评价是弱依赖,降级为空列表不阻塞详情页
    }
}

降级的核心是区分强依赖与弱依赖:评价、推荐、通知这类弱依赖降级返回空/默认值,主流程继续;库存、价格这类强依赖不能假数据,降级要快速失败+明确提示。


六、规则持久化到 Nacos

6.1 不持久化的痛点

控制台直接配规则,规则存在客户端内存里——应用一重启全丢。生产必须把规则放到 Nacos,应用启动拉取、变更监听自动推送。

6.2 配置

spring:
  cloud:
    sentinel:
      datasource:
        # 流控规则
        flow:
          nacos:
            server-addr: ${nacos.server-addr}
            namespace: ${nacos.namespace}
            group-id: SENTINEL_GROUP
            data-id: ${spring.application.name}-flow-rules.json
            rule-type: flow
        # 熔断降级规则
        degrade:
          nacos:
            server-addr: ${nacos.server-addr}
            namespace: ${nacos.namespace}
            group-id: SENTINEL_GROUP
            data-id: ${spring.application.name}-degrade-rules.json
            rule-type: degrade
        # 热点参数规则
        param-flow:
          nacos:
            server-addr: ${nacos.server-addr}
            namespace: ${nacos.namespace}
            group-id: SENTINEL_GROUP
            data-id: ${spring.application.name}-param-rules.json
            rule-type: param-flow

6.3 Nacos 中的规则内容

order-service-degrade-rules.json

[
  {
    "resource": "queryProductDetail",
    "grade": 0,
    "count": 500,
    "slowRatioThreshold": 0.5,
    "minRequestAmount": 5,
    "statIntervalMs": 10000,
    "timeWindow": 10
  }
]

order-service-flow-rules.json

[
  {
    "resource": "createOrder",
    "limitApp": "default",
    "grade": 1,
    "count": 500,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
  }
]
流控字段含义
grade1=线程数,0=QPS(注意与熔断 grade 含义不同)
count阈值
strategy0=直接,1=关联,2=链路
controlBehavior0=快速失败,1=Warm Up,2=排队等待

变更流程:Nacos 控制台改 JSON → 发布 → 客户端监听推送实时生效(不用重启)。规则走 Nacos 还能接入配置审计(谁改的、改了什么,参考之前 Nacos 配置中心那篇)。


七、生产级规则配置清单

7.1 接口分级与基线规则

接口级别示例QPS 限流线程数熔断策略
P0 核心交易下单、支付压测容量的 80%200(容器线程 80%)异常比例 50%,熔断 10s
P1 核心查询商详、购物车容量 80% + Warm Up100慢调用比例:RT>P99 基线×2 占 50%
P2 弱依赖评价、推荐容量 60%20慢调用 RT>1s 占 50%,降级返回默认值
P3 辅助埋点、上报排队等待10异常数 > 100,直接丢弃

7.2 阈值怎么定:压测给容量,监控给基线

QPS 阈值 = 压测单实例极限 QPS × 0.8(留 20% 安全余量)
慢调用 RT 阈值 = 该接口近 7 天 P99 × 2(基线翻倍才算异常,不拿平均值当阈值)
线程数阈值 = 容器工作线程数 × 0.8(Tomcat 默认 200 → 160),弱依赖单独小池 10~20
最小请求数 = 5~10(防低流量误触)
统计时长 = 10s;熔断时长 = 10s 起步,下游是 DB 故障可给 30~60s

不要拍脑袋写 100 QPS——没压测数据的阈值要么太低误杀正常流量,要么太高形同虚设。

7.3 系统规则(整机保护)

LOAD 阈值:CPU 核数 × 1.5(仅 Linux 生效)
CPU 使用率:80%
平均 RT:所有入口 RT 均值阈值
入口 QPS:整机 QPS 上限

系统规则是最后一道总闸——单接口规则漏配时,整机维度兜底。

7.4 上线 Checklist

  •  依赖由 SCA BOM 统一版本,已引入 AOP starter
  •  eager: true,启动日志确认心跳注册成功,簇点链路可见资源
  •  规则存 Nacos 而非控制台内存,重启不丢
  •  每个 @SentinelResource 都有 blockHandler,异常有 fallback
  •  Feign 开启 feign.sentinel.enabled,弱依赖 fallback 返回空/默认值
  •  阈值来自压测容量和监控基线,非拍脑袋
  •  降级数据带标记(degraded=true),前端能区分
  •  拒绝量、熔断开启次数、慢调用比例接 Prometheus 告警
  •  上线后做故障注入演练(下游注入延迟/异常),验证刹车真的生效

7.5 监控指标

Sentinel 暴露的关键指标(可通过 Micrometer 或 Sentinel 自带 metric API 抓取):

指标告警建议
block 数/拒绝 QPS> 0 持续 1 分钟预警(说明在丢流量)
资源通过 QPS与容量对比
熔断状态(open 次数)OPEN 即 P1 告警
资源 RT 分布P99 超基线×2
机器 CPU/Load系统规则触发预警

八、常见问题

8.1 限流了但前端收到 500 而不是友好提示?

默认情况下 BlockException 抛到全局,如果没有 blockHandler 也没有全局异常处理器,就走 500。解法:① 资源配 blockHandler 返回自定义结果(推荐);② 实现全局 BlockExceptionHandler(Web 场景)统一把 BlockException 转成 429 + JSON 提示。注意区分:blockHandler 没生效大概率是签名不对(返回类型不一致、blockHandlerClass 里方法不是 static),Sentinel 找不到匹配方法会直接抛异常。

8.2 Sentinel 和 Hystrix/Resilience4j 怎么选?

Hystrix 已停止维护,不用再考虑。Resilience4j 是轻量的代码内熔断库(无控制台、无实时推送),适合不想引入中间件、规则很少变动的纯 Java 应用。Sentinel 是带控制台、实时监控、规则动态推送、热点/系统规则的完整治理平台,适合微服务集群统一治理。已经在用 Spring Cloud Alibaba 技术栈(Nacos)的团队,Sentinel 是零额外成本的自然选择

8.3 熔断打开期间用户请求看到什么?

取决于降级设计,不是简单的报错:弱依赖(评价)熔断时用户看到"评价暂时无法加载"+缓存旧数据,主流程无感;强依赖(库存)熔断时快速失败"系统繁忙请稍后重试",避免用户等 3 秒超时。关键是快速失败本身就是体验保护——10ms 返回"繁忙"比让用户等 10 秒再看到 504 好得多。

8.4 规则推送后所有实例生效吗?有延迟吗?

走 Nacos 数据源时,所有订阅该 data-id 的实例通过长轮询监听变更,通常 1~3 秒内全部生效,不需要重启。注意 data-id 按应用名区分(${appName}-flow-rules.json),公共规则可以让所有应用订阅同一个共享 data-id。控制台直推(内存模式)只推给当时在线的实例,新实例和重启都拿不到——这就是必须持久化的原因。

8.5 热点参数限流遇到恶意构造的随机参数怎么办?

热点限流按参数值分桶,攻击者每次换随机参数值会绕过单值阈值。应对:① 同时配全局限流兜底(接口总 QPS);② 参数值不可信时先在业务层归一化(如 userId 必须从登录态取,URL 里的参数做白名单);③ 高基数参数(如 orderId)慎用热点规则,分桶本身有内存开销,百万级不同参数值会占用大量统计内存。

8.6 网关层(Spring Cloud Gateway)限流和服务内限流怎么分工?

两层不冲突,是漏斗关系:网关层做粗粒度全局限流(按 IP/用户/接口的总 QPS,挡恶意流量和突发洪峰,用 sentinel-spring-cloud-gateway 适配器,限流结果不进业务服务);服务内做细粒度保护(按资源的线程数/慢调用熔断,保护下游依赖和线程池)。流量先过网关粗筛,再进服务精筛——网关挡掉的流量连业务服务都到不了,成本最低。


九、总结

速查卡

三种能力:限流管进来的量(QPS/线程数/热点参数)
         熔断管下游的烂(慢调用比例/异常比例/异常数)
         降级管拦住后给什么(兜底数据/空集合/友好提示)
┌──────────────┬────────────────────────────────────────┐
│ 配置项        │ 生产建议                                │
├──────────────┼────────────────────────────────────────┤
│ QPS 阈值      │ 压测容量 × 0.8                          │
│ 线程数阈值    │ 容器线程 × 0.8;弱依赖独立小池 10~20      │
│ 慢调用 RT     │ 近 7 天 P99 × 2                         │
│ 最小请求数    │ 5~10(防误触)                          │
│ 统计/熔断时长 │ 10s/10s 起步,重故障 30~60s             │
│ 规则存储      │ Nacos 持久化,禁控制台内存直推           │
│ 兜底         │ 弱依赖默认值/缓存;强依赖快速失败+提示     │
│ 分层         │ 网关粗筛 + 服务内精筛                    │
└──────────────┴────────────────────────────────────────┘

一句话

雪崩的根因是故障传导没有刹车,Sentinel 的三个能力就是刹车系统的三个部件:限流在入口按 QPS、并发线程数、热点参数控制"进来的量",其中线程数限流是慢依赖雪崩的对症药——QPS 不高但每个请求占 3 秒线程一样能拖垮服务;熔断盯着下游的慢调用比例、异常比例、异常数,统计窗口内最小请求数达标后触发断路器 OPEN→HALF_OPEN→CLOSED 状态机,给下游喘息窗口也让自己快速失败不堆积;降级决定被拦之后用户看到什么——弱依赖返回空集合或缓存旧数据主流程无感,强依赖快速失败+明确提示绝不放假数据。落地只有四个关键点:依赖必须由 SCA BOM 统一版本且带上 AOP(否则 @SentinelResource 切面不生效)、blockHandler 与 fallback 职责分离且签名严格匹配、规则持久化到 Nacos 重启不丢变更秒级推送、阈值必须来自压测容量和监控基线而不是拍脑袋。最后一句话:限流熔断规则配了不算数,故障注入演练验证刹车真的能踩住,才算服务治理闭环。

给团队的建议

建议
集成SCA BOM 管版本 + AOP starter + eager 注册,先确认簇点链路可见
设计接口分 P0~P3 四级,强弱依赖降级策略分开
阈值压测定容量、监控 P99 定 RT 基线,季度复评
持久化规则全部进 Nacos,控制台只做观察不做长期配置入口
兜底blockHandler 全覆盖,降级数据打标记,写操作不降级放假成功
分层Gateway 粗粒度限流 + 服务内细粒度熔断
演练上线后注入下游延迟/异常,验证熔断与降级链路
告警block QPS、熔断 OPEN、P99 超基线三类指标必接告警

互动话题:你们的限流阈值是压测出来的还是拍脑袋定的?有没有遇到过敏捷依赖把线程池占满的真实雪崩?评论区聊聊。


参考资料


标题:服务治理核心:Sentinel 限流熔断降级——生产级配置实战
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/09/19/1789226016376.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消