目录

一、Sentinel 入门基础

1. Sentinel 介绍

2. Sentinel 基本概念

3. Sentinel核心功能

 4. Sentinel 是如何工作的

二、Sentinel 实战 

1. Sentinel 控制台下载和启动

2. 启动 nacos

3. Spring boot 集成

三、Sentinel 控制台配置详情

 1. 实时监控

2. 族点链路

 3.流控规则

3.1 阀值类型

 QPS

并发线程数

3.2 针对来源

3.3 流控模式

关联

链路

3.4 流控效果

快速失败(直接拒绝)

Warm Up(预热)

匀速排队(排队等待)

4. 熔断规则

4.1 熔断策略-慢调用比例

4.2 熔断策略-异常比例

 4.3 熔断策略-异常数

5. 热点规则

6. 系统规则

7. 授权控制规则

四、Sentinel 规则持久化

从 Nacos 配置中心中获取配置

将Sentinel配置的规则推送到Nacos中

五、RestTemplate 整合 Sentinel

六、OpenFeign 整合 Sentinel

 七、Gateway 整合 Sentinel


一、Sentinel 入门基础

1. Sentinel 介绍

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel 官方网站:introduction | Sentinel

Sentinel Demohttps://gitee.com/original-intention/sentinel-gorgor-demo 

2. Sentinel 基本概念

  • 资源

        资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。

        只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

  • 规则

        围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整

3. Sentinel核心功能

功能 作用 应用场景
流量控制 限制QPS/线程数,防止突发流量击垮服务 秒杀活动、API防刷
熔断降级 自动阻断不稳定资源(如响应慢/高异常),避免级联故障 依赖服务超时、数据库访问异常
系统保护 全局维度的负载控制(CPU/线程数/总QPS) 服务器资源过载预警
热点参数限流 针对特定参数(如用户ID)精细化控制流量 热门商品查询、高频用户限制

 4. Sentinel 是如何工作的

Sentinel 的主要工作机制如下:

  • 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
  • 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
  • Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。

其核心工作机制基于责任链模式(Slot Chain)实现多维度流量控制、熔断降级和系统保护。以下是其主要工作机制的详细解析:

核心处理流程:Slot Chain 责任链

当资源(如方法、接口)被访问时,Sentinel 会触发一条由多个 ProcessorSlot 组成的处理链,按固定顺序执行以下核心槽位(Slot):

槽位 职责 关键技术
NodeSelectorSlot 构建资源调用路径树,区分不同入口(Context)的流量 树状结构(EntranceNode/DefaultNode)
ClusterBuilderSlot 创建全局资源节点(ClusterNode)和来源节点(OriginNode) 多维度统计(资源总流量、调用来源)
StatisticSlot 实时统计运行时指标(QPS、RT、线程数、异常率) 滑动窗口(LeapArray)、高性能内存计算
SystemSlot 检查系统负载(CPU、LOAD、入口QPS) 动态阈值调整(仅对入口流量生效)
AuthoritySlot 黑白名单控制(基于调用来源origin) 字符串匹配规则
FlowSlot 流量控制(QPS/线程数限流) 滑动窗口、令牌桶/漏桶算法
DegradeSlot 熔断降级(慢调用/异常比例/异常数) 状态机(OPEN/HALF_OPEN/CLOSED)
LogSlot 记录拦截日志 异常事件输出

控制台设置配置,服务接口配置的核心类:com.alibaba.csp.sentinel.command.handler.ModifyRulesCommandHandler

Spring Mvc 接口请求,触发限流降级的核心类:

com.alibaba.csp.sentinel.adapter.spring.webmvc.AbstractSentinelInterceptor

二、Sentinel 实战 

1. Sentinel 控制台下载和启动

Sentinel 下载地址

sentinel-dashboard-1.8.6 下载地址

 启动命令

在 Cmd 中执行Sentinel jar 启动控制台

java -Dserver.port=8889 -Dcsp.sentinel.dashboard.server=localhost:8889 -Dproject.name=sentinel-dashboard-1.8.6 -jar sentinel-dashboard-1.8.6.jar

访问控制台

http://localhost:8889/

2. 启动 nacos

下载地址:nacos-server-2.2.0

 在 bin 目录下启动 nacos命令

startup.cmd

3. Spring boot 集成

引入依赖

        <!-- sentinel 依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

配置控制台参数

