SpringCloud Alibaba实战之Sentinel

Springcloud Alibaba相关组件系列文章如下:

SpringCloud Alibaba实战之Nacos-CSDN博客

SpringCloud Alibaba实战之OpenFeign-CSDN博客

SpringCloud Alibaba实战之Sentinel-CSDN博客

SpringCloud Alibaba实战之Gateway-CSDN博客

SpringCloud Alibaba实战之Seata-CSDN博客

sentinel

官网:https://github.com/alibaba/Sentinel/wiki

quick-start | Sentinel

随着微服务的流行,服务和服务之间的稳定性变得越来越重要,SpringCloud Alibaba Sentinel以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。

1、sentinel简单使用

1. 下载sentinel-dashboard的jar包,官网地址:https://github.com/alibaba/Sentinel

2. 使用java -jar sentinel-dashboard-xxx.jar命令启动sentinel-dashboard,访问8080端口登录sentinel,用户名和密码都是sentinel。

登录sentinel之后,sentinel-dashboard界面是没有任何资源的,必须启动微服务并调用接口之后,在sentinel控制台才能看到被调用接口的资源信息。可以在application.yml配置文件中通过spring.cloud.sentinel.eager: true 属性设置饥饿加载,服务启动就连接sentinel,默认是第一次调用服务才会连接sentinel。

3. 在services模块的pom.xml文件中引入sentinel的依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>services</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>service-order</module>
        <module>service-product</module>
    </modules>

    <dependencies>
        <!-- 引入model模块 -->
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- 启动Springboot服务,内嵌Tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 服务注册与发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- 配置中心 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- 负载均衡远程调用 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- 服务调用 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- 服务保护(限流、熔断降级) -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

4. 在application.yml文件中添加sentinel的配置

sentinel:
  transport:
    # 配置sentinel地址,连接sentinel
    dashboard: localhost:8080
  eager: true # 服务启动就连接sentinel,默认是第一次调用服务才会连接sentinel

service-order服务的application.yml文件内容如下:

server:
  port: 8000
spring:
  profiles:
    active: dev # 通过修改spring.profiles.active属性的值来选择不同环境的配置
    include: feign # 引入application-feign.yml文件
  application:
    name: service-order
  cloud:
    nacos:
      # 配置nacos地址,将微服务注册到nacos上
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false
        # 如果没有配置spring.profiles.active,则默认使用public命名空间
        namespace: ${spring.profiles.active:public}
    sentinel:
      transport:
        # 配置sentinel地址,连接sentinel
        dashboard: localhost:8080
      eager: true # 服务启动就连接sentinel,默认是第一次调用服务才会连接sentinel

logging:
  level:
    # 给com.springcloud.demo.order.feign包下的类设置日志级别为debug
    com.springcloud.demo.order.feign: debug

---
spring:
  config:
    import:
      - nacos:common.properties?group=service-order
      - nacos:database.properties?group=service-order
    activate:
      on-profile: dev
---
spring:
  config:
    import:
      - nacos:common.properties?group=service-order
    activate:
      on-profile: test
---
spring:
  config:
    import:
      - nacos:database.properties?group=service-order
    activate:
      on-profile: product

service-product服务的application.yml文件内容如下:

server:
  port: 9000
spring:
  application:
    name: service-product
  cloud:
    nacos:
      # 配置nacos地址,将微服务注册到nacos上
      server-addr: 127.0.0.1:8848
      config:
        # 如果引入了Nacos配置中心依赖,需要导入Nacos的配置,或禁用Nacos配置检查,否则服务启动失败
        import-check:
          enabled: false
    sentinel:
      transport:
        # 配置sentinel地址,连接sentinel
        dashboard: localhost:8080
      eager: true # 服务启动就连接sentinel,默认是第一次调用服务才会连接sentinel

5. 在OrderServiceImpl类中通过@SentinelResource注解定义一个sentinel资源

package com.springcloud.demo.order.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.springcloud.demo.order.bean.Order;
import com.springcloud.demo.order.config.OrderProperties;
import com.springcloud.demo.order.feign.ProductFeignClient;
import com.springcloud.demo.order.service.OrderService;
import com.springcloud.demo.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Collections;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private OrderProperties orderProperties;

    /**
     * 从Nacos配置中心获取配置信息
     *
     * @return 配置信息
     */
    @Override
    public String getConfig() {
        String s = "order.timeout=" + orderProperties.getTimeout()
                + ", order.auto-confirm=" + orderProperties.getAutoConfirm()
                + ", order.db-url=" + orderProperties.getDbUrl();
        log.info("The nacos config is: {}.", s);
        return s;
    }

    @SentinelResource(value = "createOrder")  // 定义一个sentinel资源
    @Override
    public Order createOrder(Long userId, Long productId) {
        Product product = productFeignClient.getProductById(productId);
        Order order = new Order();
        order.setId(1L);
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickName("zhangsan");
        order.setAddress("上海");
        order.setProductList(Collections.singletonList(product));
        return order;
    }
}

6. 启动service-order和service-product服务,先调用service-order服务接口,再访问sentinel-dashboard界面。

如下图所示,可以对每一个资源配置流控、熔断、热点和授权等操作,这些操作都可以被称为规则。

例如对于/order/create资源配置流控规则QPS=1,也就是/order/create接口每秒最多支持1个请求,超过流控规则就会触发sentinel限制,抛出BlockException异常。

2、sentinel资源异常处理

Sentinel资源可以简单分成web接口类型的资源、@SentinelResource注解标记的资源、OpenFeign调用类型的资源等,对于不同类型的sentinel资源配置流控、熔断、热点、授权等规则之后,如果请求资源时违反了规则限制,则会抛出BlockException异常,如果不对该异常处理,直接抛给前端,对前端不太友好,所以针对这几种类型的资源抛出BlockException异常时分别提供不同的处理方式。

1、web接口sentinel资源的异常处理

1. 为了给前端返回统一的数据格式,如下所示:

{
    "code": 200,
    "msg": "success",
    "data": {}
}

在model模块创建Result实体类:

package com.springcloud.demo.common;

import lombok.Data;

@Data
public class Result {
    private Integer code;
    private String msg;
    private Object data;

    public static Result ok() {
        Result result = new Result();
        result.setCode(200);
        result.setMsg("success");
        return result;
    }

    public static Result ok(Object data) {
        Result result = ok();
        result.setData(data);
        return result;
    }

    public static Result error() {
        Result result = new Result();
        result.setCode(500);
        return result;
    }

    public static Result error(Integer code, String msg) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }
}

2. 在service-order服务中自定义异常处理器,实现BlockExceptionHandler接口

package com.springcloud.demo.order.exception;

import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springcloud.demo.common.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;

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

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       String resourceName, BlockException e) throws Exception {
        response.setContentType("application/json;charset=utf-8");
        try (PrintWriter writer = response.getWriter()) {
            String cause = e.getMessage() == null ? e.toString() : e.getMessage();
            String msg = resourceName + " 被sentinel限制了,原因:" + cause;
            Result error = Result.error(500, msg);
            writer.write(objectMapper.writeValueAsString(error));
            writer.flush();
        }
    }
}

3. 通过postman连续快速调用service-order服务的/order/create接口。

2、@SentinelResource标记的资源异常处理

1. 使用@SentinelResource注解标记某个业务方法作为资源,并设置blockHandler属性。

