六、Sentinel

文章目录

Sentinel简述

Sentinel 是什么?

Sentinel 是阿里巴巴开源的一款微服务流量治理组件。它主要用来保护微服务,防止某个接口因为访问量过大、响应太慢或者下游服务故障,最终拖垮整个系统。

简单来说:Sentinel 就像微服务系统中的"流量警察"和"安全保护器"。

Sentinel 主要解决什么问题?

在微服务系统中,一个请求通常会经过多个服务:

text 复制代码
用户请求
   ↓
订单服务
   ↓
商品服务
   ↓
库存服务
   ↓
数据库

假设库存服务突然响应很慢,大量请求就会堆积在订单服务中。请求不断增加后,可能出现:

text 复制代码
线程被占满
连接池被占满
CPU 使用率升高
内存持续增长
订单服务无法处理其他请求
最终整个调用链发生雪崩

Sentinel 会对这些请求进行监控和控制,在系统即将超出承受能力之前,主动限制一部分请求,避免故障继续扩大。

Sentinel 能做什么?

Sentinel 的核心功能主要包括:

  • 流量控制,限制接口每秒可以接收的请求数量。

  • 熔断降级,当下游服务响应太慢或者大量报错时,Sentinel 会暂时停止调用该服务。

  • 系统保护,Sentinel 可以根据整个服务器的运行状态进行保护,例如:CPU 使用率、系统负载、线程数量、入口请求数量、接口响应时间。当系统整体压力过大时,Sentinel 会限制新的请求进入,让系统先处理已经接收的请求。

  • 热点参数限流,Sentinel 不仅可以限制整个接口,还可以限制某个参数。例如商品查询接口:

    text 复制代码
    /product/1001
    /product/1002
    /product/1003

    正常情况下,不同商品访问量比较平均。但是商品 1001 突然成为爆款:/product/1001 每秒访问 5000 次,Sentinel 可以只限制商品 1001 的访问,不影响其他商品:

    text 复制代码
    /product/1001:限流
    /product/1002:正常访问
    /product/1003:正常访问

    这就是热点参数限流。

  • 授权控制,Sentinel 可以根据请求来源决定是否允许访问。例如:

    text 复制代码
    订单服务:允许调用商品服务
    后台管理服务:允许调用商品服务
    测试服务:禁止调用商品服务

    它类似于简单的黑白名单控制。

Sentinel 中的几个核心概念

  • 资源:资源就是 Sentinel 要保护的对象。资源可以是:

    text 复制代码
    一个 Controller 接口
    一个 Service 方法
    一个 OpenFeign 调用
    一段业务代码

    例如:

    java 复制代码
    @GetMapping("/product/{id}")
    @SentinelResource(value = "getProduct")
    public Product getProduct(@PathVariable Long id) {
        return productService.getById(id);
    }

    这里的:getProduct。就是资源名称。

  • 规则:规则表示如何保护这个资源。例如:

    text 复制代码
    资源名称:getProduct
    限流阈值:100
    统计方式:QPS

    表示:getProduct 接口每秒最多允许通过 100 个请求。Sentinel 常见规则包括:

    text 复制代码
    流控规则
    熔断规则
    热点规则
    系统规则
    授权规则
    QPS

Sentinel 的核心目的就是: 在高流量或服务故障时,牺牲少部分请求,保护整个系统继续运行。

一句话总结: Sentinel 是一款微服务流量治理和容错保护组件,通过限流、熔断、降级、热点控制和系统保护,防止流量过大或下游服务故障导致整个微服务系统雪崩。

Nacos: 负责服务注册、发现和配置管理

OpenFeign: 负责微服务之间的远程调用

Sentinel: 负责限流、熔断、降级和系统保护