spring:
  application:
    name: order-server

  cloud:
    sentinel:
      # 将其配置为 false 即可根据不同的 URL 进行链路限流
      web-context-unify: false
      transport:
        # 添加sentinel的控制台地址
        dashboard: localhost:8889
        # 指定应用与Sentinel控制台交互的端口,应用会起一个HttpServer占用该端口
        port: 8719

 与 Spring boot 集成完毕,此时我们去 Sentinel 配置限流降级策略就可以。但有个要点要注意,

微服务 与 Sentinel 控制台的连接是在首次请求触发资源访问时建立的,而非应用启动时立即建立。这是 Sentinel 的懒加载机制设计。

连接建立流程

资源初始化入口 核心类:com.alibaba.csp.sentinel.Env

所以,我们如果想要配置服务下某个接口的限流降级策略,则需要先访问任意一个接口,然后才可以去配置。

三、Sentinel 控制台配置详情

 1. 实时监控

监控接口的通过的QPS 和拒绝的QPS

 注意:请确保 Sentinel 控制台所在的机器时间与应用的机器时间保持一致,否则会导致拉不到实时的监控数据。

还有一个要注意的,实时监控仅存储5分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。

2. 族点链路

用来显示微服务所监控的 API,我们可以在这里配置流控、熔断,热点和授权规则。

访问 http://localhost:8060/order/getOrder?userId=gorgor 

 3.流控规则

一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型,QPS 或线程数
  • strategy: 根据调用关系选择策略

3.1 阀值类型

基于QPS/并发数的流量控制

流量控制主要有两种统计类型,一种是统计线程数,另外一种则是统计 QPS。类型由 FlowRule.grade 字段来定义。其中,0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制。其中线程数、QPS 值,都是由 StatisticSlot 实时统计获取的。

  •  QPS

配置流控规则

请求接口

http://localhost:8060/order/getOrder?userId=gorgor

 流控效果

默认流控提示信息类为:com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.DefaultBlockExceptionHandler

调用的地方是在 spring mvc 接口资源限流入口 HandlerInterceptor 的 AbstractSentinelInterceptor 的 preHandle 方法中,对异常的处理是 BlockExceptionHandler

如果需要更改流控的提示信息,则需要自己实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler 接口。

自定义 BlockExceptionHandler 的实现类统一处理 BlockException

/**
 * 自定义限流异常处理,替换默认的异常处理类 DefaultBlockExceptionHandler
 */
@Slf4j
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {


    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        log.info("BlockExceptionHandler BlockException================"+e.getRule());

        Result r = null;

        if (e instanceof FlowException) {
            r = Result.failed("接口限流了");

        } else if (e instanceof DegradeException) {
            r = Result.failed("服务降级了");

        } else if (e instanceof ParamFlowException) {
            r = Result.failed("热点参数限流了");

        } else if (e instanceof SystemBlockException) {
            r = Result.failed("触发系统保护规则了");

        } else if (e instanceof AuthorityException) {
            r = Result.failed("授权规则不通过");
        }

        //返回json数据
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getWriter(), r);

    }
}

 重启服务后测试流控效果。

  • 并发线程数

并发线程数控制用于保护业务线程池不被慢调用耗尽,简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阀值,新的请求会被立即拒绝,效果类似于信号量隔离。

配置流控规则

测试效果

  测试接口

http://localhost:8060/order/getOrder?userId=gorgor

使用压测工具 jmeter 进行压测

线程组配置

 流控效果

3.2 针对来源

基于调用针对来源关系的流量控制,根据调用方限流,origin 参数表明了调用方身份。默认是没有实现来源数据的,需要实现 RequestOriginParser 接口,如下:

@Component
public class MyRequestOriginParser implements RequestOriginParser {
    /**
     * 通过request获取来源标识,交给授权规则进行匹配
     * @param request
     * @return
     */
    public String parseOrigin(HttpServletRequest request) {
        // 标识字段名称可以自定义
        String origin = request.getParameter("userId");
//        if (StringUtil.isBlank(origin)){
//            throw new IllegalArgumentException("userId参数未指定");
//        }
        return origin;
    }
}

配置流控规则

流控效果

接口: http://localhost:8060/order/getOrder?userId=gorgor 会被流控

 接口: http://localhost:8060/order/getOrder?userId=jj不会被流控

 可以通过一下命令来展示不用的调用方对同一个资源的调用数据

http://localhost:8721/origin?id=/order/getOrder