package com.springcloud.demo.order.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.springcloud.demo.order.bean.Order;
import com.springcloud.demo.order.config.OrderProperties;
import com.springcloud.demo.order.feign.ProductFeignClient;
import com.springcloud.demo.order.service.OrderService;
import com.springcloud.demo.product.bean.Product;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Collections;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private OrderProperties orderProperties;

    /**
     * 从Nacos配置中心获取配置信息
     *
     * @return 配置信息
     */
    @Override
    public String getConfig() {
        String s = "order.timeout=" + orderProperties.getTimeout()
                + ", order.auto-confirm=" + orderProperties.getAutoConfirm()
                + ", order.db-url=" + orderProperties.getDbUrl();
        log.info("The nacos config is: {}.", s);
        return s;
    }

    /**
     * 使用@SentinelResource注解定义一个sentinel资源,用于标记需要受保护的资源。
     * value:资源名称,在控制台配置规则的依据,该属性是必须的。
     * entryType:流量方向:IN(入口)/OUT(出口)。
     * blockHandler:处理BlockException异常的降级方法名。
     * fallback:业务异常时的降级方法名。
     * defaultFallback:默认降级方法名(无参或单Throwable参数)。
     * exceptionsToIgnore:忽略的异常类型(不触发fallback)。
     * 被@SentinelResource注解标记为资源的业务方法,如果违反资源的规则,会抛出BlockException异常,
     * 则按顺序依次使用blockHandler、fallback或defaultFallback指定的方法进行兜底回调,
     * 若这三个属性都没有指定兜底回调方法,则抛出BlockException异常信息。
     *
     * @param userId 用户id
     * @param productId 商品id
     * @return 订单信息
     */
    @SentinelResource(value = "createOrder", blockHandler = "createOrderFallback")
    @Override
    public Order createOrder(Long userId, Long productId) {
        Product product = productFeignClient.getProductById(productId);
        Order order = new Order();
        order.setId(1L);
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setNickName("zhangsan");
        order.setAddress("上海");
        order.setProductList(Collections.singletonList(product));
        return order;
    }
    // 方法参数需要与原方法相同,可以在最后添加BlockException或Throwable
    // 若原方法上@SentinelResource注解使用的是blockHandler,则该方法添加BlockException,
    // 若原方法上@SentinelResource注解使用的是fallback,则该方法添加Throwable
    public Order createOrderFallback(Long userId, Long productId, BlockException e) {
        Order order = new Order();
        order.setId(0L);
        order.setTotalAmount(new BigDecimal("0"));
        order.setUserId(userId);
        order.setNickName(e.toString());
        return order;
    }
}

2. 在sentinel控制台配置对应资源的规则,例如配置"createOrder"资源每秒最多请求1次。

3. 通过postman多次调用service-order服务创建订单的接口,观察触发兜底回调的响应结果。

package com.springcloud.demo.order.controller;

import com.springcloud.demo.common.Result;
import com.springcloud.demo.order.bean.Order;
import com.springcloud.demo.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    /**
     * POST请求接收JSON数据时使用简单类型(Integer、String等)不能自动填充数据
     * 必须要封装成实体类或者使用Map接收,而且使用Map接收时还要注明泛型
     *
     * @param map 请求参数
     * @return 订单信息
     */
    @PostMapping("/create")
    public Result createOrder(@RequestBody Map<String, Object> map) {
        Long userId = Long.parseLong(String.valueOf(map.get("userId")));
        Long productId = Long.parseLong(String.valueOf(map.get("productId")));
        Order order = orderService.createOrder(userId, productId);
        return Result.ok(order);
    }

    @GetMapping("/config")
    public Result getConfig() {
        return Result.ok(orderService.getConfig());
    }
}

3、OpenFeign调用类型的sentinel资源异常处理

1. 在application-feign.yml或application.yml文件中开启sentinel的熔断降级功能。

spring:
  cloud:
    openfeign:
      client:
        config:
          default: # 默认连接超时和读取超时都设置成30秒,对所有feign客户端生效
            logger-level: full
            connect-timeout: 30000
            read-timeout: 30000
          service-product: # 被调用的服务名称,@FeignClient注解value属性值
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
#            request-interceptors:
#              - com.springcloud.demo.order.interceptor.XTokenRequestInterceptor
feign:
  sentinel:
    enabled: true # 开启熔断降级功能

2. 上面在OrderServiceImpl类的createOrder方法中,通过ProductFeignClient接口远程调用service-product服务的getProductById方法获取商品信息,ProductFeignClient接口如下所示。当getProductById方法出现异常时,通过@FeignClient注解的fallback属性指定的兜底回调返回默认的数据。

package com.springcloud.demo.order.feign;

import com.springcloud.demo.order.feign.fallback.ProductFeignClientFallback;
import com.springcloud.demo.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @FeignClient是Spring Cloud提供的一个声明式HTTP客户端注解,主要用于简化微服务之间的远程调用。
 * 通过简单的接口定义和注解配置,即可实现服务间的HTTP调用,开发者无需手动编写HTTP请求代码。
 * 多种参数配置:
 * value/name:指定要调用的服务名称
 * url:直接指定服务URL,绕过服务发现,使用该方式调用没有在Nacos上注册的第三方服务
 * fallback:定义调用失败时的降级处理类
 * configuration:自定义Feign客户端配置
 */
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
    /**
     * 调用"/product/query/{id}"接口发送请求获取结果
     * @GetMapping、@PostMapping、@PutMapping等是Spring提供的处理HTTP请求的注解,
     * 1.当它们与@RestController注解一起使用时,表示接受其他服务发送过来的请求。
     * 2.当它们与@FeignClient注解一起使用时,表示给其他服务发送请求。
     *
     * @param id 商品id
     * @return 商品信息
     */
    @GetMapping("/product/query/{id}")
    Product getProductById(@PathVariable(value = "id") Long id);
}

3. 创建FeignClient接口的兜底回调实现类,用于返回默认数据。

package com.springcloud.demo.order.feign.fallback;
import java.math.BigDecimal;

import com.springcloud.demo.order.feign.ProductFeignClient;
import com.springcloud.demo.product.bean.Product;
import org.springframework.stereotype.Component;

@Component
public class ProductFeignClientFallback implements ProductFeignClient {
    @Override
    public Product getProductById(Long id) {
        System.out.println("ProductFeignClient的兜底回调......");
        Product product = new Product();
        product.setId(id);
        product.setPrice(new BigDecimal("0"));
        product.setProductName("未知商品");
        product.setNum(0);
        return product;
    }
}

4. 在sentinel控制台给OpenFeign调用类型的资源配置规则。

5. 通过postman多次调用service-order服务创建订单的接口,观察响应结果,得到兜底数据。

3、sentinel规则

1、流量控制(FlowRule)

模式类型

适用场景

特点

配置方式

直接模式

单资源独立控制

最基础模式,直接针对资源名限流

FlowRule 指定资源名

关联模式

资源间依赖控制

基于关联资源状态控制目标资源

DegradeRule 配置关联规则

链路模式

全链路流量控制

按调用链整体限流(需Tracer)

FlowRule 设置 clusterMode=true

