Day 45 | Sentinel流量治理:从限流到熔断降级再到系统自适应保护

专栏:《Java高级进阶》90天进阶系列 版本环境:Spring Boot 3.2.x + Spring Cloud Alibaba 2023.0.1.0 + Sentinel 1.8.6


一、开篇:大促那一夜,限流救了我们

有一年双 11,公司搞了一场优惠券秒杀。接口上线前压测明明过了,结果活动开始 3 分钟,数据库连接池打满,Redis 主节点被打到 CPU 飙到 95%,整个下单链路全崩。

事后看监控,羊毛党的脚本和正常用户混在一起,QPS 曲线像一把刀一样竖起来。我们说:"下次一定要做限流。" 但限流怎么做才不算"一刀切"?把所有请求都拒掉,正常用户也一起骂娘。

Sentinel 就是用来解决这个问题的。它不是简单的"超过阈值就 429",而是把流量当成一个立体系统来治理:入口限流、链路隔离、熔断降级、热点防护、系统自适应保护,一层一层把风险关在笼子里。

今天这一篇,把生产里用得最多的 4 个能力串起来,给你一套能直接落地的代码。


二、Sentinel 的设计精髓:一条 Slot 责任链

理解 Sentinel 最快的方式,不是先背概念,而是先看它的执行链。

Sentinel 把每一次资源访问都抽象成一条调用链,链上每个节点叫 Slot(槽),各司其职:

  • NodeSelectorSlot:按调用链路构建树,方便你做链路限流。

  • ClusterBuilderSlot:统计集群维度的总流量。

  • StatisticSlot:滑动窗口计数,所有限流/熔断的判断数据都来自这里。

  • FlowSlot:核心流控,支持 QPS、线程数、关联、链路等多种策略。

  • DegradeSlot:熔断降级,慢调用、异常比例、异常数三种触发条件。

  • SystemSlot:系统保护,根据 Load、CPU、RT、线程数做入口总控。

  • ParamFlowSlot:热点参数限流,针对某个参数值(比如用户 ID、商品 ID)做精细控制。

这条链的最大好处是:每一层只做一件事,组合起来就是一套完整的防御体系。


三、代码实战 1:流量控制,先学会"精准拒绝"

流控是 Sentinel 最基础也最常用的能力。生产里不要一上来就全局 QPS 限流,而是按资源粒度配规则。

下面是一个 Spring Boot 3.2 项目的完整最小可用示例。

3.1 Maven 依赖

java 复制代码
<!-- Spring Boot 3.2.x 请使用 2023.0.1.0 版本 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2023.0.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2023.0.1.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
​
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
</dependencies>

3.2 启动时加载流控规则

java 复制代码
@Configuration
public class SentinelFlowConfig {
​
    @PostConstruct
    public void initRules() {
        List<FlowRule> rules = new ArrayList<>();
​
        // 资源名建议用 "类名:方法名" 或业务语义名,和 @SentinelResource 保持一致
        FlowRule seckillRule = new FlowRule("seckill:submit");
        seckillRule.setGrade(RuleConstant.FLOW_GRADE_QPS);   // 按 QPS 限流
        seckillRule.setCount(1000);                          // 阈值 1000 QPS
        seckillRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
        seckillRule.setWarmUpPeriodSec(10);                  // 预热 10 秒,防止冷启动瞬间打崩
        seckillRule.setLimitApp("default");
        rules.add(seckillRule);
​
        // 关联限流:当查询接口流量过高时,限制下单接口
        FlowRule queryRelatedRule = new FlowRule("order:create");
        queryRelatedRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        queryRelatedRule.setCount(500);
        queryRelatedRule.setStrategy(RuleConstant.STRATEGY_RELATE);
        queryRelatedRule.setRefResource("order:query");     // 关联 order:query 资源
        queryRelatedRule.setRefThreshold(2000);             // order:query 超过 2000 时触发
        rules.add(queryRelatedRule);
​
        FlowRuleManager.loadRules(rules);
    }
}

3.3 Controller 与统一异常处理