3.3 流控模式

  • 关联

        基于关联调用关系的流量控制,可以简单理解为,当关联资源A达到阀值的时候,对资源B进行流控。使用关联限流来避免具有关联关系的资源之间过度的争抢,比如当写库操作过于频繁时,可以配置一个法治,读数据的请求会被限流。

配置流控规则

测试效果 

使用压测工具 jmeter 压测 http://localhost:8060/order/getRedOrderById/1 接口,

线程组配置

然后访问另外一个接口   http://localhost:8060/order/getOrder?userId=gorgor ,接口会被流控。

  • 链路

        基于链路调用关系的流量控制,可以简单理解为,资源A和B都会调用C,设置入口资源是A,当C达到阀值时,对入口资源A进行流控,而不会对B进行流控。

环境准备

OrderServicelmpl#getOrderByld 方法添加注解@SentinelResource进行资源保护

@SentinelResource(value = "getOrderById",blockHandler = "handleException")
    @Override
    public Result<?> getOrderById(Integer id) {

        Order order = orderMapper.getOrderById(id);
        return Result.success(order);
    }

    public Result handleException(Integer id, BlockException ex) {
        return  Result.failed("===被限流降级啦===");
    }

参考OrderController#getOrderByld方法 新增TestController测试类

@RestController
public class TestController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/test1/{id}")
    public Result<?> test1(@PathVariable("id") Integer id){

        Result<?> res = null;
        try {
            res = orderService.getOrderById(id);
        }
        catch (BusinessException e) {
            return Result.failed(e.getMessage());
        }
        return res;
    }

    @GetMapping("/test2/{id}")
    public Result<?> test2(@PathVariable("id") Integer id){

        Result<?> res = null;
        try {
            res = orderService.getOrderById(id);
        }
        catch (BusinessException e) {
            return Result.failed(e.getMessage());
        }
        return res;
    }

}

配置流控规则

 测试效果

  • http://localhost:8060/test2/1 会被流控
  • http://localhost:8060/test1/1 不会被流控

3.4 流控效果

  • 快速失败(直接拒绝)

        达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

  • Warm Up(预热)

预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold/3 开始,经预热时长逐渐升至设定的 QPS 阈值。

配置流控规则

测试效果

 测试接口

http://localhost:8060/order/getOrder?userId=gorgor

使用压测工具 jmeter 进行压测

线程组配置

 

流控效果

  • 匀速排队(排队等待)

        所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长,请求的等待时间不能超过下面配置的超时时间,比如下面配置的单机阀值是 10,代表1秒钟只能通过10个请求,超过了则会被限流,然后10个请求会放到队列中,每隔 100 ms 执行一个请求,1秒内如果在队列中的请求超过500ms ,则同时也会被限流。

配置流控规则

测试效果

使用压测工具 jmeter 进行压测

测试接口

http://localhost:8060/order/getOrder?userId=gorgor

线程组配置

流控效果

4. 熔断规则

对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。

例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。此时,可以使用熔断降级机制,保护服务。

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

4.1 熔断策略-慢调用比例

