服务保护 Sentinel

Sentinel 介绍

服务链:随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。在一次请求中,多个服务就构成了一条调用的服务链路。

雪崩问题:在微服务架构中,一个服务出现故障或者异常,导致其他服务也出现故障或者异常,从而形成连锁反应,导致整个系统崩溃的问题。

微服务雪崩问题的原因:

  • 服务雪崩:一个服务出现故障,导致其他服务也出现故障。
  • 资源耗尽:一个服务占用太多的资源,导致其他服务无法获取足够的资源而出现故障。
  • 并发量过高:一个服务并发量过高,导致系统负载过重而出现故障。

解决微服务雪崩问题的方式:

  • 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。
  • 仓壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离。
  • 断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
  • 流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。

Sentinel 是阿里巴巴微服务生态下的流量治理中心,可以实现流量控制、熔断降级、系统自适应保护等功能,从而解决服务雪崩的问题。

Sentinel 的核心思想是控制流量,它可以通过限流、熔断、降级等手段来控制流量,避免系统过载,从而保护系统的稳定性和可用性。具体来说,当系统流量超出预设阈值时,Sentinel 会自动触发熔断降级操作,停止对该服务的调用,从而避免服务链路中的其他服务受到影响,避免服务雪崩的发生。

Sentinel官网地址

Sentinel分为两个部分:
核心库(Java客户端):不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo/ Spring Cloud等框架也有较好的支持。
控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

Sentinel 的核心概念

  • 资源(Resource):资源是 Sentinel 的关键概念,它可以是 Java 应用程序中的任何内容,例如由应用程序提供的服务,或由应用程序调用的其他应用提供的服务,甚至可以是一段代码。
  • 规则(Rule):围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。
  • 流量控制(FlowControl):通过限流来保护系统,当流量超出预设阈值时,Sentinel 会自动触发熔断降级操作。
  • 熔断降级(CircuitBreaker):当某个服务发生故障时,通过熔断降级来避免服务链路中的其他服务受到影响。
  • 系统保护(SystemProtection):通过系统保护来避免系统过载,从而保护系统的稳定性和可用性。

Sentinel 的下载使用

下载 Sentinel 控制台程序,

  • 下载地址
  • 运行命令:java -jar sentinel-dashboard-1.8.1.jar
  • 访问地址:http://localhost:8080/,登录账户:sentinel,登录密码:sentinel

请添加图片描述

微服务整合Sentinel
在pom文件中,新增依赖

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

在配置文件中新增 sentinel 相关的配置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: http://localhost:8080

完成配置后,重启应用,访问微服务的任意端点,触发sentinel监控。
请添加图片描述

Sentinel 流控规则

簇点链路:就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下 sentinel 会监控 SpringMVC 的每一个端点(Endpoint),因此 SpringMVC 的每一个端点(Endpoint)就是调用链路中的一个资源。流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:
请添加图片描述
点击资源后的流控按钮,弹出如下表单,可以添加流控规则。
QPS(每秒查询率) :QPS 通常用于描述系统或应用程序在单位时间内能够处理的查询或请求量。通过设置QPS来进行流量控制。
线程数:通过并发的线程数量来进行流量控制。
请添加图片描述

流控规则介绍

选择高级选项,可以看到更多的流控设置规则。
在这里插入图片描述

  • 资源名:一般是我们的请求路径
  • 针对来源:是指来自于哪个应用
  • 阈值类型:分为QPS或线程数
  • 单机阈值:单个节点的QPS或线程数阈值
  • 是否集群:被请求的服务是否集群部署
  • 流控模式:
    • 直接,统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
    • 关联,统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
    • 链路,统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
  • 流控效果:(线程数阈值类型不存在此属性)
    • 快速失败,达到阈值后,新的请求会被立即拒绝并抛出 FlowException 异常。是默认的处理方式。
    • Warm Up,预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
    • 排队等待,在QPS阈值到达后,让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。如果等待时间超出最大时长,则会被拒绝。可以适用于突发流量的请求。

流控规则演示

流控模式直接:

当请求的资源 QPS大于1的时候,就会直接失败。

请添加图片描述

请添加图片描述
流控模式关联:

请添加图片描述
当 /write 资源访问量触发阈值时,就会对 /read 资源限流,避免影响 /write 资源。

流控模式链路:
链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
请求链路:/test1 —> /test2
请添加图片描述
只统计从/test1 进入/test2 的请求,当链路请求大于阈值,则快速失败。

流控效果快速失败:
快速失败方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。
请添加图片描述
流控效果Warm Up:
Warm Up 方式,即预热模式,是应对服务冷启动的一种方案。。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
请求阈值初始值是 threshold / coldFactor,持续指定时长后,逐渐提高到 threshold 值。而coldFactor的默认值是 3。