直接模式(Direct Mode

1. 核心特性

  • ‌独立控制‌:每个资源单独配置流控规则。
  • ‌精确匹配‌:精确到具体的方法或接口调用。

2. 典型场景

  • API接口频率限制
  • 秒杀活动独立限流

3. 配置示例

// 创建规则
FlowRule rule = new FlowRule("/order/create")
    .setCount(10) // QPS阈值
    .setGrade(RuleConstant.FLOW_GRADE_QPS);
// 应用规则
DegradeRuleManager.loadRules(Arrays.asList(rule));

关联模式(Association Mode

1. 核心特性

  • ‌资源联动:基于关联资源的流控状态影响目标资源。
  • 状态传递:关联资源被限流时,目标资源自动降级。

2. 典型场景

  • 订单服务依赖支付服务,支付服务限流时自动降级订单服务。
  • 库存服务影响商品服务。

3. 配置示例

DegradeRule rule = new DegradeRule("orderService") // 关联资源
    .setGrade(RuleConstant.DEGRADE_GRADE_RT) // 按RT降级
    .setCount(500) // 阈值
    .setTimeWindow(10); // 统计窗口
// 应用规则
DegradeRuleManager.loadRules(Arrays.asList(rule));

链路模式(Cluster Mode

1. 核心特性

  • ‌全局视角:对调用链整体进行流量控制。
  • 全链路关联:需开启Tracer组件追踪调用链。

2. 典型场景

  • 微服务集群整体限流
  • 分布式系统全局QPS控制

3. 配置示例

FlowRule rule = new FlowRule("clusterResource")
    .setCount(1000)
    .setGrade(RuleConstant.FLOW_GRADE_CLUSTER_QPS); // 集群模式
// 应用规则
FlowRuleManager.loadRules(Arrays.asList(rule));

流量控制代码示例

流控模式有三种:直接、关联和链路,默认是直接流控模式,在上面讲解sentinel资源异常处理时就是用的这种模式,此处不再赘述。

1、流量控制-链路模式

现在有个需求:创建订单有两种方式,日常普通创建订单和秒杀活动创建订单,只对秒杀创建订单的方式进行流控限制,而普通创建订单的方式不做流控限制。对于这个需求可以使用直接流控模式,对订单秒杀接口/order/seckill(接口名称也是资源名称)配置QPS阈值即可。也可以使用链路流控模式实现这个需求。如果使用链路流控模式实现这个需求,需要关闭统一web上下文配置。

spring.cloud.sentinel.web-context-unify的值默认为true,表示开启统一web上下文。如果开启统一web上下文,则同一个微服务下的所有接口共用一个web上下文,该web上下文的资源名是sentinel_spring_web_context,如下图所示。

1. 在service-order服务的OrderController类中新增秒杀创建订单的接口。

package com.springcloud.demo.order.controller;

import com.springcloud.demo.common.Result;
import com.springcloud.demo.order.bean.Order;
import com.springcloud.demo.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    /**
     * POST请求接收JSON数据时使用简单类型(Integer、String等)不能自动填充数据
     * 必须要封装成实体类或者使用Map接收,而且使用Map接收时还要注明泛型
     *
     * @param map 请求参数
     * @return 订单信息
     */
    @PostMapping("/create")
    public Result createOrder(@RequestBody Map<String, Object> map) {
        Long userId = Long.parseLong(String.valueOf(map.get("userId")));
        Long productId = Long.parseLong(String.valueOf(map.get("productId")));
        Order order = orderService.createOrder(userId, productId);
        return Result.ok(order);
    }

    /**
     * 秒杀场景创建订单
     *
     * @param map 请求参数
     * @return 订单信息
     */
    @PostMapping("/seckill")
    public Result seckill(@RequestBody Map<String, Object> map) {
        Long userId = Long.parseLong(String.valueOf(map.get("userId")));
        Long productId = Long.parseLong(String.valueOf(map.get("productId")));
        Order order = orderService.createOrder(userId, productId);
        order.setId(Long.MAX_VALUE);
        return Result.ok(order);
    }

    @GetMapping("/config")
    public Result getConfig() {
        return Result.ok(orderService.getConfig());
    }
}

2. 在application.yml文件配置spring.cloud.sentinel.web-context-unify: false,关闭统一web上下文。

server:
  port: 8000
spring:
  profiles:
    active: dev # 通过修改spring.profiles.active属性的值来选择不同环境的配置
    include: feign # 引入application-feign.yml文件
  application:
    name: service-order
  cloud:
    nacos:
      # 配置nacos地址,将微服务注册到nacos上
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false
        # 如果没有配置spring.profiles.active,则默认使用public命名空间
        namespace: ${spring.profiles.active:public}
    sentinel:
      transport:
        # 配置sentinel地址,连接sentinel
        dashboard: localhost:8080
      eager: true # 服务启动就连接sentinel,默认是第一次调用服务才会连接sentinel
      web-context-unify: false  # 是否统一web上下文,默认为true

logging:
  level:
    # 给com.springcloud.demo.order.feign包下的类设置日志级别为debug
    com.springcloud.demo.order.feign: debug

---
spring:
  config:
    import:
      - nacos:common.properties?group=service-order
      - nacos:database.properties?group=service-order
    activate:
      on-profile: dev
---
spring:
  config:
    import:
      - nacos:common.properties?group=service-order
    activate:
      on-profile: test
---
spring:
  config:
    import:
      - nacos:database.properties?group=service-order
    activate:
      on-profile: product

3. 重启service-order和service-product微服务,通过postman调用/order/create和/order/seckill两个接口,在sentinel控制台可以看到这两个接口资源,这两个接口会使用共同的createOrder资源。如果使用直接流控模式对createOrder资源进行sentinel流控限制,会对/order/create和/order/seckill两个接口都做限流。可以使用直接流控模式对/order/seckill资源精确流控。

4. 对createOrder资源使用链路流控模式配置/order/seckill作为入口资源,sentinel实际只对/order/seckill接口进行限流,不会对/order/create接口进行限流,即使/order/create接口中也用到了createOrder资源。

5. 通过postman分别多次调用/order/create和/order/seckill接口,发现前者一直成功,后者偶尔会触发sentinel限流而返回兜底数据。

2、流量控制-关联模式

现在有个需求:当对数据库只读或只写时不做流量控制,如果频繁写数据库时,对读数据库操作进行流量控制,优先执行写数据库操作。例如有两个接口/order/create和/order/config,当调用/order/create接口频繁创建订单写数据库时,对查询订单配置/order/config接口进行限流。如果只调用查询订单配置接口,不会触发限流。

通过postman先对/order/create接口频繁调用,然后立即调用/order/config接口,观察是否会触发sentinel流控限制。

2、熔断降级(DegradeRule)

断路器工作原理:

服务之间正常调用时断路器状态是关闭的,若下游服务异常导致服务之间调用异常,满足熔断触发条件(达到慢调用比例、异常比例或异常数)时,断路器状态变成打开,在设置的熔断时长内(例如30秒),上游服务会直接返回错误或使用兜底数据,不会真正的调用下游服务。当设置的熔断时长结束之后,此时断路器状态变成半开,上游服务再次调用下游服务时,先放行一个请求尝试调用下游服务(其他调用依然会直接返回错误或使用兜底数据),若这个调用可以正常返回,则断路器状态变成关闭,后续请求都会真正的调用下游服务。若这个调用返回异常,则断路器状态变成打开,再次设置熔断时长,在熔断时长内,上游服务会直接返回错误或使用兜底数据,不会真正的调用下游服务。

熔断触发条件有三种:慢调用比例、异常比例和异常数熔断策略。

1、慢调用比例熔断策略有以下几个参数:

  • 最大RT:最大响应时间(单位:ms),慢调用的阈值,超过该值的请求是“慢调用”。
  • 比例阈值:慢调用占比达到该值时触发熔断(取值范围是0.0-1.0)。
  • 熔断时长:触发熔断后,服务进入“熔断状态”的持续时间(单位:s)。
  • 最小请求数:统计时长内,请求量需达到该值才可能触发熔断(避免“小流量下的误判”)。
  • 统计时长:统计慢调用比例的时间窗口(单位:ms)。

当满足以下条件时,Sentinel 会触发‌熔断‌:

  • 在统计时长内,请求量 ≥ 最小请求数(如 5 次),若请求量 < 最小请求数,即使慢调用比例达标也不触发熔断;
  • 慢调用比例 ≥ 比例阈值(如 80%)。

触发熔断后,后续请求会被‌降级‌(如直接返回默认值、抛异常、跳过服务调用等)。

如下图所示,在统计时长5秒之内,若请求次数大于等于5,且有80%的请求都是慢调用(响应时间超过1秒的是慢调用),则sentinel就会触发熔断,熔断时长是30秒,在这30秒内,后续请求会被降级,不会调用下游异常服务。

2、异常比例熔断策略有以下几个参数:

  • 比例阈值:若请求中异常(如500错误、超时)的比例达到“比例阈值”,则触发熔断(取值范围是0.0-1.0)。
  • 熔断时长:触发熔断后,服务进入“熔断状态”的持续时间(单位:s)。
  • 最小请求数:统计时长内,请求量需达到该值才可能触发熔断(避免“小流量下的误判”)。
  • 统计时长:统计异常比例的时间窗口(单位:ms)。

当满足以下条件时,Sentinel 会触发‌熔断‌:

  • 在统计时长内,请求量 ≥ 最小请求数(如 5 次),若请求量 < 最小请求数,即使异常比例达标也不触发熔断;
  • 异常比例 ≥ 比例阈值(如 50%)。

触发熔断后,后续请求会被‌降级‌(如直接返回默认值、抛异常、跳过服务调用等)。

如下图所示,在统计时长5秒之内,若请求次数大于等于5,且有50%的请求都是异常调用(响应返回错误或响应超时等),则sentinel就会触发熔断,熔断时长是30秒,在这30秒内,后续请求会被降级,不会调用下游异常服务。

3、异常数熔断策略有以下几个参数:

  • 异常数:若异常(如500错误、超时异常)的请求数量达到“异常数”,则触发熔断。
  • 熔断时长:触发熔断后,服务进入“熔断状态”的持续时间(单位:s)。
  • 最小请求数:统计时长内,请求量需达到该值才可能触发熔断(避免“小流量下的误判”)。
  • 统计时长:统计异常比例的时间窗口(单位:ms)。

当满足以下条件时,Sentinel 会触发‌熔断‌:

  • 在统计时长内,请求量 ≥ 最小请求数(如 5 次),若请求量 < 最小请求数,即使异常数达标也不触发熔断;
  • 异常的请求个数 ≥ 异常数(如10)。

触发熔断后,后续请求会被‌降级‌(如直接返回默认值、抛异常、跳过服务调用等)。

如下图所示,在统计时长5秒之内,若请求次数大于等于5,且超过10个请求都是异常调用(响应返回错误),则sentinel就会触发熔断,熔断时长是30秒,在这30秒内,后续请求会被降级,不会调用下游异常服务。

熔断降级代码示例

1. 在application-feign.yml或application.yml文件中开启sentinel的熔断降级功能。

spring:
  cloud:
    openfeign:
      client:
        config:
          default: # 默认连接超时和读取超时都设置成30秒,对所有feign客户端生效
            logger-level: full
            connect-timeout: 30000
            read-timeout: 30000
          service-product: # 被调用的服务名称,@FeignClient注解value属性值
            logger-level: full
            connect-timeout: 3000
            read-timeout: 5000
#            request-interceptors:
#              - com.springcloud.demo.order.interceptor.XTokenRequestInterceptor
feign:
  sentinel:
    enabled: true # 开启熔断降级功能

2. 上面在OrderServiceImpl类的createOrder方法中,通过ProductFeignClient接口远程调用service-product服务的getProductById方法获取商品信息,ProductFeignClient接口如下所示。当getProductById方法出现异常时,通过@FeignClient注解的fallback属性指定的兜底回调返回默认的数据。

package com.springcloud.demo.order.feign;

import com.springcloud.demo.order.feign.fallback.ProductFeignClientFallback;
import com.springcloud.demo.product.bean.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @FeignClient是Spring Cloud提供的一个声明式HTTP客户端注解,主要用于简化微服务之间的远程调用。
 * 通过简单的接口定义和注解配置,即可实现服务间的HTTP调用,开发者无需手动编写HTTP请求代码。
 * 多种参数配置:
 * value/name:指定要调用的服务名称
 * url:直接指定服务URL,绕过服务发现,使用该方式调用没有在Nacos上注册的第三方服务
 * fallback:定义调用失败时的降级处理类
 * configuration:自定义Feign客户端配置
 */
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
    /**
     * 调用"/product/{id}"接口发送请求获取结果
     * @GetMapping、@PostMapping、@PutMapping等是Spring提供的处理HTTP请求的注解,
     * 1.当它们与@RestController注解一起使用时,表示接受其他服务发送过来的请求。
     * 2.当它们与@FeignClient注解一起使用时,表示给其他服务发送请求。
     *
     * @param id 商品id
     * @return 商品信息
     */
    @GetMapping("/product/query/{id}")
    Product getProductById(@PathVariable(value = "id") Long id);
}

3. 创建FeignClient接口的兜底回调实现类,用于返回默认数据。

package com.springcloud.demo.order.feign.fallback;
import java.math.BigDecimal;

import com.springcloud.demo.order.feign.ProductFeignClient;
import com.springcloud.demo.product.bean.Product;
import org.springframework.stereotype.Component;

@Component
public class ProductFeignClientFallback implements ProductFeignClient {
    @Override
    public Product getProductById(Long id) {
        System.out.println("ProductFeignClient的兜底回调......");
        Product product = new Product();
        product.setId(id);
        product.setPrice(new BigDecimal("0"));
        product.setProductName("未知商品");
        product.setNum(0);
        return product;
    }
}

4. 在sentinel控制台给OpenFeign调用类型的资源配置规则。

5. 通过postman多次调用service-order服务创建订单的接口,观察响应结果,得到兜底数据。

3、热点参数限流

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

热点参数限流用于限制特定接口中某参数的高频请求‌(如订单创建接口的用户 ID 参数),避免单个参数值(如高频下单的用户)引发系统负载激增,保障服务稳定性。

在sentinel-dashboard界面新增和编辑热点规则。

热点规则有以下几个参数:

  • ‌资源名‌:需与 Sentinel 配置中定义的资源名一致(如接口路径 /order/create),确保限流规则精准绑定到目标接口。
  • 限流模式‌:选择 QPS 模式(基于请求速率限制)或 线程数模式(基于并发线程数限制),根据业务场景选择(如高频请求场景选 QPS)。
  • 参数索引‌:指定接口请求参数的‌索引位置‌(如 GET 请求的查询参数、POST 请求的 JSON 字段索引),需与接口参数结构匹配,索引0表示第一个参数,索引1表示第二个参数。
  • ‌单机阈值‌:单台机器对该参数的‌请求速率上限‌(如设置为 1,表示每秒最多允许 1 次该参数的请求)。
  • 统计窗口时长‌:统计请求速率的时间窗口(如 1 秒,表示按 1 秒内请求量计算速率)。
  • 是否集群‌:若为分布式集群,需勾选以‌集群维度‌统计请求(单机模式仅统计本机请求)。
  • 参数例外项‌:针对特定参数值设置‌例外规则‌(如用户 ID 为 5 时,允许更高限流阈值 10000000),避免误限高频正常请求。

热点参数限流代码示例

现在有个需求:在秒杀场景中,除了超级VIP用户,其他用户每秒最多只允许抢购一单。

假设秒杀接口/order/seckill的请求参数是(Long userId, Long productId),如下所示:

/**
* 使用@SentinelResource注解定义一个sentinel资源,用于标记需要受保护的资源。
* value:资源名称,在控制台配置规则的依据,该属性是必须的。
* entryType:流量方向:IN(入口)/OUT(出口)。
* blockHandler:处理BlockException异常的降级方法名。
* fallback:业务异常时的降级方法名。
* defaultFallback:默认降级方法名(无参或单Throwable参数)。
* exceptionsToIgnore:忽略的异常类型(不触发fallback)。
* 被@SentinelResource注解标记为资源的业务方法,如果违反资源的规则,会抛出BlockException异常,
* 则按顺序依次使用blockHandler、fallback或defaultFallback指定的方法进行兜底回调,
* 若这三个属性都没有指定兜底回调方法,则抛出BlockException异常信息。
*
* @param userId 用户id
* @param productId 商品id
* @return 订单信息
*/
@PostMapping("/seckill")
@SentinelResource(value = "seckill-order", fallback = "seckillFallback")
public Result seckill(Long userId, Long productId) {
    Order order = orderService.createOrder(userId, productId);
    order.setId(Long.MAX_VALUE);
    return Result.ok(order);
}
// 方法参数需要与原方法相同,可以在最后添加BlockException或Throwable
// 若原方法上@SentinelResource注解使用的是blockHandler,则该方法添加BlockException,
// 若原方法上@SentinelResource注解使用的是fallback,则该方法添加Throwable
public Result seckillFallback(Long userId, Long productId, Throwable exception) {
    Order order = new Order();
    order.setId(0L);
    order.setTotalAmount(new BigDecimal("0"));
    order.setUserId(userId);
    order.setNickName("异常信息:" + exception.getClass());
    return Result.ok(order);
}

对于这个需求,在sentinel-dashboard界面给"seckill-order"资源创建热点规则,设置参数索引为0,表示一个参数userId,单机阈值和统计窗口时长都是1(用户每秒最多只允许抢购一单)。另外假设userId=5是超级VIP,需要设置参数例外项,将userId=5的限流阈值调成非常大的一个数值。

若接口参数是基础类型或String类型,Sentinel 会将它们作为参数传入 SphU.entry(res, args)。但是若接口参数是对象类型,则该对象必须实现ParamFlowArgument 接口,并重写 paramFlowKey 方法来提供热点参数的键值。若接口参数原来是Map类型,也需要改造成对象类型,且这个对象需要实现ParamFlowArgument 接口。

让OrderParam对象实现ParamFlowArgument 接口:

package com.springcloud.demo.order.pojo;

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowArgument;
import lombok.Data;

@Data
public class OrderParam implements ParamFlowArgument {
    private Long userId;

    private Long productId;

    @Override
    public Object paramFlowKey() {
        // 使用 userId 作为热点键
        return userId;
    }
}

/**
 * 秒杀场景创建订单
 *
 * @param orderParam 请求参数
 * @return 订单信息
 */
@PostMapping("/seckill")
@SentinelResource(value = "seckill-order", fallback = "seckillFallback")
public Result seckill(@RequestBody OrderParam orderParam) {
    Order order = orderService.createOrder(orderParam.getUserId(), orderParam.getProductId());
    order.setId(Long.MAX_VALUE);
    return Result.ok(order);
}

public Result seckillFallback(OrderParam orderParam, Throwable exception) {
    Order order = new Order();
    order.setId(0L);
    order.setTotalAmount(new BigDecimal("0"));
    order.setUserId(orderParam.getUserId());
    order.setNickName("异常信息:" + exception.getClass());
    return Result.ok(order);
}

通过postman频繁调用秒杀接口/order/seckill,观察响应结果。

4、授权规则

授权规则用于控制调用方对资源的访问权限,通过黑白名单机制实现:

  • ‌白名单‌:允许指定来源(origin)的请求访问资源。
  • ‌黑名单‌:禁止指定来源的请求访问资源。

实现方式:

  • ‌请求来源标识‌:通过实现RequestOriginParser接口解析请求头或参数中的origin值。
  • ‌动态配置‌:支持与Nacos等配置中心集成,实现规则动态更新。

‌典型场景‌:

  • 限制只有网关发起的请求能访问微服务,防止绕过网关的直接调用。
  • 按用户角色或服务划分访问权限。

5、系统规则

系统规则是从应用整体维度进行保护的规则,仅对入口流量(如Web服务或Dubbo服务端接收的请求)生效。主要包含以下阈值类型:

  1. Load‌(仅Linux/Unix生效):当系统load超过阈值且并发线程数超过容量时触发保护。
  2. ‌RT‌(平均响应时间):入口请求的RT超过阈值时触发。
  3. ‌线程数‌:并发线程数超过阈值时触发。
  4. ‌入口QPS‌:所有入口流量的QPS总和超过阈值时触发。
  5. ‌CPU使用率‌:系统CPU使用率超过阈值时触发。

系统规则通过监控上述指标,在保证系统最大吞吐量的同时维持整体稳定性。

4、sentinel规则持久化

sentinel的所有规则都是内存存储的,应用重启后所有规则都会丢失,在生产环境下,必须确保这些规则的持久化,避免丢失。

Sentinel为了实现规则的持久化管理,提供了ReadableDataSrouce(配置读取)与WritableDataSource(配置写入)两个接口,通过这两个接口可以向指定的存储设备中实现规则的读写处理。

如果要开发一个系统,这个系统可以由管理人员在服务应用部署上线之前,就可以进行所有限流规则的维护,这个时候一定需要有一个完善的管理界面(排除没有完善管理界面的存储终端:Redis、ZooKeeper),所以最佳的做法就是使用 Nacos,因为 Nacos有完善的管理界面,而且其本身是提供有 JSON 数据(各种数据类型都是支持的)的直接存储,最重要的是 Nacos 还是 SpringCloudAlibaba 套件之中最为重要的注册中心。

Sentinel 提供的 DataSource 是一个逻辑上的概念,具体的存储可以是关系型数据库、文件、ZooKeeper、Redis、Nacos 等存储终端,在终端中可以保存所需要的限流规则,而对于 DataSource 的操作形式有两种:拉模式(Pull-Based)和推模式(Push-Based)。

sentinel支持三种规则管理模式:

  • 原始模式:Sentinel的默认模式,将规则保存在内存,重启应用服务会导致规则丢失。
  • pull模式:sentinel客户端(即应用服务)主动向规则管理中心定期轮询拉取规则,规则中心可以是RDBMS、文件等,虽然此种方式简单,但是却无法及时获取到配置更新。
  • push模式:所有的规则由配置中心(Nacos、Zookeeper、Redis 等)统一推送,sentinel客户端(即应用服务)通过注册监听器的方式监听配置中心,获取配置变更的推送消息,这样可以更好的保持配置的实时性和一致性。

ReadableDataSource源码如下所示:

package com.alibaba.csp.sentinel.datasource;

import com.alibaba.csp.sentinel.property.SentinelProperty;

public interface ReadableDataSource<S, T> {
// 加载配置
    T loadConfig() throws Exception;
    // 读取原生配置的数据项
    S readSource() throws Exception;
    // 获取Sentinel配置属性
    SentinelProperty<T> getProperty();
    // 关闭读取的数据源
    void close() throws Exception;
}

在Sentinel中所有与配置属性有关的更新处理操作全部都是由SentinelProperty接口完成的,而这个接口需要及时处理数据更新,最佳的做法就是通过nacos实现处理。

package com.alibaba.csp.sentinel.property;

public interface SentinelProperty<T> {
// 添加监听器,监听配置属性变更
    void addListener(PropertyListener<T> var1);
    // 移除监听器
    void removeListener(PropertyListener<T> var1);
    // 更新属性值
    boolean updateValue(T var1);
}

1、规则配置属性

Sentinel各个规则(流控、熔断降级、授权、系统、热点规则)的配置信息具体有哪些属性,该如何配置这些属性,需要查看SentinelAutoConfiguration自动装配的源码,在这个类中用到了各个规则对应的实体类(FlowRule、DegradeRule、AuthorityRule、SystemRule、ParamFlowRule),规则实体类中封装了每个规则的属性。

SentinelAutoConfiguration的源码如下所示:

package com.alibaba.cloud.sentinel.custom;

import com.alibaba.cloud.sentinel.SentinelProperties;
import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter;
import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
 * @author xiaojing
 * @author jiashuai.xie
 * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
 * @author freeman
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
@EnableConfigurationProperties(SentinelProperties.class)
public class SentinelAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SentinelResourceAspect sentinelResourceAspect() {
       return new SentinelResourceAspect();
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
    @ConditionalOnProperty(name = "resttemplate.sentinel.enabled", havingValue = "true",
          matchIfMissing = true)
    public static SentinelBeanPostProcessor sentinelBeanPostProcessor(
          ApplicationContext applicationContext) {
       return new SentinelBeanPostProcessor(applicationContext);
    }

    @Bean
    @ConditionalOnMissingBean
    public SentinelDataSourceHandler sentinelDataSourceHandler(
          DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties,
          Environment env) {
       return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env);
    }

    @ConditionalOnClass(ObjectMapper.class)
    @Configuration(proxyBeanMethods = false)
    protected static class SentinelConverterConfiguration {  // 转换JSON格式的规则配置

       @Configuration(proxyBeanMethods = false)
       protected static class SentinelJsonConfiguration {

          private ObjectMapper objectMapper = new ObjectMapper();

          public SentinelJsonConfiguration() {
             objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
                   false);
          }

          @Bean("sentinel-json-flow-converter")
          public JsonConverter jsonFlowConverter() {
             return new JsonConverter(objectMapper, FlowRule.class);
          }

          @Bean("sentinel-json-degrade-converter")
          public JsonConverter jsonDegradeConverter() {
             return new JsonConverter(objectMapper, DegradeRule.class);
          }

          @Bean("sentinel-json-system-converter")
          public JsonConverter jsonSystemConverter() {
             return new JsonConverter(objectMapper, SystemRule.class);
          }

          @Bean("sentinel-json-authority-converter")
          public JsonConverter jsonAuthorityConverter() {
             return new JsonConverter(objectMapper, AuthorityRule.class);
          }

          @Bean("sentinel-json-param-flow-converter")
          public JsonConverter jsonParamFlowConverter() {
             return new JsonConverter(objectMapper, ParamFlowRule.class);
          }

       }

       @ConditionalOnClass(XmlMapper.class)
       @Configuration(proxyBeanMethods = false)
       protected static class SentinelXmlConfiguration {  // 转换XML格式的规则配置

          private XmlMapper xmlMapper = new XmlMapper();

          public SentinelXmlConfiguration() {
             xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
                   false);
          }

          @Bean("sentinel-xml-flow-converter")
          public XmlConverter xmlFlowConverter() {
             return new XmlConverter(xmlMapper, FlowRule.class);
          }

          @Bean("sentinel-xml-degrade-converter")
          public XmlConverter xmlDegradeConverter() {
             return new XmlConverter(xmlMapper, DegradeRule.class);
          }

          @Bean("sentinel-xml-system-converter")
          public XmlConverter xmlSystemConverter() {
             return new XmlConverter(xmlMapper, SystemRule.class);
          }

          @Bean("sentinel-xml-authority-converter")
          public XmlConverter xmlAuthorityConverter() {
             return new XmlConverter(xmlMapper, AuthorityRule.class);
          }

          @Bean("sentinel-xml-param-flow-converter")
          public XmlConverter xmlParamFlowConverter() {
             return new XmlConverter(xmlMapper, ParamFlowRule.class);
          }

       }

    }

}

