乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • Spring Cloud Alibaba概述与技术选型
  • Nacos注册中心
  • Nacos配置中心
  • Sentinel流量控制
  • Sentinel降级与熔断
  • Seata分布式事务
  • RocketMQ消息驱动
  • Spring AI Alibaba与AI集成
  • Dubbo RPC服务调用
  • Gateway服务网关
  • GraalVM静态编译
  • 其他组件速览
  • Alibaba最佳实践与面试考点

Sentinel 流量控制

作者:大翔 CTO 审核 | 白歌 架构师 技术校验 | 小崔 后端开发 示例编写 | 黄俪 前端开发 Dashboard 验证


定位

Sentinel 解决什么问题

Sentinel(哨兵)是阿里巴巴开源的流量防卫兵,以流量为切入点,从流量控制(Flow Control)、熔断降级(Circuit Breaking)、**系统自适应保护(System Adaptive Protection)**三个维度保障微服务稳定性。对于 985 科班同学而言,可以把它理解为微服务架构中的"智能交通调度系统"——既要保证高速公路不被冲垮(流量整形),也要在事故路段提前分流(熔断降级),还要在节假日整体限流(系统保护)。

具体解决三大问题:

问题域具体表现Sentinel 对策
流量整形(Traffic Shaping)秒杀场景瞬间 QPS 从 100 飙升至 10000,后端服务被打垮通过 QPS/线程数阈值限流,超出流量快速失败或排队等待
过载保护(Overload Protection)下游依赖变慢导致调用方线程池耗尽,级联雪崩通过熔断降级快速失败,保护调用方自身资源
流量调度(Traffic Routing)核心链路与边缘链路共用同一资源池,互相影响通过关联限流和链路限流,优先保障核心业务

与 Hystrix 的对比

Hystrix 是 Netflix 开源的熔断器库,已于 2018 年进入维护模式。Sentinel 在功能丰富度、可观测性和活跃度上全面超越:

维度HystrixSentinel
规则类型仅熔断(线程池/信号量隔离)流控 + 熔断 + 热点参数 + 系统规则 + 授权规则
可视化需要集成 Hystrix Dashboard + Turbine自带 Dashboard,实时监控 QPS/RT/线程数/调用链路
隔离机制线程池隔离(重量级,额外上下文切换开销)信号量隔离(轻量级,基于并发线程数统计)
规则推送无原生推送,需依赖 Archaius 轮询支持原始模式 → Pull 模式 → Push 模式(配置中心推送)
活跃度停维,仅修复严重 Bug阿里巴巴持续迭代,社区活跃
扩展性有限的插件体系Slot Chain 责任链模式,可自定义 Slot SPI 扩展

核心概念

在深入代码之前,理解四个核心概念的定位至关重要:

  • 资源(Resource):Sentinel 要保护的目标。可以是一个 URL(/order/{id})、一个方法、甚至一段代码块。在 Sentinel 的世界观里,一切需要被保护的调用都是资源。
  • 规则(Rule):围绕资源设定的控制策略。例如"资源 getOrder 的 QPS 不超过 10"、"资源 getUser 在异常比超过 50% 时熔断 10 秒"。规则可以动态添加、修改和删除,无需重启应用。
  • 入口(Entry):每次资源调用都会生成一个 Entry 对象,代表一次流量"进入 Sentinel 管辖范围"。Entry 穿过 Slot Chain 时会被各种规则检查。
  • Slot Chain(插槽链):Sentinel 的核心处理流水线,基于责任链模式(Chain of Responsibility Pattern)。每个 Slot 负责一个维度的检查(统计、流控、熔断、授权等),链式调用,逐层校验。

核心概念

资源的三种定义方式

小崔在飞翔科技订单服务中,根据不同的保护粒度选择不同的资源定义方式:

方式一:URL 默认资源(零侵入)

Sentinel 自动将 Spring MVC 的 URL 作为资源名,无需任何代码改动。适合快速接入、按接口维度限流的场景。

# Sentinel Dashboard 中自动出现:
# 资源名: GET:/order/{id}
# 资源名: POST:/order/create

缺点:资源名不可自定义,粒度固定为 URL 级别。

方式二:@SentinelResource 注解(推荐)

通过注解声明资源名和降级处理方法,粒度为方法级别,是生产环境最常用的方式。

@RestController
@RequestMapping("/order")
public class OrderController {

    @GetMapping("/{id}")
    @SentinelResource(
        value = "getOrder",                   // 资源名
        blockHandler = "getOrderBlockHandler" // 流控/熔断降级方法
    )
    public String getOrder(@PathVariable Long id) {
        // 业务逻辑...
        return "Order-" + id;
    }

    // blockHandler:处理 BlockException(流控/熔断触发)
    public String getOrderBlockHandler(Long id, BlockException ex) {
        return "订单查询限流中,请稍后重试";
    }
}

优点:资源名可控;可指定 blockHandler 和 fallback;支持热点参数限流。

方式三:SphU API 硬编码(代码块级)

最细粒度的控制方式,适合保护非 Web 入口的代码块(如定时任务、MQ 消费、第三方 SDK 调用)。

import com.alibaba.csp.sentinel.SphU;