Sentinel 架构原理

  1. 业务应用引入 Sentinel Core,在每个微服务内部建立本地保护能力。

    订单服务、商品服务、用户服务等业务应用引入 Sentinel 依赖后,每个微服务内部都会运行一套 Sentinel Core。 它会将 Controller 接口、Service 方法、OpenFeign 远程调用等需要保护的代码定义为 Sentinel 资源, 并在业务执行前完成规则检查。Sentinel Core 主要负责资源定义、请求统计、规则校验和异常处理。 需要注意,真正执行限流、熔断和降级判断的是各个微服务内部的 Sentinel Core,而不是 Sentinel Dashboard。即使多个服务使用同一个控制台,每个服务仍然独立统计自己的流量,并在本地执行自己的保护规则。

  2. 业务请求进入微服务后,由 Sentinel Core 统计数据并执行规则判断。

    当请求进入订单服务、商品服务或用户服务时,会先经过 Sentinel 的资源入口。Sentinel Core 会实时统计该资源的 QPS、并发线程数、响应时间、慢调用数量、异常数量和异常比例等指标,然后根据本地加载的流控、熔断、热点参数、授权和系统保护规则进行判断。请求通过规则检查后,才会继续执行 Controller、Service、数据库查询或 OpenFeign 远程调用;如果没有通过规则检查,Sentinel 会立即阻止后续业务执行,并抛出 BlockException ,然后由 blockHandler、Fallback 或统一异常处理返回"系统繁忙,请稍后重试"等兜底结果。

  3. Sentinel Dashboard 统一管理所有接入 Sentinel 的微服务。

    Sentinel Dashboard 是 Sentinel 的可视化控制台, 主要负责规则管理、实时监控、机器发现和链路查看。订单服务、商品服务和用户服务启动并连接控制台后,会定期向 Dashboard 发送心跳, 告诉控制台自己的应用名称、IP、端口等信息,因此控制台可以发现当前有哪些 Sentinel 客户端在线。同时,各个微服务会把资源访问数据上报给 Dashboard,例如接口的 QPS、通过数量、阻塞数量、响应时间和异常情况,开发人员可以在控制台中查看每个服务、每个资源的实时运行状态以及调用链路。

  4. 开发人员在 Dashboard 中创建规则,再将规则推送到对应微服务。

    开发人员可以在 Sentinel Dashboard 中为不同服务和资源创建流控规则、熔断规则、热点参数规则、授权规则以及系统保护规则。例如,为商品查询接口设置每秒最多通过 100 个请求;为订单服务调用商品服务设置慢调用比例熔断;或者限制只有指定来源的服务才能访问某个资源。规则创建后,Dashboard 会把规则推送给对应的微服务客户端,Sentinel Core 收到规则后加载到应用本地。后续请求到达时,就会立即按照新规则进行判断,不需要重启整个微服务。

  5. 动态规则数据源负责持久化规则,并同步到所有服务实例。

    如果规则只保存在 Sentinel Dashboard 或微服务内存中,当应用重启或 Dashboard 重启后,规则可能丢失。因此,生产环境通常会接入 Nacos、Apollo、ZooKeeper或本地文件等动态规则数据源, 将 Sentinel 规则持久化保存。以 Nacos 为例,开发人员修改规则后,可以把规则写入 Nacos;各个微服务监听 Nacos 中的规则配置,规则变化后自动刷新本地规则。这样,即使订单服务部署了多个实例,每个实例也可以从同一个规则数据源读取相同规则,保证集群中的规则一致。Dashboard 主要负责可视化管理,动态规则数据源负责规则的长期保存和同步。

  6. 多个微服务实例可以按需使用集群流控。

    默认情况下,每个微服务实例都在本地独立统计流量。例如,订单服务部署了三台实例,每台实例配置 QPS 阈值为 100,那么三台实例合计可能允许约 300 QPS。 某些场景要求整个集群共享一个总阈值,此时可以使用 Sentinel 集群流控。集群中的 Token Client 在请求进入时向 Token Server 申请令牌,Token Server 统一统计整个集群的流量,并决定是否发放令牌。申请成功,请求继续执行;申请失败,请求被限流。通过这种方式,可以把"每台机器各自限流"升级为"整个服务集群统一限流"。集群流控属于可选能力,普通项目使用 Sentinel Core 的本地限流即可。

  7. Sentinel 各部分共同形成"本地执行、控制台管理、数据源持久化"的架构。

    Sentinel Core 嵌入每个业务应用,在请求路径中完成资源统计、规则判断、限流、熔断和异常处理;Sentinel Dashboard 统一发现机器、展示监控数据、查看调用链路并管理规则;Nacos 等动态规则数据源负责持久化和同步规则;集群流控则在需要统一控制整个集群流量时,由 Token Server 和 Token Client 统一分配令牌。Dashboard 不会转发业务请求,也不会代替微服务执行规则,因此业务请求仍然直接进入对应服务,保护判断也在应用本地完成。

总结: Sentinel 采用"客户端嵌入式治理 + 控制台统一管理 + 动态数据源持久化"的架构。各个微服务内部的 Sentinel Core 负责实时统计并执行限流、熔断等规则;Dashboard 负责监控、机器发现和规则配置;Nacos 等数据源负责规则持久化与动态同步;需要对整个集群统一限制流量时,还可以使用 Token Server 和 Token Client 实现集群流控。

一句话理解: Sentinel Core 负责在微服务本地真正执行保护规则,Dashboard 负责统一查看和管理规则,Nacos 等数据源负责保存和同步规则。

Sentinel 工作原理

Sentinel 是一个面向分布式系统的流量控制和服务容错组件。它会把需要保护的接口、方法或远程调用定义为"资源",在业务代码真正执行之前,先统计资源的访问情况,再根据流控、熔断、系统保护、授权等规则决定本次请求是继续执行,还是直接拦截并返回兜底结果。

  1. 系统启动时加载规则并定义受保护资源。
    Spring Boot 应用启动后,会初始化 Sentinel 客户端,并加载流控、熔断、系统保护、热点参数和授权等规则。Controller 接口、Service 方法、OpenFeign 调用以及通过 @SentinelResource 标记的方法,都可以被定义为 Sentinel 资源。规则和资源信息最终保存在应用本地,由微服务中的 Sentinel 客户端负责执行。
  2. 业务请求进入 Sentinel 资源入口。
    当请求访问受保护的接口或方法时,Sentinel 会为本次调用创建资源入口 Entry,并根据资源名称查找对应的保护规则。请求不会立即执行后面的业务代码,而是先进入 Sentinel 的规则检查流程。
  3. 请求经过 ProcessorSlot 责任链检查。
    请求进入资源后,会依次经过 Sentinel 内部的 ProcessorSlot 责任链。StatisticSlot 负责统计 QPS、并发线程数、响应时间和异常数量;AuthoritySlot 检查访问来源;SystemSlot 检查系统整体负载;FlowSlot 执行流量控制;DegradeSlot 执行熔断判断;ParamFlowSlot 检查热点参数。每个 Slot 完成自己的工作后,再将请求交给下一个 Slot。
  4. Sentinel 根据实时数据判断是否放行。
    Sentinel 会结合当前资源的实时运行数据和已加载的规则,判断请求是否超过流量阈值、系统是否过载、资源是否处于熔断状态、请求来源是否允许以及热点参数是否访问过于频繁。只有所有规则都检查通过,请求才可以继续执行。
  5. 通过检查则执行正常业务,未通过则进入限流或降级处理。
    请求通过规则检查后,会继续执行 Controller、Service、数据库操作或 OpenFeign 远程调用。请求未通过检查时,Sentinel 会立即阻止后续业务执行并抛出 BlockException,然后由 blockHandler、Fallback 或统一异常处理返回限流提示或兜底结果。BlockException 表示请求被 Sentinel 规则拦截,与业务代码自身产生的普通异常不同。
  6. Sentinel 持续统计指标,并由 Dashboard 统一监控和管理规则。
    请求执行结束后,Sentinel 会记录通过数量、阻塞数量、QPS、并发线程数、响应时间和异常比例等指标,这些数据会继续参与后续的流控和熔断判断。微服务还会将监控数据上报给 Sentinel Dashboard,开发人员可以在控制台中查看运行情况并配置规则。Dashboard 负责监控和规则管理,真正的规则判断、限流和熔断仍然在各个微服务本地完成。