AbstractRule的部分源码,省略了一些方法:

package com.alibaba.csp.sentinel.slots.block;

import java.util.Objects;

/**
 * Abstract rule entity.
 *
 * @author youji.zj
 * @author Eric Zhao
 */
public abstract class AbstractRule implements Rule {

    /**
     * rule id.
     */
    private Long id;

    /**
     * Resource name.
     */
    private String resource;

    /**
     * <p>
     * Application name that will be limited by origin.
     * The default limitApp is {@code default}, which means allowing all origin apps.
     * </p>
     * <p>
     * For authority rules, multiple origin name can be separated with comma (',').
     * </p>
     */
    private String limitApp;

    /**
     * Whether to match resource names according to regular rules
     */
    private boolean regex;
}

1、流控规则配置属性

FlowRule的部分源码,省略了一些方法:

package com.alibaba.csp.sentinel.slots.block.flow;

import com.alibaba.csp.sentinel.slots.block.AbstractRule;

public class FlowRule extends AbstractRule {

    public FlowRule() {
        super();
        setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
    }

    public FlowRule(String resourceName) {
        super();
        setResource(resourceName);
        setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
    }

    /**
     * The threshold type of flow control (0: thread count, 1: QPS).
     */
    private int grade = RuleConstant.FLOW_GRADE_QPS;