public void batchExportOrders() {
    Entry entry = null;
    try {
        entry = SphU.entry("batchExportOrders");
        // 受保护的代码块
        doBatchExport();
    } catch (BlockException e) {
        // 被限流或熔断
        log.warn("批量导出被限流");
    } finally {
        if (entry != null) {
            entry.exit();
        }
    }
}

三种方式的推荐顺序:@SentinelResource > URL 默认资源 > SphU API。99% 的场景用注解即可满足。

流量控制规则(FlowRule)

流量控制规则是 Sentinel 最核心的规则类型,围绕"多少请求可以通过"展开。大翔在技术评审时总结了一句话:"理解 FlowRule,就理解了 Sentinel 的 80%。"

两种统计维度:

维度含义适用场景技术本质
QPS 模式(FLOW_GRADE_QPS)每秒通过的请求数流量整形,控制请求频率通过滑动窗口统计每秒请求数,超过阈值则拒绝。适合大多数 Web 接口保护
线程数模式(FLOW_GRADE_THREAD)同时处理的并发线程数隔离慢调用,防止线程池耗尽统计当前正在处理的请求数,超过阈值则拒绝。适合保护调用下游慢服务的场景
// 代码方式定义 FlowRule
FlowRule rule = new FlowRule();
rule.setResource("getOrder");               // 资源名
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // QPS 模式
rule.setCount(3);                            // 阈值:3 QPS
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 快速失败
FlowRuleManager.loadRules(Collections.singletonList(rule));

白歌补充:"QPS 和线程数的选择有门道。QPS 像高速公路收费站——控制进入的车流量;线程数像停车场——控制同时停着的车数量。如果下游 RT 波动不大,用 QPS 就够了;如果下游偶尔会变慢(比如调用第三方接口),用线程数模式能更好地隔离慢调用扩散。"

流控效果(ControlBehavior)

当 QPS/线程数超过阈值后,如何处理超出的请求?三种效果:

效果行为适用场景用户体验
快速失败(Fast Fail,默认)直接抛出 FlowException对响应时间要求极高,不允许排队"系统繁忙" 提示(黄俪设计的友好提示页)
Warm Up(预热)阈值从 1/coldFactor 逐步升至设定值冷启动防止流量冲击用户无感知(客户端自动重试)
排队等待(Rate Limiter)请求排队,匀速通过,多余等待超时脉冲流量削峰填谷响应变慢但不会失败

Warm Up 预热原理(令牌桶算法的变体):

Sentinel 预热模式基于 Guava 的 SmoothWarmingUp 思想,使用**令牌桶算法(Token Bucket Algorithm)**的变体实现。启动时桶内令牌生成速率很低(阈值 / coldFactor,coldFactor 默认为 3),在 warmUpPeriodSec 时长内线性增长到全速率。

预热曲线:
QPS │
    │                    ┌────────── 全速率 100
100 ├────────────────────┤
    │               ╱
    │            ╱
 33 ├─────────╱  coldFactor=3,初始速率 33
    │
    └─────┬──────────────────► 时间
          预热期(warmUpPeriodSec,默认 10s)

小崔的感受:"之前有一次服务重启后立即被大量流量打挂,就是因为刚启动时缓存没热、连接池没满。加上 Warm Up 预热后,前 10 秒只放行 1/3 流量,等 JIT 编译完成、连接池建立好,再全量放开。"

流控模式(Strategy)

三种流控模式决定了"什么时候触发限流":

模式触发条件配置项典型场景
直接(Direct)当前资源 QPS/线程数超过阈值strategy=0限制下单接口本身
关联(Relate)关联资源超过阈值时,限制当前资源strategy=1 + refResource写接口压力大时,自动限制读接口
链路(Chain)当前资源在指定入口链路中超过阈值strategy=2 + refResource同一个 Service 方法,只限制来自下单入口的调用

直接模式:最常用,资源自己管自己。

关联模式:优先级调度思想。白歌举了个例子:"订单查询(读)和订单写入(写)共享数据库连接池。写操作本身不重要可以排队,但如果写操作 QPS 过高抢占连接池,会导致读操作 RT 升高。这时设置关联规则——当写接口 QPS 超过阈值时,自动限制读接口,优先保证写操作完成。"

链路模式:精细化流量治理。同一个 Service 方法被多个入口调用,只想限制某个入口的调用频率。

// 关联流控规则
FlowRule rule = new FlowRule();
rule.setResource("getOrder");             // 被限制的资源:读接口
rule.setStrategy(RuleConstant.STRATEGY_RELATE); // 关联模式
rule.setRefResource("createOrder");       // 关联资源:写接口
rule.setCount(50);                        // 当写接口 QPS > 50 时,限制读接口
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);

热点参数限流(ParamFlowRule)

流量控制的精细化工夫。不是"整个接口限 100 QPS",而是"接口整体限 100 QPS,但商品 ID=100 的热卖品只给 1 QPS"。

核心思想:统计同一资源中不同参数值(如商品 ID、用户 ID)的请求频率,对高频参数值(热点)单独限流,对低频参数值(普通)使用全局阈值。既保障了整体流量,又精准打压了热点。

// 热点参数规则
ParamFlowRule rule = new ParamFlowRule();
rule.setResource("getProductDetail");     // 商品详情接口
rule.setParamIdx(0);                       // 第 0 个参数(商品 ID)
rule.setCount(100);                        // 全局阈值:100 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);