例如,设置QPS的 threshold 为10,预热时间为5秒,那么初始阈值就是 10 / 3 ,也就是3,然后在5秒后逐渐增长到10。
请添加图片描述
流控效果排队等待:
当请求超过 QPS 阈值时,快速失败和 warm up 会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则会被拒绝。

例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着预期等待超过2000ms的请求会被拒绝并抛出异常
请添加图片描述

Sentinel 热点规则

之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过QPS阈值。

在这里插入图片描述
热点规则配置页,其中参数类型只支持基本的七种数据类型,热点参数限流对默认的SpringMVC资源无效,热点规则的资源名要使用 @SentinelResource 进行定义。

在这里插入图片描述
在代码中 使用 @SentinelResource 注解定义热点规则资源

@SentinelResource("hot")
@GetMapping("order/{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
	// 根据id查询订单并返回
    return orderService.queryOrderById(orderId);
}

新增热点规则,hot 资源 的 0号 long类型参数限流,每1秒相同参数的QPS不能超过5,然后设置了两个参数例外项,如果参数值是1,则每1秒允许的QPS为10,如果参数值是2,则每1秒允许的QPS为15
在这里插入图片描述

Sentinel 隔离和熔断降级

Sentinel 隔离和熔断降级都是微服务架构中的保护措施,用于防止故障扩散和保证系统可用性。
Sentinel 隔离是通过线程池隔离和熔断降级,将故障控制在一个可控范围内,避免雪崩的现象。

线程池隔离:调用者在调用服务提供者时,给每个调用的请求分配独立线程池,出现故障时,最多消耗这个线程池内资源,避免把调用者的所有资源耗尽。

熔断降级:在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败比例过高,则熔断该业务,不允许访问该服务的提供者。

Sentinel 隔离和熔断降级都需要在调用方进行设置,因此需要在调用方发起远程调用时进行线程隔离、或者服务熔断。

Feign 整合 Sentinel

在微服务中服务的远程调用都是通过Feign来实现的,所以客户端保护必须整合Feign和Sentinel。

导入 OpenFeign 依赖和 Sentinel依赖

<!--feign客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--引入sentinel依赖-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

在配置文件中进行设置

spring:
  cloud:
    sentinel:
      transport:
        dashboard: http://localhost:8080
#开启feign对Sentinel的支持,默认为false
feign:
  sentinel:
    enabled: true

主启动类添加注解 @EnableFeignClients 激活 openFeign
使用openFeign 客户端进行远程调用,并编写调用失败后的降级逻辑。

UserClient 客户端,fallbackFactory 指定了调用失败后的处理降级逻辑的工厂类

@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

UserClientFallbackFactory 实现 FallbackFactory接口,指明要生成的客户端类。在 create 方法中重写客户端类的逻辑,在客户端类调用失败后,会使用工厂类生成新的客户端,并进行调用。

@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable throwable) {
        return new UserClient() {
            @Override
            public User findById(Long id) {
                log.error("查询用户异常", throwable);
                return new User();
            }
        };
    }
}

需要将 UserClientFallbackFactory 注入容器。

@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
    return new UserClientFallbackFactory();
}

使用 UserClient 进行远程调用,如果远程调用失败,则会调用失败后的降级逻辑

@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
    // 1.查询订单
    Order order = orderMapper.findById(orderId);
    // 2.用Feign远程调用
    User user = userClient.findById(order.getUserId());
    // 3.封装user到Order
    order.setUser(user);
    // 4.返回
    return order;
}

线程隔离

Sentinel 可以设置线程数来进行限流。
当调用某个 API 的线程数达到设定的阈值时,Sentinel 会进行限流,即拒绝继续提供服务。

线程隔离有两种方式实现:

  • 线程池隔离,给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
  • 信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求。(Sentinel默认采用)

请添加图片描述

在添加限流规则时,设置线程数的值。即可实现线程隔离
在这里插入图片描述

熔断降级

熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。

断路器的三个状态, closed, open, half-open。
在closed状态,服务可以正常访问,会统计服务的失败比例,达到阈值会进行 open状态,服务会不可用,在熔断时间结束后,会进入 half-open状态,尝试放行服务,请求成功会进入 closed,失败会进入open状态,继续进行熔断。
请添加图片描述
断路器熔断策略有三种:慢调用、异常比例、异常数

慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。

解读:RT超过500ms的调用是慢调用,统计最近1000s内的请求,如果请求量超过5次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
请添加图片描述
异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。

解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
请添加图片描述
解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常数不低于4,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
请添加图片描述

Sentinel 授权规则

授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。