    /**
     * Flow control threshold count.
     */
    private double count;

    /**
     * Flow control strategy based on invocation chain.
     *
     * {@link RuleConstant#STRATEGY_DIRECT} for direct flow control (by origin);
     * {@link RuleConstant#STRATEGY_RELATE} for relevant flow control (with relevant resource);
     * {@link RuleConstant#STRATEGY_CHAIN} for chain flow control (by entrance resource).
     */
    private int strategy = RuleConstant.STRATEGY_DIRECT;

    /**
     * Reference resource in flow control with relevant resource or context.
     */
    private String refResource;

    /**
     * Rate limiter control behavior.
     * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter
     */
    private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;

    private int warmUpPeriodSec = 10;

    /**
     * Max queueing time in rate limiter behavior.
     */
    private int maxQueueingTimeMs = 500;

    private boolean clusterMode;
    /**
     * Flow rule config for cluster mode.
     */
    private ClusterFlowConfig clusterConfig;

    /**
     * The traffic shaping (throttling) controller.
     */
    private TrafficShapingController controller;

}

结合AbstractRule和FlowRule的源码可知,流控规则的配置属性大致如下所示。

[
  {
    "resource": "/order/create",  // 资源名称
    "limitApp": "default",  // 应用来源,使用默认值default
    "grade": 1,  // 阈值类型,0:线程数量,1:QPS
    "count": 2,  // 单机阈值(若"grade": 1,"count": 2,表示每秒最多允许两个请求)
    "strategy": 0,  // 流控模式,0:直接,1:关联,2:链路
    "refResource": 0,  // 关联资源,strategy =1关联模式或strategy =2链路模式需要该字段
    "controlBehavior": 0,  // 流控效果,0:快速失败,1:WarmUp,2:排队等待
    "warmUpPeriodSec": 10,  // 预热时长,controllBehavior=1(WarmUp)需要该字段
    "maxQueueingTimeMs": 500,  // 队列超时时间,controllBehavior=2(排队等待)需要该字段
    "clusterMode": false,  // 采用非集群模式
    "clusterConfig": { // 集群配置,clusterMode=true集群模式需要该配置
      "thresholdType": 0,  // 集群阈值模式,0:单机均摊,1:总体阈值
      "fallbackToLocalWhenFail": true  // 失败退化,如果Token Server不可用是否退化到单机限流
    }
  }
]