// 对特定参数值设置例外:商品 ID=100 的 QPS 限制为 1
ParamFlowItem item = new ParamFlowItem();
item.setObject("100");                    // 参数值
item.setClassType(String.class.getName());
item.setCount(1);                          // 例外阈值
rule.setParamFlowItemList(Collections.singletonList(item));

热点参数限流的判定优先级:先检查热点参数例外 → 再检查全局 QPS 阈值。如果商品 ID=100 的请求已达 1 QPS,即使全局还有 99 QPS 的余量也会被拒绝。

系统自适应保护规则(SystemRule)

前面的规则都是"针对某个资源的限流",而系统规则是"从整个应用实例的维度保护",防止系统整体负载过高导致雪崩。

触发条件含义默认阈值说明
Load(系统负载)SystemLoad >= 阈值CPU 核数 × 2.5仅 Linux/Unix 有效,Windows 不可用
RT(平均响应时间)所有入口流量的平均 RT >= 阈值—单位 ms,防止 RT 恶化
线程数所有入口的并发线程数 >= 阈值—防止线程池耗尽
入口 QPS所有入口的 QPS 总和 >= 阈值—整体限流,兜底策略
CPU 使用率CPU usage >= 阈值—仅支持 JDK 9+ / Sun JDK
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(4.0);            // Load 阈值
rule.setHighestCpuUsage(0.8);              // CPU 使用率 80%
rule.setAvgRt(100);                        // 平均 RT ≥ 100ms
rule.setMaxThread(200);                    // 并发线程数 ≥ 200
rule.setQps(500);                          // 入口 QPS ≥ 500
SystemRuleManager.loadRules(Collections.singletonList(rule));

白歌强调:"系统规则是最后一道防线。当业务层的流控规则配置失误时,系统规则能从操作系统层面兜底,确保单个实例不会被打挂。"


核心原理

Sentinel Slot Chain 架构

Sentinel 的整个处理流水线基于**责任链模式(Chain of Responsibility Pattern)**构建。每个请求进入 Sentinel 时,依次穿过如下 Slot 链:

各 Slot 职责:

Slot职责关键操作
NodeSelectorSlot构建调用链路树,记录资源间的调用关系为每个资源创建 DefaultNode,挂载到 Context 的调用树
ClusterBuilderSlot构建集群节点统计信息,聚合同一资源的全局指标创建 ClusterNode,同一资源的所有链路共享
StatisticSlot核心统计插槽:记录通过数、阻塞数、异常数、RT、线程数Fire-and-Forget 模式:先放行后续 Slot,再统计(保证异常和 RT 被准确计数)
AuthoritySlot黑白名单授权控制按 origin(调用方来源应用)做访问控制
SystemSlot系统自适应保护检查 Load / CPU / RT / 线程数
FlowSlot流量控制从 FlowRuleManager 获取规则,判断是否限流
DegradeSlot熔断降级从 DegradeRuleManager 获取规则,判断是否熔断
ParamFlowSlot热点参数限流按参数值统计,对热点参数执行例外规则

关键设计点——StatisticSlot 的 Fire-and-Forget 统计时机:

伪代码展示核心逻辑:

entry(context, resource, node, count, ...) {
    // 步骤1:先放行后续 Slot 链(FlowSlot → DegradeSlot → 业务逻辑)
    fireEntry(context, resource, node, count, ...);

    // 步骤2:请求成功通过,统计数据
    node.increaseThreadNum();        // 当前线程数 +1
    node.addPassRequest(count);     // 通过 QPS + count

    long startTime = now();
    try {
        // 步骤3:执行业务逻辑
    } catch (BlockException e) {
        node.increaseBlockQps(count); // 被阻塞 QPS + count
        throw e;
    } catch (Exception e) {
        node.increaseExceptionQps(count); // 异常 QPS + count
    } finally {
        long rt = now() - startTime;
        node.addRtAndSuccess(rt, count);  // RT 统计 + 成功数
        node.decreaseThreadNum();         // 当前线程数 -1
    }
}

Fire-and-Forget 的设计精妙之处:先放行再统计,确保即使业务逻辑抛出异常,统计信息也不会丢失。同时 RT 的统计完全包含业务逻辑的执行时间,真实反映用户体感延迟。

扩展机制——Slot SPI:

Sentinel 的 Slot Chain 通过 Java SPI 机制加载,开发者可以自定义 Slot 并插入到链的任意位置。大翔曾在一次技术分享中提到:"Sentinel 的可扩展性源于 Slot Chain 的开放性。曾经在一个项目里,我们写了一个 MetricsPushSlot 插在 StatisticSlot 后面,把 QPS/RT 数据实时推送到 Prometheus,就改了一行 SPI 配置。"

LeapArray 滑动窗口统计原理

Sentinel 的 QPS 统计不是简单的"每秒清零计数器",而是基于**环形数组(Ring Buffer)+ 滑动窗口(Sliding Window)**的 LeapArray 算法。这是白歌在面试中最喜欢追问候选人的知识点。

关键参数与默认值:

参数默认值说明
sampleCount2环形数组窗口数量
intervalInMs1000统计周期(ms)
窗口长度(windowLengthInMs)500msintervalInMs / sampleCount

核心操作——currentWindow() 窗口定位:

当前时间 currentTime = 1520000000000ms