总结: Sentinel 的完整工作过程就是:系统启动时加载保护规则;业务请求到达后先进入 Sentinel 资源入口;请求经过 ProcessorSlot 责任链完成数据统计和规则检查;通过检查则放行业务请求并正常返回结果;未通过检查则抛出 BlockException,执行限流提示或兜底逻辑;同时 Sentinel 持续记录 QPS、响应时间和异常比例等指标,并将监控数据上报到 Dashboard。

一句话理解:

请求进入 Sentinel 后,会先进行实时统计,再依次检查授权、系统保护、流控、熔断和热点参数等规则;检查通过才执行真正的业务,检查不通过则立即拦截,并返回限流或降级结果。

Sentinel 功能描述

随着微服务架构越来越复杂,服务之间的调用关系也越来越多。当某个服务流量突然增加、响应速度变慢或者发生大量异常时,故障可能沿着调用链不断传播,最终导致多个服务同时不可用。Sentinel 以流量为切入点,从流量控制、熔断降级、系统保护、热点参数保护等多个维度保护微服务的稳定性。

  • 流量控制, 限制单位时间内进入系统的请求数量。
    当某个接口的访问量超过系统能够承受的范围时,Sentinel 可以根据配置的 QPS、并发线程数等阈值对请求进行限制。例如,商品查询接口最多只能承受每秒 100 个请求,那么第 101 个及之后的请求就可以被 Sentinel 拦截,避免大量请求同时进入业务代码、数据库或下游服务。被拦截的请求可以返回"系统繁忙,请稍后重试"等友好提示,从而保证已经进入系统的请求能够正常处理。Sentinel 还支持直接拒绝、排队等待、预热等流控效果,可以根据不同业务场景决定流量应该立即拒绝、匀速通过,还是逐渐增加。
  1. 根据调用关系和访问来源进行精细化流量控制。
    Sentinel 不仅可以限制某个接口的总流量,还可以结合调用链路、请求来源和关联资源进行控制。例如,同一个商品查询接口可能同时被订单服务、购物车服务和后台管理系统调用,可以根据调用来源决定哪些服务允许访问、哪些服务限制访问。对于调用链路中的不同入口,也可以设置不同规则;即使最终调用的是同一个方法,也能够根据请求从哪个入口进入进行区分。关联限流则可以在某个关键资源压力升高时,限制与它相关的其他资源,优先保证核心业务正常运行。
  2. 通过线程数隔离、慢调用降级和异常熔断阻止故障扩散。
    当下游服务响应变慢时,上游请求会长时间占用线程。如果请求不断积压,可能耗尽当前服务的线程池,导致原本正常的接口也无法访问。Sentinel 可以限制资源的并发线程数量,避免一个慢接口占满所有业务线程。同时,Sentinel 会统计接口的响应时间、慢调用比例、异常比例和异常数量。当慢调用或异常达到设定阈值时,Sentinel 会打开熔断器,在一段时间内暂时阻止请求继续调用故障服务,直接返回降级结果。熔断时间结束后,Sentinel 会尝试放行少量请求进行探测;如果服务恢复正常,就关闭熔断器,否则继续保持熔断。
  3. 通过系统自适应保护,防止整个应用被过载流量压垮。
    普通流控主要保护某一个接口,而系统自适应保护关注的是整个应用的运行状态。Sentinel 可以根据系统负载、CPU 使用率、入口 QPS、并发线程数、平均响应时间等指标判断当前应用是否已经接近承载极限。当系统整体压力过高时,Sentinel 会限制新的流量继续进入,为正在执行的请求保留足够资源,避免系统因为 CPU 过高、线程堆积或者响应时间持续增长而彻底失去响应。它保护的不是单个业务接口,而是整个应用的可用性。
  4. 通过热点限流、 集群限流和削峰填谷处理特殊流量场景。
    热点限流用于保护访问特别频繁的参数值。例如,商品查询接口整体流量并不高,但某个热门商品的 ID 被大量访问,Sentinel 可以只限制这个热点商品,而不影响其他普通商品的查询。集群限流用于多个服务实例共享同一个总流量阈值,例如订单服务部署了三台机器,需要整个集群每秒最多处理 1000 个请求,而不是每台机器各处理 1000 个请求。削峰填谷则可以将短时间内突然到达的大量请求排队并匀速放行,把瞬时流量高峰转换成相对平稳的请求流量,减少数据库和下游服务受到的冲击。
  5. Sentinel 控制台统一完成实时监控、机器发现和规则配置。
    接入 Sentinel 的 Spring Cloud、Dubbo、gRPC 或 Service Mesh 应用会向 Sentinel Dashboard 上报心跳和运行指标。控制台可以发现在线应用和服务实例,并展示资源的 QPS、通过数量、限流数量、异常数量、响应时间等实时数据。开发人员还可以在控制台中配置流控、熔断、热点参数、授权和系统保护规则。需要注意,Dashboard 主要负责查看监控和管理规则,真正的规则判断和请求拦截仍然由每个业务应用内部的 Sentinel Core 完成。
  6. 通过动态数据源实现规则持久化和自动同步。
    如果规则只保存在 Sentinel Dashboard 或应用内存中,服务重启后规则可能丢失。因此,实际项目中通常会把规则保存到 Nacos、Apollo、ZooKeeper 等动态规则数据源中。微服务启动时从数据源读取规则,并持续监听规则变化;管理员修改规则后,新的规则会自动同步到各个服务实例,无需重启应用。这样既可以保证规则长期保存,也能保证同一个服务的多个实例使用统一规则。