流控规则根据是否集群、流控模式和流控效果的不同组合有以下几种形式,规则配置属性根据不同的组合形式大致相同,略有差异。

1. 单机-直接-快速失败

对应的流控规则配置属性如下所示:

[
    {
        "resource": "/order/create",
        "limitApp": "default",
        "grade": 1,
        "count": 2,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

在Nacos上创建配置:

2. 单机-直接-WarmUp

3. 单机-直接-排队等待

4. 单机-关联-快速失败

5. 单机-关联-WarmUp

6. 单机-关联-排队等待

7、单机-链路-快速失败/WarmUp/排队等待

8、集群模式

2、熔断降级规则配置属性

DegradeRule的部分源码,省略了一些方法:

package com.alibaba.csp.sentinel.slots.block.degrade;

import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;

import java.util.Objects;

public class DegradeRule extends AbstractRule {

    public DegradeRule() {}

    public DegradeRule(String resourceName) {
        setResource(resourceName);
    }

    /**
     * Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count).
     */
    private int grade = RuleConstant.DEGRADE_GRADE_RT;

    /**
     * Threshold count. The exact meaning depends on the field of grade.
     * <ul>
     *     <li>In average RT mode, it means the maximum response time(RT) in milliseconds.</li>
     *     <li>In exception ratio mode, it means exception ratio which between 0.0 and 1.0.</li>
     *     <li>In exception count mode, it means exception count</li>
     * <ul/>
     */
    private double count;

    /**
     * Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will
     * transform to half-open state for trying a few requests.
     */
    private int timeWindow;

    /**
     * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking.
     *
     * @since 1.7.0
     */
    private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT;

    /**
     * The threshold of slow request ratio in RT mode.
     *
     * @since 1.8.0
     */
    private double slowRatioThreshold = 1.0d;

    /**
     * The interval statistics duration in millisecond.
     *
     * @since 1.8.0
     */
    private int statIntervalMs = 1000;
}

结合AbstractRule和DegradeRule的源码可知,熔断降级规则的配置属性大致如下所示。

[
  {
    "resource": " GET:http://service-product/product/query/{id}",  // 资源名称
    "grade": 0,  // 熔断策略,0:慢调用比例,1:异常比例,2:异常数
    "count": 1000, // 阈值数量,grade=0,count表示最大响应时间RT,单位毫秒;grade=1,count表示比例阈值,取值范围0.0-1.0;grade=2,count表示异常数
    "slowRatioThreshold": 1.0,  // 比例阈值,grade=0慢调用比例熔断策略时需要该属性
    "timeWindow": 30,  // 熔断时长,单位秒
    "minRequestAmount": 5,  // 最小请求数
    "statIntervalMs": 5000  // 统计时长,单位毫秒
  }
]

在前文已对不同熔断策略的各个参数进行详细解释,此处不再赘述。

在Nacos上创建配置:

3、热点参数限流规则配置属性

ParamFlowRule的部分源码,省略了一些方法:

package com.alibaba.csp.sentinel.slots.block.flow.param;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;

public class ParamFlowRule extends AbstractRule {

    public ParamFlowRule() {}

    public ParamFlowRule(String resourceName) {
        setResource(resourceName);
    }

    /**
     * The threshold type of flow control (0: thread count, 1: QPS).
     */
    private int grade = RuleConstant.FLOW_GRADE_QPS;

    /**
     * Parameter index.
     */
    private Integer paramIdx;

    /**
     * The threshold count.
     */
    private double count;

    /**
     * Traffic shaping behavior (since 1.6.0).
     */
    private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT;

    private int maxQueueingTimeMs = 0;
    private int burstCount = 0;
    private long durationInSec = 1;

    /**
     * Original exclusion items of parameters.
     */
    private List<ParamFlowItem> paramFlowItemList = new ArrayList<ParamFlowItem>();

    /**
     * Parsed exclusion items of parameters. Only for internal use.
     */
    private Map<Object, Integer> hotItems = new HashMap<Object, Integer>();

    /**
     * Indicating whether the rule is for cluster mode.
     */
    private boolean clusterMode = false;
    /**
     * Cluster mode specific config for parameter flow rule.
     */
    private ParamFlowClusterConfig clusterConfig;
}

结合AbstractRule和ParamFlowRule的源码可知,热点参数限流规则的配置属性如下所示。

[
  {
    "resource": "/order/create",  // 资源名称
    "grade": 1,  // 限流模式,0:线程数量,1:QPS(默认是QPS)
    "paramIdx": 0, // 参数索引
    "count": 1,  // 单机阈值
    "controlBehavior": 0,  // 流控效果,0:快速失败,1:WarmUp,2:排队等待
    "maxQueueingTimeMs": 500,  // 队列超时时间,controllBehavior=2(排队等待)需要该字段
    "durationInSec": 1,  // 统计窗口时长,单位秒
    "clusterMode": false,  // 采用非集群模式
    "clusterConfig": { // 集群配置,clusterMode=true集群模式需要该配置
      "thresholdType": 0,  // 集群阈值模式,0:单机均摊,1:总体阈值
      "fallbackToLocalWhenFail": true  // 失败退化,如果Token Server不可用是否退化到单机限流
    },
    "paramFlowItemList": [  // 参数例外项
      {
        "object": "",
        "count": 0,  // 参数值
        "classType": ""  // 参数类型
      }
    ]
  }
]

在Nacos上创建配置:

4、授权规则配置属性

AuthorityRule的部分源码,省略了一些方法:

package com.alibaba.csp.sentinel.slots.block.authority;

import com.alibaba.csp.sentinel.slots.block.AbstractRule;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;

public class AuthorityRule extends AbstractRule {
    /**
     * Mode: 0 for whitelist; 1 for blacklist.
     */
    private int strategy = RuleConstant.AUTHORITY_WHITE;

}

结合AbstractRule和AuthorityRule的源码可知,热点参数限流规则的配置属性如下所示。

[
  {
    "resource": "/order/create",  // 资源名称
    "limitApp": 0, // 流控应用名称,多个应用名称用逗号分隔
    "strategy": 0  // 授权类型,0:白名单,1:黑名单
  }
]

5、系统规则配置属性

SystemRule的部分源码,省略了一些方法:

package com.alibaba.csp.sentinel.slots.system;

import com.alibaba.csp.sentinel.slots.block.AbstractRule;

public class SystemRule extends AbstractRule {
    /**
     * negative value means no threshold checking.
     */
    private double highestSystemLoad = -1;
    /**
     * cpu usage, between [0, 1]
     */
    private double highestCpuUsage = -1;
    private double qps = -1;
    private long avgRt = -1;
    private long maxThread = -1;
}

结合AbstractRule和SystemRule的源码可知,热点参数限流规则的配置属性如下所示。

[
  {
    "qps": 1  // 阈值类型(平均RT、最大线程数、QPS、最大CPU使用率)及其对应的阈值
  }
]

2、从Nacos同步规则至Sentinel

当前Sentinel Dashboard默认仅支持从Nacos同步规则至内存,暂不支持在sentinel-dashboard控制台修改后的规则主动同步回Nacos。

在nacos配置中心创建sentinel规则主要有以下几步:

1、启动nacos,打开nacos界面,创建命名空间

记录命名空间id:677d9fd4-2dea-4391-891d-52671399ed48,在下面配置文件中需要使用。

2、在nacos上创建规则配置

示例1:创建限流规则配置

[  // 可以有多个限流配置,所以使用JSON数组
    {  // 配置某一个具体的限流规则
        "resource": "/order/create",  // 资源名称
        "limitApp": "default",  // 应用来源,使用默认值default
        "grade": 1,  // 阈值类型,0:线程数量,1:QPS
        "count": 1,  // 单机阈值(若"grade": 1,"count": 1,表示每秒最多允许一个请求)
        "strategy": 0,  // 流控模式,0:直接,1:关联,2:链路
        "controlBehavior": 0,  // 流控效果,0:快速失败,1:WarmUp,2:排队等待
        "clusterMode": false  // 采用非集群模式
    }
]

这些配置属性与在sentinel控制台创建流控规则的界面是一一对应的,如下图所。之前是在sentinel控制台创建规则的,现在是在nacos界面通过JSON数据格式进行规则配置的。

示例2:创建熔断降级规则

[
  {
    "resource": "GET:http://service-product/product/query/{id}",
    "grade": 0,
    "count": 1000,
    "slowRatioThreshold": 1.0,
    "timeWindow": 30,
    "minRequestAmount": 5,
    "statIntervalMs": 5000
  }
]

3、在services模块的pom.xml文件中引入sentinel规则持久化保存到nacos的依赖

<!-- sentinel配置规则持久化 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.8</version>
</dependency>

4、在service-order的application.yml文件中添加如下配置。注意:不要使用username和password属性连接nacos,sentinel是无法通过认证连接到nacos的。

service-order的application.yml文件完整配置如下所示:

server:
  port: 8000
spring:
  profiles:
    active: dev # 通过修改spring.profiles.active属性的值来选择不同环境的配置
    include: feign # 引入application-feign.yml文件
  application:
    name: service-order
  cloud:
    nacos:
      # 配置nacos地址,将微服务注册到nacos上
      server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false
        # 如果没有配置spring.profiles.active,则默认使用public命名空间
        namespace: ${spring.profiles.active:public}
    sentinel:
      transport:
        # 配置sentinel地址,连接sentinel
        dashboard: localhost:8080
      eager: true # 饥饿加载,服务启动就连接sentinel,默认是第一次调用服务才会连接sentinel
      web-context-unify: false  # 是否统一web上下文,默认为true
      datasource:  # 数据源配置
        flow-datasource:  # 流控的数据源配置
          nacos:  # 当前的存储介质是 Nacos
            server-addr: 127.0.0.1:8848  # nacos的地址
            namespace: 677d9fd4-2dea-4391-891d-52671399ed48  # 命名空间id
            group-id: SENTINEL_GROUP  # 规则配置所属的组,一般建议大写
            data-id: ${spring.application.name}_flow-rules  # 新建配置指定的DATA ID
            data-type: json  # 配置格式,即配置数据内容的格式
            rule-type: flow  # sentinel规则的类型,flow表示流控规则
        degrade-datasource:  # 熔断降级的数据源配置
          nacos:  # 当前的存储介质是 Nacos
            server-addr: 127.0.0.1:8848  # nacos的地址
            namespace: 677d9fd4-2dea-4391-891d-52671399ed48  # 命名空间id
            group-id: SENTINEL_GROUP  # 规则配置所属的组,一般建议大写
            data-id: ${spring.application.name}_degrade-rules  # 新建配置指定的DATA ID
            data-type: json  # 配置格式,即配置数据内容的格式
            rule-type: degrade  # sentinel规则的类型,degrade表示熔断降级规则

logging:
  level:
    # 给com.springcloud.demo.order.feign包下的类设置日志级别为debug
    com.springcloud.demo.order.feign: debug

---
spring:
  config:
    import:
      - nacos:common.properties?group=service-order
      - nacos:database.properties?group=service-order
    activate:
      on-profile: dev
---
spring:
  config:
    import:
      - nacos:common.properties?group=service-order
    activate:
      on-profile: test
---
spring:
  config:
    import:
      - nacos:database.properties?group=service-order
    activate:
      on-profile: product

5、先启动sentinel,再启动service-order服务,观察sentinel-dashboard控制台的规则是否与上面在nacos中配置的一致。

6、通过postman多次调用创建订单的接口,观察是否会触发sentinel限流。

7、修改nacos配置,观察sentinel-dashboard控制台是否也发生了变更。

nacos_config数据库的config_info表中的数据如下所示。

3、从Sentinel同步规则至Nacos

在Sentinel控制台修改的规则只是保存到内存中,如果应用服务重启,规则就会消失。若想要将Sentinel控制台创建或修改的规则持久化,就需要修改Sentinel-dashboard模块的源码,将其与指定的数据源(如Sentinel-Nacos数据源)进行对接,这样开发者通过Sentinel控制台所做出的任何流控配置就可以自动保存在数据源之中。Sentinel-dashboard源码中提供了与Nacos整合的流控规则(其他Sentinel规则都没有)持久化实现方式,本文就是通过修改官方提供的源代码来实现将Sentinel控制台创建或修改的流控规则持久化到Nacos上。

修改sentinel-dashboard模块的源码主要有以下几个步骤:

1. 下载sentinel的源码压缩包,如下图所示。

2. 解压之后使用idea打开sentinel整个项目工程,通过idea导入项目。

3. 修改sentinel-dashboard模块的application.properties文件,添加Nacos数据源的配置。

4. 修改sentinel-dashboard的pom.xml文件,去掉sentinel-datasource-nacos的scope。

5. 将test目录下的rule/nacos复制到src/main/java目录下的rule/nacos。

6. 修改NacosConfig类,添加在application.properties中配置的属性

package com.alibaba.csp.sentinel.dashboard.rule.nacos;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Properties;

/**
 * @author Eric Zhao
 * @since 1.4.0
 */
@Configuration
public class NacosConfig {
    @Value("${nacos.address}")
    private String address; // application.properties中配置的属性

    @Value("${nacos.namespace}")
    private String namespace;

    @Value("${nacos.clusterName}")
    private String clusterName;

    @Bean
    public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
        return JSON::toJSONString;
    }

    @Bean
    public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
        return s -> JSON.parseArray(s, FlowRuleEntity.class);
    }

    @Bean
    public ConfigService nacosConfigService() throws Exception {
        // 进行Nacos服务的配置
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, address);
        properties.put(PropertyKeyConst.NAMESPACE, namespace);
        properties.put(PropertyKeyConst.CLUSTER_NAME, clusterName);
        return ConfigFactory.createConfigService(properties);
    }
}