// 1. 计算窗口索引
windowIndex = (currentTime / windowLengthInMs) % sampleCount
            = (1520000000000 / 500) % 2
            = 0 或 1

// 2. 计算窗口起始时间
windowStart = currentTime - (currentTime % windowLengthInMs)

// 3. 判断窗口是否过期
if (windowStart > currentWindow.windowStart) {
    重置窗口数据  // 旧窗口被复用
}

为什么不用简单计数器?

简单计数器(AtomicLong 每秒清零)存在跨秒边界精度崩溃问题:

简单计数器问题示意:
Time:  0ms ───────── 900ms ── 1000ms ── 1100ms ─────────►
       请求A=100个      请求B=100个
                          ↑ 按简单计数器,前一秒=200, 后一秒=100
                          ↑ 但实际900~1100ms这200ms内有200个请求
                          ↑ 精度崩溃:200ms窗口被统计为1s的数据

滑动窗口始终覆盖最近 intervalInMs 的精确窗口,无边界误差。每个窗口的数据是"最近一个 intervalInMs 内该窗口时段的真实统计"。

控制台通信机制

Sentinel Dashboard 与客户端(微服务实例)之间的通信是理解规则推送和监控数据来源的关键。

关键配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080    # Dashboard 地址
        port: 8719                    # 应用与 Dashboard 通信的本地端口
        heartbeat-interval-ms: 5000   # 心跳间隔(默认 5s)
      eager: true                     # 启动时立即注册到 Dashboard

规则推送模式演进:

模式原理优缺点
原始模式(API 直推)Dashboard 通过 HTTP API 直接推送到应用内存简单但重启丢失
Pull 模式(定时拉取)应用定时从配置中心拉取规则有延迟,实时性差
Push 模式(配置中心推送)规则写入 Nacos/Apollo → 配置中心实时推送到所有实例推荐方案,持久化 + 实时推送

Push 模式配置(推荐):

spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-flow-rules
            group-id: SENTINEL_GROUP
            data-type: json
            rule-type: flow

白歌提醒:"面试时一定会问规则持久化。记住一句话:Dashboard 负责规则的可视化管理,Nacos 负责规则的持久化存储和实时推送,应用实例通过 DataSource 监听 Nacos 变更并实时更新本地 RuleManager。三者各司其职。"


环境准备

Sentinel Dashboard 下载与启动

Sentinel Dashboard 是独立的 Spring Boot 应用,提供规则管理和实时监控可视化。

下载:

从 GitHub Release 页面下载最新版 jar 包:

# 下载 Sentinel Dashboard(以 1.8.8 版本为例)
wget https://github.com/alibaba/Sentinel/releases/download/1.8.8/sentinel-dashboard-1.8.8.jar

启动:

# Windows 启动(默认端口 8080)
java -Dserver.port=8080 ^
     -Dcsp.sentinel.dashboard.server=localhost:8080 ^
     -Dproject.name=sentinel-dashboard ^
     -jar sentinel-dashboard-1.8.8.jar

# Linux/macOS 启动
java -Dserver.port=8080 \
     -Dcsp.sentinel.dashboard.server=localhost:8080 \
     -Dproject.name=sentinel-dashboard \
     -jar sentinel-dashboard-1.8.8.jar

启动后访问 http://localhost:8080,默认账号密码均为 sentinel。

黄俪体验 Dashboard 后的反馈:"界面很简洁,实时监控面板一眼就能看到 QPS 曲线和单台机器的 RT 分布。簇点链路功能可以直观看到每个资源的调用量排名,给前端做监控大屏时直接嵌入 iframe 就行。"

客户端接入依赖与配置

小崔在订单服务中引入 Sentinel:

Maven 依赖:

<!-- Sentinel 核心 Starter(已包含 sentinel-core + sentinel-transport-simple-http) -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

使用 spring-cloud-alibaba-dependencies BOM 管理版本,无需指定 version。

application.yml 配置:

spring:
  application:
    name: order-service
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080       # Dashboard 地址
        port: 8719                       # 本地通信端口(每个实例需不同)
      eager: true                        # 启动时立即注册(否则首次访问后才注册)
      web-context-unify: true            # 默认统一 Web Context(链路限流时需设为 false)
      filter:
        enabled: true                    # 启用 Spring MVC Filter 拦截

transport.port 默认从 8719 开始自增寻找可用端口,多实例部署在同一机器时无需手动分配。


完整示例1:QPS 限流(直接模式 + 快速失败)

场景描述

飞翔科技的订单服务是小崔负责的核心微服务。某天运营部门策划了一场爆品秒杀活动,预计瞬时 QPS 将突破 500。小崔在压力测试中发现,不做任何保护时,订单查询接口在 500 QPS 下直接 OOM(Out Of Memory)。

大翔在技术评审会上拍板:"订单查询接口是核心中的核心,但数据库只能撑 3 QPS。多出来的请求没必要进业务层,直接在网关层被 Sentinel 拦截,快速失败返回,既保护了数据库也让用户立刻知道限流了。"

代码实现

package com.feixiangtech.order.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/order")
public class OrderController {