总结: Sentinel 的主要功能就是在请求进入业务系统后,对流量、并发线程、响应时间、异常情况、热点参数、系统负载和请求来源进行实时统计与判断。请求符合规则就正常放行;请求超过限制、下游服务异常或系统已经过载时,就执行限流、熔断或降级,返回兜底结果,防止局部故障进一步扩散。

整合Sentinel-基础场景

Sentinel下载地址

下载的jar包,直接:java -jar sentinel-dashboard-1.8.8.jar 启动

在需要的模块中添加Sentinel依赖和配置配置:

xml 复制代码
<!-- Sentinel:限流、熔断降级以及OpenFeign兜底支持 -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yml 复制代码
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 # Sentinel Dashboard 控制台地址
      eager: true # 项目启动时立即初始化 Sentinel,并向控制台发送心跳

访问: http://localhost:8080进入Sentinel Dashboard。默认端口是8080,用户名、密码默认都是:sentinel。

菜单 作用
实时监控 查看资源实时请求情况,例如:、通过QPS、拒绝QPS、平均响应时间
簇点链路 作用是:查看资源、查看调用链、直接为资源创建规则。注意:只有访问过的资源才会出现在簇点链路中
流控规则 集中查看、修改和删除所有流控规则。在簇点链路点击:+流控 创建完成后,就会出现在这里。
熔断规则 集中管理熔断降级规则。
热点规则 集中管理热点参数限流规则。
系统规则 从整个应用层面保护系统,而不是只保护某个接口。可以根据这些指标进行保护:、系统负载、CPU使用率、平均响应时间、入口QPS、最大并发线程数、
授权规则 管理黑名单、白名单规则。
集群流控 用于多个微服务实例之间统一计算流量。普通单机流控是:每个实例分别计算自己的QPS。集群流控是:多个实例共享一个总阈值
机器列表 查看当前应用连接到 Sentinel Dashboard 的所有实例,例如:、IP地址、Sentinel端口、心跳时间、健康状态、版本信息

操作按钮:就是为该资源添加不用的规则,+流控规则、 +熔断规则、 +热点规则、 +授权规则。

下面测试一下流控规则:

测试:快速点击 http://localhost:8000/order/normal/1 这个接口,让接口每秒超过1次请求。

异常处理

sentinel的异常类是BlockException,有多个子类,不同的异常子类对应不同规则,结构如下:

text 复制代码
BlockException                   (被流控/熔断/降级的父类)
├─ FlowException                  // 【流控异常】触发了 QPS 或线程数限流规则
├─ ParamFlowException             // 【热点参数异常】触发了热点参数限流规则
├─ DegradeException               // 【熔断降级异常】(旧版) 触发了平均RT、异常比例或异常数降级规则
├─ SystemBlockException           // 【系统保护异常】触发了系统自适应限流(如 Load 过高、CPU 使用率过高、入口 QPS 过高等)
├─ AuthorityException             // 【授权异常】触发了黑白名单规则(来源访问权限被拒绝)
└─ CircuitBreakerException        // 【断路器异常】(新版本 1.8.0+) 触发了新版熔断规则(慢调用比例或异常比例)

web接口异常处理

对于web接口,我们给用户返回默认的异常处理肯定是不有好的:

因此我们可以自定义一下,返回友好的信息:

java 复制代码
package com.order.exception;

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;

@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override

    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = httpServletResponse.getWriter();
        HashMap<String, Object> result = new HashMap<>();
        String resourceName = httpServletRequest.getRequestURI();

        String message;
        if (e instanceof FlowException) {
            message = "请求过于频繁被限流,请稍后重试";              // 触发流量控制
        } else if (e instanceof DegradeException) {
            message = "服务暂时不可用,请稍后重试";          // 触发熔断降级
        } else if (e instanceof ParamFlowException) {
            message = "热点参数访问过于频繁";                // 触发热点参数限流
        } else if (e instanceof AuthorityException) {
            message = "当前请求没有访问权限";               // 触发授权规则
        } else if (e instanceof SystemBlockException) {
            message = "系统负载过高,请稍后重试";            // 触发系统保护
        } else {
            message = "请求被 Sentinel 拦截";
        }

        result.put("code", 10086);
        result.put("msg", message);
        result.put("resourceName", resourceName);

        String json = objectMapper.writeValueAsString(result);

        writer.write(json);
        writer.flush();
        writer.close();
    }
}

@SentinelResource异常处理

使用@SentinelResource注解指定一个资源,当该资源出现异常应该怎么处理?

java 复制代码
/**
 * 正常远程调用。
 */
@SentinelResource(value = "getNormalOrder", blockHandler = "getNormalOrderFallback")
@Override
public OrderInfo getNormalOrder(Long productId) {

    // 表面是Java方法调用,实际由Feign代理对象转换成HTTP请求
    Product product = productFeignClient.getProductById(productId);

    return buildOrder(productId, product);
}


// 兜底回调
public OrderInfo getNormalOrderFallback(Long productId, BlockException e) {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderId(0L);
    orderInfo.setOrderName("未知用户订单,异常信息:" + e.toString());
    return orderInfo;
}

@SentinelResource(value = "资源名称", blockHandler = "兜底方法"),如果出发规则则执行兜底方法。

按照上面web接口的测试方法,设置流控规则,每秒钟qps为1个。结果如下:

openfeign的异常