白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者不允许访问
请添加图片描述
获取请求来源方式
Sentinel 是通过 RequestOriginParser 接口的 parseOrigin 来获取请求的来源的。

public interface RequestOriginParser {
    // 从请求request对象中获取origin,获取方式自定义  
    String parseOrigin(HttpServletRequest request);
}

实现接口,获取 origin。

@Component
public class HeaderOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        // 1.获取请求参数
        String origin = request.getParameter("origin");
        // 2.非空判断
        if (StringUtils.isEmpty(origin)) {
            origin = "blank";
        }
        return origin;
    }
}

定义白名单
请添加图片描述
在进行请求时,如果请求参数中携带 origin 参数,且值为 white,则可以进行正常访问。

定义黑名单请添加图片描述
在进行请求时,如果请求参数中携带 origin 参数,值为 black,则不能访问,未携带 origin 参数,或值不为 black,则可以进行访问。

Sentinel 系统规则

Sentinel 系统自适应保护从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

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

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

Sentinel 自定义异常

默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。
返回的错误是:“Blocked by Sentinel (flow limiting)”,这对调用方很不友好,可以自定义异常处理。

如果要自定义异常时的返回结果,需要实现 BlockExceptionHandler 接口:

public interface BlockExceptionHandler {
    /*     
    * 处理请求被限流、降级、授权拦截时抛出的异常:BlockException     
    */    
    void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}

而 BlockException 包含很多个子类,分别对应不同的场景:

在这里插入图片描述
自定义异常类具体实现

@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        String msg = "未知异常";
        int status = 429;
        if (e instanceof FlowException) {
            msg = "请求被限流了";
        } else if (e instanceof ParamFlowException) {
            msg = "请求被热点参数限流";
        } else if (e instanceof DegradeException) {
            msg = "请求被降级了";
        } else if (e instanceof AuthorityException) {
            msg = "没有权限访问";
            status = 401;
        }
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(status);
        response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
    }
}

Sentinel 资源定义

资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。

使用 Sentinel 来进行资源保护,主要分为几个步骤:

  • 定义资源
  • 定义规则
  • 检验规则是否生效

先把可能需要保护的资源定义好,之后再配置规则。只要有了资源,就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。

url 默认资源

Sentinel 为 Spring Boot 程序提供了一个 starter 依赖,由于Sentinel starter 依赖默认情况下就会把所有的HTTP服务定义为资源,所以在 Spring Boot 中的 Controller 都可以受到Sentinel的保护,当然,还需要为Sentinel配置保护的规则。

可以在 application.yaml 中,通过配置关闭对微服务的默认保护。

spring:
  cloud:
    sentinel:
      transport:
        dashboard: http://localhost:8080
      #关闭sentinel对controller的url的保护,默认为 true
      filter:
        enabled: false

抛出异常的方式定义资源

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

示例代码如下:
1.5.0 版本开始可以利用 try-with-resources 特性

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

1.5.0 之前的版本的示例:

Entry entry = null;
// 务必保证finally会被执行
try {
  // 资源名可使用任意有业务语义的字符串
  entry = SphU.entry("自定义资源名");
  // 被保护的业务逻辑
  // do something...
} catch (BlockException e1) {
  // 资源访问阻止,被限流或被降级
  // 进行相应的处理操作
} finally {
  if (entry != null) {
    entry.exit();
  }
}

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

返回布尔值方式定义资源

SphO 提供 if-else 风格的 API。用这种方式,当资源发生了限流之后会返回 false,这个时候可以根据返回值,进行限流之后的逻辑处理。

示例代码如下:

  // 资源名可使用任意有业务语义的字符串
  if (SphO.entry("自定义资源名")) {
    // 务必保证finally会被执行
    try {
      /**
      * 被保护的业务逻辑
      */
    } finally {
      SphO.exit();
    }
  } else {
    // 资源访问阻止,被限流或被降级
    // 进行相应的处理操作
  }

注解方式定义资源

Sentinel 支持通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理。

@SentinelResource 注解包含以下属性:

  • value:资源名称,必需项

  • entryType:entry 类型,可选项(默认为 EntryType.OUT)

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。

    • blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。
    • blockHandler 函数默认需要和原方法在同一个类中。
    • 若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。
    fallback 函数签名和位置要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。
    • 若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所以类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。
    defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。
    • 若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注解方式埋点不支持 private 方法。

代码示例:
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。

// 原方法调用被限流/降级/系统保护的时候调用
public class MyBlockHandler {
    public static String testC(@PathVariable("id") int id, BlockException blockException) {
        return "testC:" + id + ",限流了,msg:" + blockException.getMessage();
    }
}
// 原方法出现业务异常的时候调用
public class MyFallback {
    public static String testC(@PathVariable("id") int id, Throwable t) {
        return "testC:" + id + ",降级了,msg:" + t.getMessage();
    }
}