java 复制代码
@RestController
@RequestMapping("/seckill")
public class SeckillController {
​
    @GetMapping("/submit")
    @SentinelResource(value = "seckill:submit",
                      blockHandler = "submitBlockHandler",
                      fallback = "submitFallback")
    public String submit(@RequestParam Long skuId, @RequestParam Long userId) {
        // 真正的秒杀下单逻辑
        return "下单成功:sku=" + skuId;
    }
​
    // blockHandler:仅处理 Sentinel 限流/降级抛出的 BlockException
    public String submitBlockHandler(Long skuId, Long userId, BlockException ex) {
        return "系统繁忙,请稍后再试";
    }
​
    // fallback:处理业务异常(如库存不足、RPC 超时)
    public String submitFallback(Long skuId, Long userId, Throwable ex) {
        return "下单失败:" + ex.getMessage();
    }
}
@RestControllerAdvice
public class SentinelGlobalExceptionHandler {
​
    @ExceptionHandler(BlockException.class)
    public ResponseEntity<String> handleBlockException(BlockException e) {
        return ResponseEntity.status(429).body("请求过于频繁,请稍后重试");
    }
}

老梁提醒

  • blockHandler 只处理 Sentinel 规则触发;fallback 处理业务异常。两者参数签名必须一致,且最后一个是 BlockExceptionThrowable

  • 预热模式对秒杀场景非常有用,避免流量瞬间打到最大值。


四、代码实战 2:熔断降级,让故障"快速失败"

限流是"防冲",熔断是"止损"。下游服务变慢或者疯狂报错时,继续调用只会把故障放大。

Sentinel 熔断有三种触发策略:

策略 含义 适用场景
慢调用比例 单位时间内慢调用占比超过阈值 下游 RT 抖动、GC 频繁
异常比例 异常调用占比超过阈值 下游偶发 500、网络抖动
异常数 异常调用次数超过阈值 下游明确故障

下面以保护 AI 模型调用接口为例,演示慢调用比例熔断。

java 复制代码
@Configuration
public class SentinelDegradeConfig {
​
    @PostConstruct
    public void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();
​
        DegradeRule aiRule = new DegradeRule("ai:chat");
        aiRule.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType()); // 慢调用比例
        aiRule.setCount(0.5);                  // 慢调用比例阈值 50%
        aiRule.setSlowRatioThreshold(800);     // 超过 800ms 算慢调用
        aiRule.setTimeWindow(10);              // 熔断后 10 秒内快速失败
        aiRule.setMinRequestAmount(10);        // 最小统计请求数,避免刚启动就熔断
        aiRule.setStatIntervalMs(1000);        // 统计窗口 1 秒
        rules.add(aiRule);
​
        DegradeRuleManager.loadRules(rules);
    }
}
@Service
public class AiChatService {
​
    private final ChatClient chatClient;
​
    public AiChatService(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }
​
    @SentinelResource(value = "ai:chat",
                      blockHandler = "chatBlock",
                      fallback = "chatFallback")
    public String ask(String question) {
        return chatClient.prompt()
                         .user(question)
                         .call()
                         .content();
    }
​
    public String chatBlock(String question, BlockException ex) {
        // 熔断或限流时,直接返回本地兜底话术
        return "AI 服务繁忙,已切换至离线模式。常见问题请查看帮助文档。";
    }
​
    public String chatFallback(String question, Throwable ex) {
        // 大模型调用本身的异常
        return "AI 响应异常:" + ex.getMessage();
    }
}

这里的关键是:熔断时间窗口结束后,Sentinel 会进入半开状态,放少量请求试探,成功后关闭熔断。 不要自己写死"休眠 10 秒再恢复",那在波动环境里很容易误恢复。


五、代码 3:热点参数限流,针对"某个用户/某个商品"精准打击

全局 QPS 限流有一个问题:羊毛党用 10 个账号并发,每个账号请求量都不高,但总量能压垮系统。热点参数限流就是来解决这种"局部热点"的。

假设我们要保护一个 AI 文档摘要接口,针对 docId 做限流,同时给 VIP 用户单独配置阈值。

java 复制代码
@Configuration
public class SentinelParamFlowConfig {
​
    @PostConstruct
    public void initParamRules() {
        List<ParamFlowRule> rules = new ArrayList<>();
​
        ParamFlowRule docRule = new ParamFlowRule("ai:summary");
        docRule.setParamIdx(0);              // 第 0 个参数是 docId
        docRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        docRule.setCount(10);                // 默认每个 docId 10 QPS
​
        // 对特定热点值单独限流:docId=10086 的文档被疯狂点击
        List<ParamFlowItem> items = new ArrayList<>();
        items.add(new ParamFlowItem("10086", 2, String.class.getName()));
        docRule.setParamFlowItemList(items);
​
        rules.add(docRule);
        ParamFlowRuleManager.loadRules(rules);
    }
}
@RestController
@RequestMapping("/ai")
public class AiSummaryController {