    /**
     * 订单查询接口
     * @SentinelResource 将本方法声明为资源 "getOrder"
     * blockHandler 指定流控/熔断触发时的降级方法
     */
    @GetMapping("/{id}")
    @SentinelResource(
        value = "getOrder",                          // 资源名
        blockHandler = "getOrderBlockHandler"         // 流控/熔断降级方法
    )
    public String getOrder(@PathVariable Long id) {
        // 模拟数据库查询
        // 真实场景:orderService.findById(id)
        return "订单详情: ID=" + id + ", 商品=MacBook Pro, 金额=14999元";
    }

    /**
     * BlockHandler 方法(流控/熔断触发时调用)
     * 必须满足以下条件:
     *   1. 方法签名与原方法一致(参数类型和返回值类型)
     *   2. 最后额外加一个 BlockException 参数
     *   3. 方法必须是 public
     *   4. 如果原方法在同一个类中,blockHandler 方法必须是 static
     *      (不同类则不需要 static)
     */
    public String getOrderBlockHandler(Long id, BlockException ex) {
        return "Blocked by Sentinel —— 订单查询限流中,请稍后重试";
    }
}

Dashboard 配置

  1. 启动订单服务,确认 Sentinel Dashboard 的"机器列表"中出现 order-service
  2. 进入"簇点链路",找到资源 getOrder,点击"流控"按钮
  3. 配置规则:
配置项值说明
资源名getOrder与 @SentinelResource value 一致
阈值类型QPS按每秒请求数限流
单机阈值3每秒最多 3 个请求
流控模式直接当前资源自己触发限流
流控效果快速失败直接抛出 FlowException

操作前后对比

状态最大 QPS超出流量行为数据库压力用户体验
操作前(无限流)500+全部进入业务层CPU 100%,OOM 崩溃服务不可用,500 错误
操作后(QPS=3)3快速返回限流提示CPU < 20%,稳定限流提示友好,核心请求正常处理

黄俪在前端加了一个简单的重试提示:"收到 'Blocked by Sentinel' 后,页面显示一个倒计时 3 秒的遮罩层,用户无需手动刷新,自动重试。"


完整示例2:关联限流 + Warm Up 预热

场景描述

飞翔科技订单服务有两个核心接口:

  • GET /order/{id} —— 订单查询(核心,用户下单前必查,对延迟敏感)
  • POST /order/create —— 创建订单(次要,允许排队,对延迟不敏感)

两个接口共享同一个数据库连接池(最大连接数 20)。白歌在 Code Review 时发现一个隐患:"当批量导入历史订单时,创建接口 QPS 暴增,连接池被写操作占满,导致查询接口 RT 从 50ms 飙升至 3000ms。用户在订单确认页面卡住,开始刷屏,进一步放大问题。"

解决方案:设置关联限流——当创建订单 QPS 超过 10 时,自动限流查询接口,优先保证写操作完成。同时,查询接口配置 Warm Up 预热,防止服务重启后冷启动流量冲击。

Dashboard 配置(关联规则)

配置项值说明
资源名getOrder被限制的资源(查询接口)
阈值类型QPS—
单机阈值3当关联资源 QPS > 10 时,查询接口降到 3 QPS
流控模式关联不是 getOrder 自己触发,而是关联资源触发
关联资源createOrder当 createOrder QPS > 10 时限流 getOrder
流控效果Warm Up预热模式(兼顾服务重启场景)
预热时长10s启动前 10s 阈值 = 3 / 3 = 1,线性增长到 3

Warm Up 效果说明

QPS │
  3 ├──────────────────────────────── 全阈值
    │                    ╱
    │                 ╱
    │              ╱
  1 ├───────────╱ coldFactor=3,初始 QPS = 3/3 = 1
    │
    └────┬──────────┬────────────────► 时间
        0s        10s(warmUpPeriodSec)
        启动        预热完成

操作前后对比

状态创建订单 QPS=20 时查询接口情况用户体验
操作前(无关联)数据库连接池占满RT 3000ms,几乎不可用页面卡死,用户刷屏
操作后(关联限流)自动触发查询限流查询 QPS 降到 3,RT 回到 50ms查询返回限流提示但可接受

白歌的评价:"关联限流本质上是一种优先级调度——高优先级的资源(查询接口)在低优先级资源(写接口)压力大时主动让渡资源。这种设计比无脑限流要优雅得多。"


完整示例3:热点参数限流

场景描述

飞翔科技商城有 10 万个商品,商品详情接口 GET /product/{productId} 的整体 QPS 上限设为 100。但在"618 大促"中,运营团队发现 99% 的流量集中在前 3 个爆款商品上,其中商品 ID=100(iPhone 16 Pro)的请求量是普通商品的 100 倍以上。

如果按全局 100 QPS 限流,热卖品会把额度全占满,普通商品反而被误伤。小崔意识到需要用热点参数限流——对商品 ID=100 单独设置 1 QPS 的严苛限制。

代码实现

@RestController
@RequestMapping("/product")
public class ProductController {

    /**
     * 商品详情接口
     * @param productId 商品 ID(热点参数)
     */
    @GetMapping("/{productId}")
    @SentinelResource(
        value = "getProductDetail",
        blockHandler = "getProductDetailBlockHandler"
    )
    public String getProductDetail(@PathVariable Long productId) {
        // 模拟查询商品详情
        return "商品详情: ID=" + productId + ", 名称=iPhone 16 Pro, 库存=有限";
    }

    /**
     * BlockHandler
     * 注意:参数列表必须与原方法一致,最后加 BlockException
     */
    public String getProductDetailBlockHandler(Long productId, BlockException ex) {
        return "商品 " + productId + " 访问过热,请稍后再试——限流保护中";
    }
}