java 复制代码
@FeignClient(
        name = "cloud-product", // 根据服务名称从Nacos获取商品服务实例
        configuration = OpenFeignConfig.class, // 加载重试器和请求拦截器
        fallbackFactory = ProductFeignFallbackFactory.class // 最终调用失败时执行兜底类,可以获得具体失败原因
)
public interface ProductFeignClient {
    /**
     * 正常调用:
     * 用于测试远程调用、负载均衡、日志和拦截器。
     */
    @GetMapping("/product/{id}")
    Product getProductById(@PathVariable("id") Long id);
}

=====================================================================================================
package com.order.feign.fallback;

import com.order.domain.Product;
import com.order.feign.ProductFeignClient;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;

/**
 * 商品服务OpenFeign兜底工厂。
 *
 * 当商品服务发生连接超时、读取超时、服务不可用、
 * HTTP异常、Sentinel限流或Sentinel熔断时,创建兜底实现对象。
 *
 * 相比普通fallback,fallbackFactory可以获得具体的异常原因。
 */
@Component
public class ProductFeignFallbackFactory
        implements FallbackFactory<ProductFeignClient> {

    @Override
    public ProductFeignClient create(Throwable cause) {

        return new ProductFeignClient() {

            @Override
            public Product getProductById(Long id) {
//                return createFallbackProduct(id, "商品服务调用失败:" + cause.getMessage());
                return createFallbackProduct(id, "商品服务调用失败:" + getErrorMessage(cause));
            }

            @Override
            public Product getSlowProductById(Long id) {
//                return createFallbackProduct(id, "商品服务调用超时:" + cause.getMessage());
                return createFallbackProduct(id, "商品服务调用超时:" + getErrorMessage(cause));
            }

            @Override
            public Product getErrorProductById(Long id) {
//                return createFallbackProduct(id, "商品服务发生异常:" + cause.getMessage());
                return createFallbackProduct(id, "商品服务发生异常:" + getErrorMessage(cause));
            }
        };
    }


    /**
     * 创建统一的商品兜底对象。
     */
    private Product createFallbackProduct(
            Long id,
            String message) {

        Product product = new Product();

        product.setId(id);
        product.setProductName("默认商品");
        product.setPrice(null);
        product.setServerPort("fallback");
        product.setSourceService("cloud-order");
        product.setInternalToken(null);
        product.setFallback(true);
        product.setMessage(message);

        return product;
    }

    /**
     * 获取简化后的异常信息。
     */
    private String getErrorMessage(Throwable cause) {

        if (cause == null) {
            return "未知异常";
        }

        if (cause.getMessage() == null) {
            return cause.getClass().getSimpleName();
        }

        return cause.getMessage();
    }
}

SphU硬编码方式处理异常

SphU包含了 try-catch 风格的 API。用这种方式,当资源发生了限流之后会抛出 。这个时候可以捕捉异常,进行限流之后的逻辑处理。示例代码如下:BlockException

java 复制代码
// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
  // 被保护的业务逻辑
  // do something here...
} catch (BlockException ex) {
  // 资源访问阻止,被限流或被降级
  // 在此处进行相应的处理操作
}

注意,若 entry 的时候传入了热点参数,那么 exit 的时候也一定要带上对应的参数(exit(count, args)),否则可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外通过 Tracer.trace(ex) 来统计异常信息时,由于 try-with-resources 语法中 catch 调用顺序的问题,会导致无法正确统计异常数,因此统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。

手动 exit 示例:

java 复制代码
Entry entry = null;
// 务必保证 finally 会被执行
try {
  // 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
  // EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
  entry = SphU.entry("自定义资源名");
  // 被保护的业务逻辑
  // do something...
} catch (BlockException ex) {
  // 资源访问阻止,被限流或被降级
  // 进行相应的处理操作
} catch (Exception ex) {
  // 若需要配置降级规则,需要通过这种方式记录业务异常
  Tracer.traceEntry(ex, entry);
} finally {
  // 务必保证 exit,务必保证每个 entry 与 exit 配对
  if (entry != null) {
    entry.exit();
  }
}

热点参数埋点示例:

java 复制代码
Entry entry = null;
try {
    // 若需要配置例外项,则传入的参数只支持基本类型。
    // EntryType 代表流量类型,其中系统规则只对 IN 类型的埋点生效
    // count 大多数情况都填 1,代表统计为一次调用。
    entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
    // Your logic here.
} catch (BlockException ex) {
    // Handle request rejection.
} finally {
    // 注意:exit 的时候也一定要带上对应的参数,否则可能会有统计错误。
    if (entry != null) {
        entry.exit(1, paramA, paramB);
    }
}

SphU.entry() 的参数描述:

参数名 类型 解释 默认值
entryType EntryType 资源调用的流量类型,是入口流量()还是出口流量(),注意系统规则只对 IN 生效 EntryType.IN EntryType.OUT EntryType.OUT
count int 本次资源调用请求的 token 数目 1
args Object\[\] 传入的参数,用于热点参数限流

注意: 需要与 方法成对出现,匹配调用,否则会导致调用链记录异常,抛出 异常。常见的错误:SphU.entry(xxx) entry.exit() ErrorEntryFreeException

  • 自定义埋点只调用 ,没有调用 SphU.entry() entry.exit()
  • 顺序错误,比如:,应该为 entry1 → entry2 → exit1 → exit2 entry1 → entry2 → exit2 → exit1

流控规则

只有请求过得接口,才会在簇点链路中显现出来

阈值类型

QPS

限流单位是"每秒允许通过的请求数量"。它是衡量系统处理能力最直接的指标。

如果你设置了 QPS 阈值为 10。那么在这个接口上,1秒钟内最多允许进来 10 个请求。如果在短短一秒内来了第 11 个请求,这个请求就会被 Sentinel 立即拦截(抛出 FlowException),返回被限流的提示。