7. 修改FlowControllerV2类,将"flowRuleDefaultProvider"修改成"flowRuleNacosProvider",将"flowRuleDefaultPublisher"修改成"flowRuleNacosPublisher"。

8. 将sentinel-dashboard\src\main\webapp\resources\app\scripts\directives\sidebar\sidebar.html文件中有关dashboard.flow的注释放开。这个放开是为了在sentinel-dashboard界面上可以看到流控规则V1 tab页。

9. 编译打包,生成sentinel-dashboard.jar包

10. 使用java -jar sentinel-dashboard.jar命令启动sentinel-dashboard,访问sentinel控制台,用户名和密码都是sentinel。

在流控规则V1 tab页可以看到之前在Nacos界面创建的流控规则。修改规则的阈值,观察Nacos界面是否也同步被修改了。如果在“流控规则V1” tab页看不到在Nacos界面创建的规则,可能的原因是创建规则时使用的GROUP_ID或DATA ID与sentinel-dashboard源码中的不一致,具体原因可查看本文最后的解释。

11. 修改之前Nacos界面上的流控规则count=20,修改之后变成了count=1.0,如下图所示。

另外,使用postman多次访问创建订单接口,触发了sentinel限流。

注意:sentinel-dashboard源码NacosConfigUtil中的GROUP_ID = "SENTINEL_GROUP",FLOW_DATA_ID_POSTFIX = "-flow-rules",在Nacos界面上创建流控规则时,使用的DATA ID必须是应用服务的名称-flow-rules,Group必须是SENTINEL_GROUP,否则在“流控规则V1”tab页看不到在Nacos上创建的流控规则。如果在Nacos界面创建流控规则时DATA ID后缀不是-flow-rules,或者GROUP_ID 不是 "SENTINEL_GROUP",要么修改Nacos的规则配置信息,要么修改sentinel-dashboard源码,使两者保持一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值