Dashboard 配置

  1. 进入"热点规则"标签页,新增热点规则:
配置项值说明
资源名getProductDetail商品详情资源
参数索引0从 0 开始计数,productId 是方法第 0 个参数
单机阈值100全局默认阈值
统计窗口时长1s1 秒内统计
  1. 在"参数例外项"中添加:
参数类型参数值限流阈值
long1001

操作前后对比

状态全局 QPS商品 ID=100 的 QPS普通商品 QPS效果
操作前(统一限流)10098(占满额度)2(被挤压)热卖品霸占资源
操作后(热点限流)1001(单独限制)100(有余量)热卖品被精准打压

大翔在复盘时说:"热点参数限流是 Sentinel 的杀手锏功能,Hystrix 做不到。它的价值不在技术复杂度,而在于解决了一个真实的业务痛点——不是所有请求生而平等。"


完整示例4:链路限流

场景描述

飞翔科技的订单服务中有一个 OrderService.queryOrder() 方法,被两个入口调用:

  1. 下单入口:OrderController.createOrder() → OrderService.queryOrder()(下单前查询库存和价格)
  2. 管理后台入口:AdminController.exportOrders() → OrderService.queryOrder()(批量导出订单报表)

大翔发现管理后台的导出操作非常重,一次导出可能查询数千条订单。如果不加限制,导出操作会拖慢真实用户的下单流程。但小崔不想限制管理后台本身(管理员需要导出),只想限制来自下单入口的调用量。

解决方案:使用链路限流——只对从 createOrder 入口出发的调用链路进行限流,从 exportOrders 入口出发的调用不受限。

代码实现

第一步:关闭 Context 统一

spring:
  cloud:
    sentinel:
      web-context-unify: false   # 必须关闭!否则所有 URL 共享同一个 Context

默认 web-context-unify: true 会将所有 URL 入口的 Context 合并为 sentinel_spring_web_context,导致链路限流失效。关闭后,每个 URL 入口拥有独立的 Context。

第二步:在 Service 方法上定义资源

@Service
public class OrderService {

    /**
     * 查询订单——被两个入口调用
     * @SentinelResource 将此方法声明为资源 "queryOrderService"
     */
    @SentinelResource("queryOrderService")
    public Order queryOrder(Long orderId) {
        // 数据库查询逻辑
        return orderMapper.findById(orderId);
    }
}
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public String createOrder(@RequestParam Long orderId) {
        // 下单前查询——来自 createOrder 入口
        Order order = orderService.queryOrder(orderId);
        // ...
        return "下单成功";
    }
}
@RestController
@RequestMapping("/admin")
public class AdminController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/export-orders")
    public void exportOrders() {
        // 批量导出——来自 exportOrders 入口
        List<Long> ids = getAllOrderIds();
        for (Long id : ids) {
            Order order = orderService.queryOrder(id);  // 不会被链路限流限制
        }
    }
}

Dashboard 配置

配置项值说明
资源名queryOrderServiceService 层的资源名
阈值类型QPS—
单机阈值10—
流控模式链路只限制特定入口链路的流量
入口资源POST:/order/create只限制来自 createOrder 的调用链路

效果验证

入口资源链路限流是否生效实际 QPS
POST:/order/createqueryOrderService是(被限制)≤ 10
GET:/admin/export-ordersqueryOrderService否(不受限)无限流

白歌的解释:"链路限流本质上是给同一资源在不同入口设置不同的限流规则。它的底层实现依赖于 Sentinel 的 Context 机制——每个入口有独立的 Context,Context 中记录了 Entry 的调用链路。FlowSlot 判断限流时,不仅检查资源名,还检查当前 Context 的入口是否匹配 refResource。"


易错场景

错误1:blockHandler 方法签名不正确

错误写法:

@SentinelResource(value = "getOrder", blockHandler = "handleBlock")

// ❌ 错误:参数类型不匹配(缺少 Long id 参数)
public String handleBlock(BlockException ex) {
    return "限流了";
}
// ❌ 错误:方法不是 public
String handleBlock(Long id, BlockException ex) {
    return "限流了";
}
// ❌ 错误:BlockException 参数位置不对(必须在最后)
public String handleBlock(BlockException ex, Long id) {
    return "限流了";
}

正确写法:

/**
 * blockHandler 四条铁律:
 * 1. 方法必须 public
 * 2. 返回值类型与原方法一致
 * 3. 参数列表与原方法完全一致
 * 4. 最后额外加一个 BlockException 参数
 */
public String handleBlock(Long id, BlockException ex) {
    return "限流了";
}

小崔踩坑实录:"有一次我在 Controller 里写 blockHandler,方法是 public 的但忘了加 static,结果限流时直接 500 报 NoSuchMethodException。排查了半小时才发现:同类的 blockHandler 必须是 static,不同类才不需要。"

错误2:Sentinel Dashboard 规则未持久化

现象:应用重启后,Dashboard 上配置的流控规则全部消失。

原因:默认规则存储在应用内存中(通过 Dashboard HTTP API 推送到 JVM 内存),重启即丢失。

解决方案:配置 Nacos DataSource 持久化规则。

spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: 127.0.0.1:8848
            data-id: ${spring.application.name}-flow-rules
            group-id: SENTINEL_GROUP
            data-type: json
            rule-type: flow

Nacos 中存储的流控规则 JSON:

[
  {
    "resource": "getOrder",
    "limitApp": "default",
    "grade": 1,
    "count": 3,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
  }
]

错误3:Fallback 和 BlockHandler 混淆

这是一个高频错误——两个降级方法的触发条件完全不同。

概念触发条件处理方法签名典型场景
BlockHandler流控/熔断触发(BlockException)参数 + BlockException"请求太多,限流了"
Fallback业务异常(Throwable)参数 + Throwable"下游服务挂了,降级处理"
@SentinelResource(
    value = "getOrder",
    blockHandler = "handleBlock",   // ← 流控/熔断 → BlockException
    fallback = "handleFallback"     // ← 业务异常 → Throwable
)
public String getOrder(Long id) {
    // 可能抛异常的业务代码
}

// 处理流控/熔断(BlockException)
public String handleBlock(Long id, BlockException ex) {
    return "Blocked by Sentinel";
}

// 处理业务异常(Throwable)
public String handleFallback(Long id, Throwable ex) {
    return "Fallback: 服务暂时不可用,请稍后重试";
}

白歌总结:"记住一句话——BlockHandler 管流量,Fallback 管异常。面试时被问到区别,一定要说清楚 BlockException 是 Sentinel 自己抛的,而 Fallback 捕获的是业务代码里的 Exception。"

错误4:@SentinelResource 未配合 blockHandler

现象:限流触发后,用户看到的是 500 错误页面,而不是友好的限流提示。黄俪为此跟小崔吵过一架——"前端明明做了优雅的降级提示,结果后端直接甩了个 500,用户体验极差!"

原因:@SentinelResource 声明了资源但未指定 blockHandler,限流时 Sentinel 抛出 FlowException,Spring MVC 的默认异常处理将其转为 500。

// ❌ 错误:没有 blockHandler,限流时直接 500
@SentinelResource("getOrder")
public String getOrder(Long id) {
    return "Order-" + id;
}
// ✅ 正确:指定 blockHandler
@SentinelResource(value = "getOrder", blockHandler = "handleBlock")
public String getOrder(Long id) {
    return "Order-" + id;
}

public String handleBlock(Long id, BlockException ex) {
    return "Blocked by Sentinel —— 限流保护中";
}

错误5:热点参数限流 paramIndex 写错

现象:配置了热点参数限流但完全不生效。

原因:paramIndex 从 0 开始计数,容易因为参数列表的写法而出错。

// 假设想对 productId 做热点限流

// ❌ 错误:@PathVariable 是第 1 个参数,paramIndex 应为 1
public String getProductDetail(@RequestParam String source,
                                @PathVariable Long productId) {
    // paramIndex=0 实际对应的是 source,不是 productId!
}
// ✅ 正确:明确 productId 是第 1 个参数(从 0 开始),paramIndex=1
@SentinelResource(value = "getProductDetail")
public String getProductDetail(@RequestParam String source,
                                @PathVariable Long productId) {
    // paramIndex=1 对应 productId
}

面试考点

一、Sentinel Slot Chain 的设计模式与扩展机制

考点:你了解 Sentinel 的 Slot Chain 架构吗?它用了什么设计模式?怎样扩展?

核心回答:

Sentinel 的核心处理流水线基于责任链模式(Chain of Responsibility Pattern)。每个 Slot 是一个独立的处理单元,链式串联:NodeSelectorSlot → ClusterBuilderSlot → StatisticSlot → AuthoritySlot → SystemSlot → FlowSlot → DegradeSlot → ParamFlowSlot。

每个 Slot 有两个核心方法:entry() 和 exit()。请求进入时按顺序调用各 Slot 的 entry(),退出时逆序调用 exit()。

StatisticSlot 是核心统计插槽,采用 Fire-and-Forget 模式——先调用 fireEntry() 放行后续 Slot,再统计通过数和 RT。这样即使后续 Slot 或业务逻辑抛异常,统计数据也不会丢失。

扩展机制:通过 Java SPI 机制实现 Slot 扩展。在 META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot 文件中注册自定义 Slot 的完全限定类名,Sentinel 在初始化 Slot Chain 时自动加载。开发者可以实现自定义的统计、日志、告警等 Slot 并插入到链的任意位置。

二、LeapArray 滑动窗口的低开销原理

考点:Sentinel 的 QPS 统计为何比简单计数器精确?滑动窗口如何保证低开销?

核心回答:

精度优势:简单计数器(每秒清零)在跨秒边界时精度崩溃——前 1 秒的最后 100ms + 后 1 秒的前 100ms 共 200ms 的请求被统计为 1s 的数据。LeapArray 使用环形数组 + 滑动窗口,始终覆盖最近 intervalInMs(默认 1s)内的精确数据,无边界误差。

低开销原理:

  1. 无锁设计:每个窗口槽位内部的计数器使用 LongAdder(高并发场景性能远超 AtomicLong)和 AtomicInteger,避免了全局锁。
  2. 环形数组复用:环形数组大小固定(默认 2 个槽位),旧槽位被直接重置复用,无 GC 压力。
  3. O(1) 定位:通过 (currentTimeMillis / windowLengthInMs) % sampleCount 取模运算直接定位当前窗口,无遍历。
  4. 懒加载:窗口槽位仅在首次访问时创建,未使用的槽位不占用内存。