适用场景:绝大多数互联网高并发业务。例如秒杀抢购、商品详情页查询、下单接口等。它用来防止突发的流量洪峰把数据库或服务器冲垮。

QPS底层原理

Sentinel 不是简单地在每个整秒把数字清零,而是使用了滑动时间窗口。

text 复制代码
例如当前时间是:12:00:00.700
Sentinel统计的可能是:11:59:59.700 ~ 12:00:00.700
	↓
过了 100 毫秒,当前时间变成:12:00:00.800
统计范围也向前移动:11:59:59.800 ~ 12:00:00.800

因此它叫:滑动时间窗口

可以把最近一秒切成几个小格子:最近1秒:

text 复制代码
┌────────┬────────┬────────┬────────┐
│ 250ms  │ 250ms  │ 250ms  │ 250ms  │
└────────┴────────┴────────┴────────┘
    1次       0次       1次       0次

Sentinel把这些小时间段中的请求数量加起来:1 + 0 + 1 + 0 = 2。得到最近一秒的通过数量。

随着时间前进,过期的小格子会被丢掉,新格子会加入:

text 复制代码
旧窗口:
[A][B][C][D]

时间继续向前:
[B][C][D][E]

这样统计会比"每到整秒清零"更准确。

并发线程数

限流单位是"当前正在处理的线程数量"。它关注的是系统同时能支撑多少个任务并行处理,不关心任务执行得快还是慢。

如果你设置了并发线程数阈值为 20。

  • 如果这个接口处理起来特别慢(比如需要 5 秒才能返回数据库结果),只要当前有 20 个线程正在处理这个接口请求,第 21 个进来的请求就会立刻被拦截(抛出 FlowException),而不是排队等待。

适用场景:耗时的、阻塞性强的接口。例如复杂的报表计算、大量数据的导出、依赖外部第三方 API 且响应慢的接口。

并发线程数的底层原理

假设配置:

text 复制代码
阈值类型:并发线程数
单机阈值:2

意思是:当前最多允许 2 个请求同时执行这个资源。

Sentinel会为这个资源维护一个类似的数字:

text 复制代码
当前正在执行的请求数:0

注意,这里不是统计整个 JVM 的线程总数,也不是 Tomcat 的全部线程数。

它统计的是:当前正在执行 /order/normal/{productId} 这个 Sentinel 资源的请求数量。

流控模式-直接

直接是最普通、最常用的模式。它的意思是:当前资源超过自己的阈值,就限制当前资源。 当前资源是:/order/normal/{productId}

Sentinel 检查:/order/normal/{productId}最近一秒通过了多少个请求,允许一秒钟有两个请求。执行过程:

text 复制代码
第1个请求 → 当前资源QPS未超过2 → 放行
第2个请求 → 当前资源QPS未超过2 → 放行
第3个请求 → 当前资源QPS达到2 → 限流

流控模式-关联

关联模式的意思是:当前资源是否被限流,不看自己的流量,而是看另一个关联资源的流量。 假设有两个资源:

text 复制代码
资源A:查询订单
资源B:修改订单

它们都要访问同一张订单表。现在希望:修改订单非常繁忙时,暂时限制查询订单,给修改业务腾出数据库资源。 可以这样设置:

最容易弄错的一点是:

text 复制代码
被限制的是:资源A
检查依据是:资源B

这条规则不会直接限制 B,而是用 B 的流量决定是否限制 A。

流控模式-链路

使用链路模式,必须在配置文件添加下面配置:

yml 复制代码
spring:
  cloud:
    sentinel:
      web-context-unify: false # 关闭 Sentinel 对 Web 上下文(Context)的默认整合。配置目的:让"链路"流控模式生效

Sentinel 默认会收敛所有 URL 的入口 Context,将它们统一为同一个入口(例如 sentinel_web_servlet_context)。这意味着,所有通过 Web 请求产生的调用,在 Sentinel 的调用链树中都属于同一个父节点。在这种"整合"状态下,链路模式(只统计来自特定入口的请求)的流控规则无法生效,因为系统无法区分不同的调用来源。

将 web-context-unify 设置为 false 后,Sentinel 会根据不同的 URL 路径来创建不同的入口 Context。这样一来,调用链路树就能清晰地分出不同的入口,链路模式的流控规则就可以正常工作了。

链路模式用于解决:同一个资源被多个入口调用,只想限制其中一条调用链。

例如有一个公共 Service 方法:

java 复制代码
@SentinelResource("queryProduct")
public Product queryProduct(Long id) {
    return productMapper.selectById(id);
}

A、B两个 Controller 都调用它,同时希望queryProduct的QPS每秒钟超过两个时A限流,B不限流:

text 复制代码
	A      B
    │      │
    ↓      ↓
  queryProduct 达到阈值限流A,B正常访问

链路模式不是简单限制整个 queryProduct,而是限制:某个入口 → queryProduct这一条具体调用路径。

三个模式可以这样记:

  • 直接: 看自己,限制自己
  • 关联: 看别人,限制自己
  • 链路: 只限制从某个入口过来的自己

流控效果

流控模式 快速失败 Warm Up 排队等待
直接 ✅ 支持 ✅ 支持,但必须是 QPS ✅ 支持,但必须是 QPS
关联 ✅ 支持 ❌ 不支持 ❌ 不支持
链路 ✅ 支持 ❌ 不支持 ❌ 不支持

流控效果-快速失败

快速失败是默认方式

假设:QPS阈值 = 2。一秒内来了五个请求:

text 复制代码
第1个 → 通过
第2个 → 通过
第3个 → 立即拒绝
第4个 → 立即拒绝
第5个 → 立即拒绝

被拒绝的请求:、不会等待、不会进入Controller、不会执行OpenFeign调用,而是直接返回兜底方法:

text 复制代码
抛出FlowException
       ↓
进入MyBlockExceptionHandler
       ↓