    @GetMapping("/summary")
    @SentinelResource(value = "ai:summary",
                      blockHandler = "summaryBlock")
    public String summary(@RequestParam String docId,
                          @RequestParam Long userId) {
        // 调用大模型生成摘要
        return "这是文档 " + docId + " 的 AI 摘要......";
    }

    public String summaryBlock(String docId, Long userId, BlockException ex) {
        // 热点限流触发,返回缓存摘要或友好提示
        return "该文档当前访问火爆,摘要生成排队中,请 5 秒后重试";
    }
}

热点参数限流在 AI 场景特别好用:

  • 同一个问题被反复问,缓存没命中时可以限流,保护 Token 费用。

  • 某个用户疯狂刷接口,按 userId 限流,不影响其他用户。


六、系统自适应保护:最后一道闸门

前面都是针对单个资源做规则。但如果机器本身已经不行了------CPU 被打满、Load 飙高、线程数暴涨------再精细的规则也救不了。

java 复制代码
Sentinel 的 SystemSlot 提供了系统级保护,根据当前系统负载动态控制入口总流量。

@Configuration
public class SentinelSystemConfig {

    @PostConstruct
    public void initSystemRules() {
        List<SystemRule> rules = new ArrayList<>();

        SystemRule rule = new SystemRule();
        rule.setHighestSystemLoad(8.0);       // Linux Load1 超过 8 限流
        rule.setHighestCpuUsage(0.8);         // CPU 超过 80% 限流
        rule.setMaxThread(500);               // 入口线程数超过 500 限流
        rule.setAvgRt(200);                   // 平均 RT 超过 200ms 限流
        rule.setQps(5000);                    // 入口总 QPS 超过 5000 限流
        rules.add(rule);

        SystemRuleManager.loadRules(rules);
    }
}

系统保护规则一般作为兜底,不要和流控规则重复叠加太多,否则容易出现"双重限流"导致正常流量被误杀。


七、生产环境里的三条实战建议

1. 规则必须持久化,否则重启就丢

Sentinel 默认规则放在内存里,服务重启就没了。生产里一定要接 Nacos、Apollo 或者 Redis 数据源。

java 复制代码
spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow
        degrade:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            rule-type: degrade

2. 限流提示要做业务语义化

不要直接抛 429 Too Many Requests 给前端。用户看不懂,产品也骂你。统一封装成:

java 复制代码
{
  "code": "RATE_LIMITED",
  "message": "当前排队人数较多,预计等待 8 秒",
  "retryAfter": 8
}

3. 先埋点,再配规则,阈值是压测出来的

很多团队一上来就凭感觉写 count=100。正确的做法是:

  • 先用 Sentinel 控制台或者 Micrometer 暴露接口 QPS/RT 基线;

  • 用 JMeter 压测找到拐点;

  • 把阈值设在拐点 70%~80% 的位置,留出缓冲。


八、结尾:把"防御"做成系统能力

流量治理不是加几行代码的事,而是一种系统能力。

限流解决"谁该进",熔断解决"坏了怎么办",降级解决"还能给什么",系统保护解决"机器不行了怎么保命"。 四层叠加,才是真正能扛住生产风暴的架构。

下一篇 Day 46,我们聊一聊 Spring Cloud Gateway 网关实战:路由、过滤器、限流全攻略,看看怎么在网关层就把坏人拦在外面。

相关推荐
cfm_29141 天前
了解Sentinel
分布式·架构·sentinel
Bruce18011 天前
Sentinel 限流熔断学习:配置策略、限流算法对比与适用场景
sentinel
成为你的宁宁1 天前
【Sentinel部署与流量防护】
sentinel
952362 天前
Sentinel
java·后端·spring·sentinel·springcloud
渣渣盟3 天前
当 Redis 集群发生主从切换(Failover)时,Flink 任务会崩溃吗?如何利用 Sentinel 实现高可用?
redis·flink·sentinel
xiaoxiangsiyan9 天前
LNMP + Redis Sentinel 高可用架构部署手册(续)
运维·网络·数据库·redis·缓存·架构·sentinel
敲代码的嘎仔9 天前
从零搭建微服务:Spring Cloud Alibaba 全家桶踩坑实录(Nacos + OpenFeign + Gateway + Sentinel)
java·spring boot·spring·spring cloud·微服务·gateway·sentinel
小罗水12 天前
第14章 Sentinel 限流与 AI 降级
人工智能·sentinel
he___H12 天前
Sentinel使用实操
java·开发语言·sentinel