面试加分项:提到 StatisticSlot 的统计时机在业务逻辑执行之后(finally 块),确保 RT 统计包含完整的业务执行时间。

三、预热(Warm Up)的数学基础

考点:Sentinel 的 Warm Up 是基于令牌桶算法还是漏桶算法?

核心回答:

Sentinel 的 Warm Up 基于令牌桶算法(Token Bucket Algorithm)的变体,借鉴了 Guava 的 SmoothWarmingUp 实现。

  • 令牌桶算法:以恒定速率生成令牌放入桶中,请求需要获取令牌才能通过。桶满后令牌溢出丢弃。允许突发流量(短时间内可以消耗桶中积累的令牌)。
  • 漏桶算法:请求进入漏桶,以恒定速率流出。不允许突发流量(即使桶空了请求也必须按速率流出)。

Sentinel 的 Warm Up 在令牌桶基础上增加了预热因子(coldFactor)——启动时令牌生成速率为正常速率的 1/coldFactor,在预热期内线性增长到正常速率。流量整形曲线是梯形而非标准令牌桶的矩形,因此能防止冷启动瞬间的流量突发。

追问"Sentinel 能不能用漏桶实现":排队等待(Rate Limiter)模式更接近漏桶思想——固定间隔放行请求。但 Sentinel 的排队等待底层用的是改良的信号量 + 等待时间计算,并非传统漏桶。

四、热点参数限流与 QPS 限流的关系

考点:如果同时配置了 QPS 流控和热点参数限流,哪个先生效?

核心回答:

在 Sentinel 的 Slot Chain 中,FlowSlot 排在 ParamFlowSlot 之前,所以QPS 流控先于热点参数限流判定。但热点参数限流的内部逻辑是:

  1. 先检查热点参数例外:如果当前参数值匹配例外项(如商品 ID=100),按例外阈值判定
  2. 再检查全局 QPS:如果参数值不匹配任何例外项,按热点规则的全局阈值判定
  3. 最后检查 FlowRule:如果热点规则未限流,再走 FlowSlot 的 QPS 规则

所以"热点参数限流优先于 QPS 限流"指的是在 FlowSlot 内部的热点检查逻辑中,参数例外项优先于全局阈值。

五、系统自适应保护规则的触发条件

考点:Sentinel 的系统规则有哪些触发条件?它们适用于什么场景?

核心回答:

系统规则从应用实例整体维度保护,共五种触发条件:

条件指标来源Windows 支持适用场景
Load(系统负载)OperatingSystemMXBean.getSystemLoadAverage()❌ 不可用Linux 服务器整体负载保护
RT(平均响应时间)所有入口流量的平均 RT✅防止 RT 恶化级联
线程数所有入口的并发线程数✅防止线程池耗尽
入口 QPS所有入口的 QPS 总和✅整体兜底限流
CPU 使用率JDK 9+ getCpuLoad()✅精确 CPU 保护

触发逻辑:任一条件达到阈值即触发限流(OR 关系),而不是全部条件同时满足。因为 Sentinel 认为任何一个维度亮红灯都说明系统已经不堪重负。

面试追问"为什么 Windows 上 Load 不可用":Java 的 OperatingSystemMXBean.getSystemLoadAverage() 在 Windows 上始终返回 -1,这是 JDK 的已知限制。Windows 上建议用 CPU 使用率替代。

六、Sentinel 与 Hystrix 在隔离机制上的本质区别

考点:Sentinel 和 Hystrix 的隔离机制有什么本质区别?为什么 Sentinel 更轻量?

核心回答:

维度HystrixSentinel
隔离方式线程池隔离(默认)+ 信号量隔离信号量隔离(基于并发线程数统计)
原理每个依赖分配独立线程池,调用在独立线程中执行统计当前资源占用的线程数,超过阈值则拒绝
额外开销线程上下文切换、线程池管理、Queue 排队仅一个 AtomicInteger 计数器
隔离强度强隔离(完全物理隔离,慢调用不会影响其他资源)软隔离(无法阻止慢调用占用 CPU,但能防止线程耗尽)
适用场景对隔离性要求极高的场景(如调用不可信第三方)大多数微服务内部调用场景

Hystrix 的线程池模式每次调用都涉及线程切换(~10μs 额外开销 + 内存开销,每个线程池约 1MB 栈内存),在 QPS 较高的微服务内部调用场景下,这种开销不可忽视。Sentinel 的信号量隔离只做"线程数加减"的轻量操作(~0.1μs),更适合同一公司内部微服务间的流量保护。

本质区别:Hystrix 追求的是物理隔离(降级目标资源本身),Sentinel 追求的是流量控制(控制进入目标资源的流量速率)。这是两种不同的设计哲学——大翔总结为"Hystrix 像防火墙(隔离),Sentinel 像交通管制(疏导)。"


参考资源:

  • Sentinel 官方文档:https://sentinelguard.io/zh-cn/docs/introduction.html
  • Sentinel GitHub:https://github.com/alibaba/Sentinel
  • Spring Cloud Alibaba Sentinel:https://sca.aliyun.com/docs/2025/user-guide/sentinel/overview/
上一页
Nacos配置中心
下一页
Sentinel降级与熔断