返回自定义JSON

流控效果-Warm Up

Warm Up 是预热、冷启动模式。

它解决的问题是:系统长时间没有流量,突然一下进来大量请求,系统可能还没有准备好。

例如系统刚启动时:

text 复制代码
缓存还没有加载
数据库连接池还没有充分建立连接
JVM代码还没有充分预热
各种对象还没有初始化完成

假设最终阈值是:10 QPS,快速失败模式下,相当于系统马上允许接收最高约 10 QPS。Warm Up 则是:

text 复制代码
刚开始:只允许较少请求通过
        ↓
随着预热时间推进
        ↓
允许通过的QPS逐渐增加
        ↓
最终达到10 QPS

例如预热时间设置为 10 秒,可以大致理解成:

text 复制代码
刚启动       → 只放少量请求
运行几秒后   → 放行能力逐渐增加
10秒之后     → 达到配置的完整阈值

它不是说 10 秒内完全不接收请求,而是:先用较低速度接收请求,再逐渐提高到完整阈值。

流控效果-排队等待

排队等待不会马上拒绝所有超额请求,而是:让请求按照比较均匀的速度依次通过。

假设设置:QPS阈值 = 2,意味着平均放行间隔大约为:1000毫秒 ÷ 2 = 500毫秒

大量请求同时到达:

text 复制代码
请求A ── 立即通过
请求B ── 等待约500ms后通过
请求C ── 再等待约500ms后通过
请求D ── 继续等待

原本的突发流量,同一时刻:A B C D E 全部到达,经过排队等待后变成:

text 复制代码
0ms       → A通过
500ms     → B通过
1000ms    → C通过
1500ms    → D通过

这就是流量整形,把不均匀的流量变得比较平稳。但排队不是无限等待,还需要设置:超时时间 例如最大排队时间是:1000ms,某个请求计算后需要等待 1500ms,超过最大等待时间,就会被拒绝:

text 复制代码
预计等待1500ms
      ↓
最大只允许等待1000ms
      ↓
抛出FlowException

Sentinel 的排队等待采用匀速放行方式,官方文档将其描述为漏桶式流量整形,用于把突发请求按照稳定速率处理。

熔断规则

熔断规则是 Sentinel 用来监控资源运行状态,并在资源出现大量慢调用或异常时,暂时阻止请求继续访问该资源的一种保护规则。

它主要用于防止故障服务持续消耗线程、连接、CPU、内存等系统资源,避免局部故障沿着微服务调用链不断扩散,最终导致系统雪崩。

Sentinel 支持的熔断策略

  • 慢调用比例
    根据请求响应时间判断是否属于慢调用。当统计窗口内的慢调用比例达到设定阈值,并且请求数量达到最小请求数时,触发熔断。
  • 异常比例
    根据异常请求占总请求数的比例判断。当统计窗口内的异常比例达到设定阈值,并且请求数量达到最小请求数时,触发熔断。
  • 异常数
    根据统计窗口内出现的异常总数判断。当异常数量达到设定阈值,并且请求数量满足条件时,触发熔断。

断路器原理

断路器内部维护三种状态

text 复制代码
CLOSED      关闭状态
OPEN        打开状态
HALF_OPEN   半开状态

状态变化完整流程:
                        请求正常通过
                              ↓
                    ┌─────────────────┐
                    │     CLOSED      │
                    │   正常统计状态   │
                    └────────┬────────┘
                             │
                  慢调用或异常达到阈值
                             │
                             ↓
                    ┌─────────────────┐
                    │      OPEN       │
                    │   请求直接拒绝   │
                    └────────┬────────┘
                             │
                        熔断时长结束
                             │
                             ↓
                    ┌─────────────────┐
                    │   HALF_OPEN     │
                    │  放行一个探测请求 │
                    └──────┬──────┬───┘
                           │      │
                       探测成功  探测失败
                           │      │
                           ↓      ↓
                       CLOSED    OPEN
  1. 正常状态下,断路器处于关闭状态 CLOSED。
    请求可以正常进入业务方法或调用下游服务。Sentinel 会持续统计资源的响应时间、慢调用比例、异常比例和异常数量,用于判断服务是否稳定。
  2. 达到熔断阈值后,断路器切换为打开状态 OPEN。
    当统计时间窗口内的请求数量达到要求,并且慢调用比例、异常比例或异常数量超过设定阈值时,Sentinel 会打开断路器,暂时切断对该资源的调用。
  3. 断路器打开后,请求快速失败并返回兜底结果。
    在熔断时间内,后续请求不会再执行原来的业务逻辑,也不会继续调用故障服务,而是直接被 Sentinel 拦截,再通过 Fallback 或异常处理返回友好提示。
  4. 熔断时间结束后,断路器进入半开状态 HALF-OPEN。
    Sentinel 会放行少量探测请求,用于判断服务是否已经恢复。此时不会立即恢复全部流量,避免尚未恢复的服务再次被大量请求压垮。
  5. 根据探测结果决定恢复还是继续熔断。
    如果探测请求成功,断路器重新切换为 CLOSED,恢复正常调用;如果探测请求仍然异常或响应过慢,断路器重新进入 OPEN,继续等待下一次探测。

定义慢调用比例规则

熔断策略:慢调用比例(当请求的响应时间超过"最大 RT"阈值时,视为慢调用)

  • 最大 RT:800 ms(响应时间超过 800ms 即被计为慢调用)
  • 熔断时长:30 s(熔断开启后,拒绝请求的时间为 30 秒)
  • 统计时长:5000 ms(统计窗口期为 5 秒,在此时间内采集指标)
  • 比例阈值:0.5(即 50%)(当慢调用比例达到 50% 时触发熔断)
  • 最小请求数:10(统计时长内请求数至少达到 10 次,才会计算比例并触发熔断)