当单位统计时长(statntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。

环境准备

添加测试代码

@RequestMapping("/test")
public String test() {
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "========test()========";
}

查询接口

http://localhost:8060/test

 配置降级规则

测试效果

使用压测工具 jmeter 进行压测

线程组配置

查看实时监控,可以看到断路器熔断效果

此时浏览器访问会出现服务降级结果 

4.2 熔断策略-异常比例

当单位统计时长(statintervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。

 环境准备

添加测试代码

AtomicInteger atomicInteger = new AtomicInteger(0);
    @RequestMapping("/test2")
    public String test222() {
        atomicInteger.getAndIncrement();
        if (atomicInteger.get() % 2 == 0){
            //模拟异常和异常比率
            int i = 1/0;
        }

        return "========test2()========";
    }

查询接口

http://localhost:8060/test2

 配置降级规则

测试效果

使用压测工具 jmeter 进行压测

线程组配置

查看实时监控,可以看到断路器熔断效果

 4.3 熔断策略-异常数

当单位统计时长内的异常数目超过阈值之后会自动进行熔断。

 环境准备

添加测试代码

AtomicInteger atomicInteger = new AtomicInteger(0);
    @RequestMapping("/test2")
    public String test222() {
        atomicInteger.getAndIncrement();
        if (atomicInteger.get() % 2 == 0){
            //模拟异常和异常比率
            int i = 1/0;
        }

        return "========test2()========";
    }

查询接口

http://localhost:8060/test2

 配置降级规则

测试效果

使用压测工具 jmeter 进行压测

线程组配置

查看实时监控,可以看到断路器熔断效果

5. 热点规则

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。

注意:

  1. 热点规则需要使用@SentinelResource("resourceName”)注解,否则不生效
  2. 参数必须是7种基本数据类型才会生效 

配置热点规则

注意:资源名必须是@SentinelResource("资源名”) 中配置的资源名,热点规则依赖于注解

测试接口

http://localhost:8060/order/getOrderById/1

测试效果

  • http://localhost:8060/order/getOrderByld/3 限流的阈值为1
  • http://localhost:8060/order/getOrderByld/1 限流的阈值为2

6. 系统规则

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。
 

系统自适应保护的目的

  • 保证系统不被拖垮
  • 在系统稳定的前提下,保持系统的吞吐量

系统规则支持以下的阈值类型

  • .Load(仅对 Linux/Unix-ike 机器生效):当系统 load1 超过國值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps* minRt 计算得出。设定参考值一般是 CPU cores* 2.5。
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护,
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
     

配置系统规则 

 测试效果

使用压测工具 jmeter 进行压测

测试接口

http://localhost:8060/order/getOrder?userId=gorgor

线程组配置

查看实时监控,可以看到限流效果

7. 授权控制规则

来源访问控制(黑白名单)根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

配置项讲解

资源名(resource):限流规则的作用对象。
流控应用(limitApp):对应的黑名单/白名单,不同 origin 用,分隔,如 appA,appB。
授权类型(strategy):限制模式,AUTHORITY WHITE 为白名单模式,AUTHORITY BLACK 为黑名单模式,默认为白名单模式。

授权控制也要实现来源数据的,需要实现 RequestOriginParser 接口,如下:

@Component
public class MyRequestOriginParser implements RequestOriginParser {
    /**
     * 通过request获取来源标识,交给授权规则进行匹配
     * @param request
     * @return
     */
    public String parseOrigin(HttpServletRequest request) {
        // 标识字段名称可以自定义
        String origin = request.getParameter("userId");
//        if (StringUtil.isBlank(origin)){
//            throw new IllegalArgumentException("userId参数未指定");
//        }
        return origin;
    }
}

 配置授权规则 

注意:自定义的RequestOriginParser接口实现类MyRequestOringinParse中指定了userId的值可以为来源。

 测试效果

  • http://localhost:8060/order/getOrder?userId=gorgor 会被限制访问
  • http://localhost:8060/order/getOrder?userId=jj   不会被限制访问

四、Sentinel 规则持久化

Sentinel 默认将规则存储在内存中,应用重启后规则会丢失。生产环境必须实现规则持久化,而Sentinel没有提供持久化的功能,只有WritableDataSource抽象,需要我们自己自定义实现WritableDataSource 接口,将数据进行持久化。

官方推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更,流程如下:

push-rules-from-dashboard-to-config-center

DataSource 扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

Sentinel 目前支持以下数据源扩展:

基于Nacos 配置中心实现推模式

  • 从 Nacos 配置中心中获取配置

引入依赖

此依赖,可以从nacos中拉取配置,然后存在内存中,sentinel 控制台就会访问到这些数据。

<!--sentinel 从nacos拉取规则 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

配置 yml

spring:
  application:
    name: order-server

  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos


    sentinel:
      # 将其配置为 false 即可根据不同的 URL 进行链路限流
      web-context-unify: false
      transport:
        # 添加sentinel的控制台地址
        dashboard: localhost:8889
        # 指定应用与Sentinel控制台交互的端口,应用会起一个HttpServer占用该端口
        port: 8719
      # 从nacos配置中心拉取规则信息
      datasource:
        flow-rules: #流控规则
          nacos:
            server-addr: localhost:8848
            username: nacos
            password: nacos
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP   # 注意groupId对应Sentinel Dashboard中的定义
            data-type: json
            rule-type: flow
        degrade-rules: #熔断降级规则
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: degrade
        param-flow-rules: #热点参数
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-param-flow-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: param-flow
        authority-rules: #权限规则
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-authority-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: authority
        system-rules: #系统规则
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-system-rules
            groupId: SENTINEL_GROUP
            data-type: json
            rule-type: system

从Nacos拉取流控规则配置的源码类:com.alibaba.cloud.sentinel.datasource.config.AbstractDataSourceProperties#postRegister

  • 将Sentinel配置的规则推送到Nacos中

微服务端扩展自定义实现写数据源 WritableDataSource 接口,将规则保存到Nacos配置中心中。

处理类

public class SentinelNacosDataSourceHandler implements SmartInitializingSingleton {

    private final SentinelProperties sentinelProperties;

    public SentinelNacosDataSourceHandler(SentinelProperties sentinelProperties) {
        this.sentinelProperties = sentinelProperties;
    }

    public void afterSingletonsInstantiated() {
        sentinelProperties.getDatasource().values().forEach(this::registryWriter);
    }

    private void registryWriter(DataSourcePropertiesConfiguration dataSourceProperties) {
        final NacosDataSourceProperties nacosDataSourceProperties = dataSourceProperties.getNacos();
        if (nacosDataSourceProperties == null) {
            return;
        }
        final RuleType ruleType = nacosDataSourceProperties.getRuleType();
        switch (ruleType) {
            case FLOW:
                WritableDataSource<List<FlowRule>> flowRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerFlowDataSource(flowRuleWriter);
                break;
            case DEGRADE:
                WritableDataSource<List<DegradeRule>> degradeRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWriter);
                break;
            case PARAM_FLOW:
                WritableDataSource<List<ParamFlowRule>> paramFlowRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWriter);
                break;
            case SYSTEM:
                WritableDataSource<List<SystemRule>> systemRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerSystemDataSource(systemRuleWriter);
                break;
            case AUTHORITY:
                WritableDataSource<List<AuthorityRule>> authRuleWriter = new NacosWritableDataSource<>(nacosDataSourceProperties, JSON::toJSONString);
                WritableDataSourceRegistry.registerAuthorityDataSource(authRuleWriter);
                break;
            default:
                break;
        }
    }
}

写数据源

@Slf4j
public class NacosWritableDataSource<T> implements WritableDataSource<T> {

    private final Converter<T, String> configEncoder;
    private final NacosDataSourceProperties nacosDataSourceProperties;

    private final Lock lock = new ReentrantLock(true);
    private ConfigService configService = null;

    public NacosWritableDataSource(NacosDataSourceProperties nacosDataSourceProperties, Converter<T, String> configEncoder) {
        if (configEncoder == null) {
            throw new IllegalArgumentException("Config encoder cannot be null");
        }
        if (nacosDataSourceProperties == null) {
            throw new IllegalArgumentException("Config nacosDataSourceProperties cannot be null");
        }
        this.configEncoder = configEncoder;
        this.nacosDataSourceProperties = nacosDataSourceProperties;
        final Properties properties = buildProperties(nacosDataSourceProperties);
        try {
            // 也可以直接注入NacosDataSource,然后反射获取其configService属性
            this.configService = NacosFactory.createConfigService(properties);
        } catch (NacosException e) {
            log.error("create configService failed.", e);
        }
    }

    private Properties buildProperties(NacosDataSourceProperties nacosDataSourceProperties) {
        Properties properties = new Properties();
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getServerAddr())) {
            properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDataSourceProperties.getServerAddr());
        } else {
            properties.setProperty(PropertyKeyConst.ACCESS_KEY, nacosDataSourceProperties.getAccessKey());
            properties.setProperty(PropertyKeyConst.SECRET_KEY, nacosDataSourceProperties.getSecretKey());
            properties.setProperty(PropertyKeyConst.ENDPOINT, nacosDataSourceProperties.getEndpoint());
        }
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getNamespace())) {
            properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDataSourceProperties.getNamespace());
        }
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getUsername())) {
            properties.setProperty(PropertyKeyConst.USERNAME, nacosDataSourceProperties.getUsername());
        }
        if (!StringUtils.isEmpty(nacosDataSourceProperties.getPassword())) {
            properties.setProperty(PropertyKeyConst.PASSWORD, nacosDataSourceProperties.getPassword());
        }
        return properties;
    }

    public void write(T value) throws Exception {
        lock.lock();
        // todo handle cluster concurrent problem
        try {
            String convertResult = configEncoder.convert(value);
            if (configService == null) {
                log.error("configServer is null, can not continue.");
                return;
            }
            // 规则配置数据推送到nacos配置中心
            final boolean published = configService.publishConfig(nacosDataSourceProperties.getDataId(), nacosDataSourceProperties.getGroupId(), convertResult);
            if (!published) {
                log.error("sentinel {} publish to nacos failed.", nacosDataSourceProperties.getRuleType());
            }
        } finally {
            lock.unlock();
        }
    }

    public void close() throws Exception {

    }
}