@SentinelResource(value = "testC",
        blockHandler = "testC", blockHandlerClass = MyBlockHandler.class,//处理sentinel规则的兜底方法
        fallback = "testC", fallbackClass = MyFallback.class //处理当前控制器业务异常的熔断降级的兜底方法
)
@RequestMapping("/goods/testC/{id}")
public String testC(@PathVariable("id") int id) {
    //模拟业务正常
    if (id == 1) {
        return "server port:" + port + ",testC...";
    }
    //模拟业务出错
    if (id == 2) {
        int i = 1 / 0;
    }
    return "other ...";
}

Sentinel 数据持久化

Sentinel的控制台规则管理有三种模式:

推送模式说明优点缺点
原始模式API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource) ,默认就是这种。简单,无任何依赖不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等简单,无任何依赖;规则持久化不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。规则持久化;一致性;引入第三方依赖

原始模式

原始模式:控制台配置的规则直接推送到 Sentinel客户端,也就是我们的应用。然后保存在内存中,服务重启则丢失。
在这里插入图片描述

Pull 模式

pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
在这里插入图片描述
需要指定数据源,将规则写入数据源中,实现定时更新数据源中的规则。
通常需要调用以下方法将数据源注册至指定的规则管理器中:

ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());

若不希望手动注册数据源,可以借助 Sentinel 的 InitFunc SPI 扩展接口。只需要实现自己的 InitFunc 接口,在 init 方法中编写注册数据源的逻辑。

package com.test.init;
public class DataSourceInitFunc implements InitFunc {
    @Override
    public void init() throws Exception {
        final String remoteAddress = "localhost";
        final String groupId = "Sentinel:Demo";
        final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";

        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
            source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
    }
}

接着将对应的类名添加到位于资源目录(通常是 resource 目录)下的 META-INF/services 目录下的 com.alibaba.csp.sentinel.init.InitFunc 文件中,比如:

// 全限定类名
com.test.init.DataSourceInitFunc

当初次访问任意资源的时候,Sentinel 就可以自动去注册对应的数据源了。

实现本地文件数据源会定时轮询文件的变更,读取规则。

package cn.itcast.order.sentinel;

public class FileDataSourceInit implements InitFunc {
    @Override
    public void init() throws Exception {
        //可以根据需要指定规则文件的位置
        String ruleDir = System.getProperty("user.home") + "/sentinel/rules";

        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";

        this.mkdirIfNotExits(ruleDir);

        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(paramFlowRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);

        // 流控规则:可读数据源
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        // 将可读数据源注册至FlowRuleManager
        // 这样当规则文件发生变化时,就会更新规则到内存
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());

        // 流控规则:可写数据源
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
        // 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则:可读数据源
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());

        // 降级规则:可写数据源
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        // 热点参数规则:可读数据源
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());

        // 热点参数规则:可写数据源
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);

        // 系统规则:可读数据源
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());

        // 系统规则:可写数据源
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

        // 授权规则:可读数据源
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());

        // 授权规则:可写数据源
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<FlowRule>>() {}
    );

    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<DegradeRule>>() {}
    );

    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<SystemRule>>() {}
    );

    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<AuthorityRule>>() {}
    );

    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<ParamFlowRule>>() {}
    );

    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}

接着将对应的类名添加到位于资源目录(通常是 resource 目录)下的 META-INF/services 目录下的 com.alibaba.csp.sentinel.init.InitFunc 文件中,比如:

cn.itcast.order.sentinel.FileDataSourceInit

在控制台创建规则后,会自动写入到文件中。
在这里插入图片描述

Push模式

push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
在这里插入图片描述
基于Nacos配置中心控制台实现规则推送
配置中心控制台 → nacos 配置中心 → Sentinel数据源 → Sentinel

引入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.1</version>
</dependency>

修改配置文件,设置 nacos 数据源地址

spring:
  application:
    name: orderservice
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
    sentinel:
      transport:
        dashboard: localhost:8080 # sentinel控制台地址
      filter:
        enabled: false
      web-context-unify: false # 关闭context整合
      datasource:  # 设置数据源
        ds1:
          nacos:
            server-addr: localhost:8848 # nacos地址
            dataId: orderservice-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow # 还可以是:degrade、authority、param-flow

进入 nacos 控制台创建文件

请添加图片描述
重启应用,就可以在 Sentinel 控制台,看到对应的限流规则。
请添加图片描述
注意:在Nacos控制台上修改规则,可以同步到Sentinel Dashboard,但是在Sentinel Dashboard上修改规则后不能同步到 Nacos。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值