统计 5 秒钟内,如果请求数达到 10 个,且其中有 50%(即 5 个) 的请求响应时间超过 800 ms,则触发熔断。

熔断开启后,后续请求将被直接拒绝,持续 30 秒。

30 秒过后,熔断器进入 半开状态 ,允许少量请求通过试水。如果此时请求仍然大量超时或异常,则熔断器会再次打开,重新计时 30 秒;如果请求恢复正常,则熔断器 关闭,恢复正常流量接入。

定义异常比例规则

  • 比例阈值:0.2(即 20%)(当异常比例达到 20% 时触发熔断)
  • 熔断时长:30 s(熔断开启后,拒绝请求的时间为 30 秒)
  • 统计时长:5000 ms(统计窗口期为 5 秒,在此时间内采集指标)
  • 最小请求数:10(统计时长内请求数至少达到 10 次,才会计算比例并触发熔断)

统计 5 秒钟内,如果请求数达到 10 个,且其中异常请求(如抛异常、超时等)的比例达到 20%,则触发熔断。

熔断开启后,后续请求将被直接拒绝,持续 30 秒。

30 秒过后,熔断器进入 半开状态,允许少量请求通过试水。如果此时请求仍然大量异常,则熔断器会再次打开,重新计时 30 秒;如果请求恢复正常,则熔断器 关闭,恢复正常流量接入。

定义异常数规则

  • 异常数:2(统计时长内,异常请求总数达到 2 次时触发熔断)
  • 熔断时长:30 s(熔断开启后,拒绝请求的时间为 30 秒)
  • 统计时长:5000 ms(统计窗口期为 5 秒,在此时间内采集指标)
  • 最小请求数:10(统计时长内请求数至少达到 10 次,才会计算并触发熔断)

统计 5 秒钟内,如果请求数达到 10 个,且其中异常请求(如抛异常、超时等)的总数达到 2 次,则触发熔断。

熔断开启后,后续请求将被直接拒绝,持续 30 秒。

30 秒过后,熔断器进入 半开状态 ,允许少量请求通过试水。如果此时请求仍然大量异常,则熔断器会再次打开,重新计时 30 秒;如果请求恢复正常,则熔断器 关闭,恢复正常流量接入。

blockHandler 、fallback

当sentinel的流控规则和熔断规则触发后就会进入blockHandler或者是fallback。

它们的区别是blockHandler只能处理sentinel的异常,也就是:

text 复制代码
BlockException                   (被流控/熔断/降级的父类)
├─ FlowException                  // 【流控异常】触发了 QPS 或线程数限流规则
├─ ParamFlowException             // 【热点参数异常】触发了热点参数限流规则
├─ DegradeException               // 【熔断降级异常】(旧版) 触发了平均RT、异常比例或异常数降级规则
├─ SystemBlockException           // 【系统保护异常】触发了系统自适应限流(如 Load 过高、CPU 使用率过高、入口 QPS 过高等)
├─ AuthorityException             // 【授权异常】触发了黑白名单规则(来源访问权限被拒绝)
└─ CircuitBreakerException        // 【断路器异常】(新版本 1.8.0+) 触发了新版熔断规则(慢调用比例或异常比例)

fallback 默认可以处理所有未被 exceptionsToIgnore 排除的异常比如:Throwable的所有子类异常。

但是他们的用法是一样的:

java 复制代码
================
blockHandler 
================
/**
 * 正常远程调用。
 */
@SentinelResource(value = "getNormalOrder", blockHandler = "getNormalOrderFallback")
@Override
public OrderInfo getNormalOrder(Long productId) {

    // 表面是Java方法调用,实际由Feign代理对象转换成HTTP请求
    Product product = productFeignClient.getProductById(productId);

    return buildOrder(productId, product);
}


// 兜底回调
public OrderInfo getNormalOrderFallback(Long productId, BlockException e) {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderId(0L);
    orderInfo.setOrderName("未知用户订单,异常信息:" + e.toString());
    return orderInfo;
}

================
fallback
================
/**
 * 正常远程调用。
 */
@SentinelResource(value = "getNormalOrder", fallback = "getNormalOrderFallback")
@Override
public OrderInfo getNormalOrder(Long productId) {
    int i = 10/0;   // 抛出业务异常
    
    return buildOrder(productId, product);
}


// 兜底回调
public OrderInfo getNormalOrderFallback(Long productId, Throwable e) {
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setOrderId(0L);
    orderInfo.setOrderName("未知用户订单,异常信息:" + e.toString());
    return orderInfo;
}
相关推荐
夜月yeyue2 小时前
AUTOSAR CP 从上电到 Runnable
c语言·网络·tcp/ip·车载系统
lsh曙光2 小时前
延时at指令和定时cron指令
linux·服务器·网络
treesforest2 小时前
IP定位技术在网络犯罪侦查中的应用与价值
网络·网络协议·tcp/ip·网络安全·ip归属地查询·反欺诈
天桥下的卖艺者3 小时前
使用scitable包,两步生成逆概率删失权重(IPCW)
数据库·r语言
XR1234567883 小时前
企业全光网络架构选型技术白皮书:从物理层到运维层的全链路分析
运维·网络·架构
隔窗听雨眠3 小时前
AI原生数据库浪潮:国产数据库的架构重构与路径之争
数据库
数据库小学妹3 小时前
数据库选型实战:从数据类型到TCO成本,五维决策框架+九款产品横评
数据库·信创·国产数据库·数据库选型·oracle迁移
萧瑟余晖4 小时前
Java深入解析篇九之NIO详解
java·网络·nio
神龙天舞20014 小时前
MySQL 备库为什么会延迟好几个小时
android·数据库·mysql
小王C语言4 小时前
【7. 实现登录注册模块】:实现会话管理、注册/登录/退出 API
网络·c++