处理类生效

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(SentinelAutoConfiguration.class)
public class SentinelNacosDataSourceConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public SentinelNacosDataSourceHandler sentinelNacosDataSourceHandler(SentinelProperties sentinelProperties) {
        return new SentinelNacosDataSourceHandler(sentinelProperties);
    }
}

五、RestTemplate 整合 Sentinel

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上@SentinelRestTemplate 注解。

代码如下

@Configuration
public class RestConfig {

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(
            blockHandler = "handleBlockException",blockHandlerClass = ExceptionUtil.class,
            fallback = "handleFallback",fallbackClass = ExceptionUtil.class
    )
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

 限流降级类

@Slf4j
public class ExceptionUtil {

    public static SentinelClientHttpResponse handleBlockException(HttpRequest request,
                                                                  byte[] body, ClientHttpRequestExecution execution, BlockException ex) {

        log.info("ExceptionUtil#handleBlockException方法,===限流啦===");
        Result result = Result.failed("===被限流啦===");
        try {
            return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(result));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static SentinelClientHttpResponse handleFallback(HttpRequest request,
                                                            byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        log.info("ExceptionUtil#handleFallback方法,===限流啦===");
        Result result = Result.failed("===被异常降级啦===");
        try {
            return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(result));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

}

源码处理类

  • com.alibaba.cloud.sentinel.custom.SentinelBeanPostProcessor
  • com.alibaba.cloud.sentinel.custom.SentinelProtectInterceptor#intercept

六、OpenFeign 整合 Sentinel

Sentinel 也适配了 Feign 组件,对Feign之间的服务调用也可以进行流控的保护。

在 yml 开启 Sentinel 对 Feign 的支持

feign:
  sentinel:
    enabled: true  #开启Sentinel 对 Feign 的支持

在Feign的声明式接口上添加fallback或者fallbackFactory属性

@FeignClient(value = "tlmall-order-sentinel-demo",path = "/order",fallback = FallbackOrderFeignService.class)
public interface OrderFeignService {

降级逻辑FallbackOrderFeignService实现类

@Component   //必须交给spring 管理
public class FallbackOrderFeignService implements OrderFeignService {
    @Override
    public Result getOrder(String userId) {
        return Result.failed("=======服务降级了========");
    }

    @Override
    public Result<?> post1(OrderDTO orderDTO) {
        return Result.failed("=======服务降级了========");
    }

    @Override
    public Result<?> post2(OrderDTO orderDTO, String token) {
        return Result.failed("=======服务降级了========");
    }

    @Override
    public Result<?> post3(OrderDTO orderDTO, String userId) {
        return Result.failed("=======服务降级了========");
    }

    @Override
    public Result getOrderByUserId(String userId) {
        return Result.failed("=======服务降级了========");
    }
}

 七、Gateway 整合 Sentinel

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。

sentinel-api-gateway-common-arch

Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑:

  • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeld
  • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

引入依赖

<!-- gateway接入sentinel  -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

 在 yml 配置 sentinel 控制台配置

spring:
  cloud:
    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: tlmall-sentinel-dashboard:8889

 注意:

基于SpringBoot3的 Spring Cloud Gateway和Sentinel存在兼容性问题,使用默认的 

DefaultBlockRequestHandler会抛出异常,如下:

 解决:

自定义实现 BlockRequestHandler 替换掉 DefaultBlockRequestHandler

public class MyBlockRequestHandler implements BlockRequestHandler {

    @Override
    public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {

        //返回json数据;
        return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(fromObject(buildErrorResult(t)));
    }

    private Result buildErrorResult(Throwable ex) {

        if (ex instanceof ParamFlowException) {
            return Result.failed("请求被限流了");
        }
        return Result.failed("系统繁忙");
    }


}

实现 ApplicationRunner接口,springboot启动之后替换异常处理类。

@Component
public class MyApplicationRunner implements ApplicationRunner {


    @Override
    public void run(ApplicationArguments args) throws Exception {
        GatewayCallbackManager.setBlockHandler(new MyBlockRequestHandler());
    }
}

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