第四章 服务容错

作者为冰河忠实粉丝,可以加入【冰河技术】知识星球获取源码,也可以关注【冰河技术】公众号,加入社群学习,学习氛围好,干货满满。
冰河技术.jpg

目前,我们实现了用户服务、产品服务和订单服务之间的远程调用,并且实现了服务调用的负载均衡。但是,现在系统中存在着一个很明显的问题,那就是如果系统的并发量上来后,系统并没有容错的能力,这可能会导致系统不可用或者直接宕机,所以,我们的系统需要支持容错的能力。

1 服务雪崩与容错方案

1.1 并发对系统的影响

当一个系统的架构设计采用微服务架构模式时,会将庞大而复杂的业务拆分成一个个小的微服务,各个微服务之间以接口或者RPC的形式进行互相调用。在调用的过程中,就会涉及到网路的问题,再加上微服务自身的原因,例如很难做到100%的高可用等。如果众多微服务当中的某个或某些微服务出现问题,不可用或者宕机了,那么其他微服务调用这些微服务的接口时就会出现延迟。如果此时有大量请求进入系统,就会造成请求任务的大量堆积,甚至会造成整体服务的瘫痪。

1.2 压测实战

1.2.1 说明

  1. 为了更加直观的说明当系统没有容错能力时,高并发、大流量场景对于系统的影响,我们在这里模拟一个并发的场景。在订单微服务shop-order的cn.mawenda.shop.order.controller.OrderController类中新增一个concurrentRequest()方法,源码如下所示。
@GetMapping(value = "/concurrent_request")
public String concurrentRequest(){
    log.info("测试业务在高并发场景下是否存在问题");
    return "冰河技术";
}
  1. 为了更好的演示效果,我们限制下Tomcat处理请求的最大并发数,在订单微服务shop-order的resources目录下的application.yml文件中添加如下配置。
server:
  port: 8080
  tomcat:
    max-threads: 20
  1. 限制Tomcat一次最多只能处理20个请求。接下来,我们就使用JMeter对创建订单扣减库存的http://localhost:8080/order/submit_order 接口进行压测,由于订单微服务中没有做任何的容错处理,当对接口的请求压力过大时,我们再访问http://localhost:8080/order/concurrent_request 接口时,会发现concurrent_request 接口会受到并发请求的影响,访问很慢甚至根本访问不到。

1.2.2 压测

使用JMeter对 http://localhost:8080/order/submit_order 接口进行压测,下载过程自行百度,JMeter的配置过程如下所示。

  1. 打开JMeter主界面,在测试计划中右击,添加线程组,如下所示。

image.png

  1. 在线程组中配置并发线程数,如下所示。

image.png
线程数配置成50,Ramp-Up时间配置成0,循环次数为100。表示JMeter每次会在同一时刻向系统发送50个请求,发送100次为止

  1. 线程组中天假HTTP请求,如下所示。

image.png

  1. 配置HTTP请求,如下所示。

image.png
配置内容如下:

  • 协议:http
  • 服务器名称或IP:localhost
  • 端口号:8080
  • 方法:GET
  • 路径:/order/submit_order?userId=1001&productId=1001&count=1
  • 内容编码:UTF-8
  1. 配置好后,点击启动按钮开始压测,如下所示。

image.png

  1. 测试效果

点击保存后,开始对 http://localhost:8080/order/submit_order 接口进行压测,在压测的过程中会发现订单微服务打印日志时,会比较卡顿,同时在浏览器或其他工具中访问http://localhost:8080/order/concurrent_request 接口会卡顿,甚至根本访问不到,如图所示。
image.png
说明订单微服务中的某个接口一旦访问的并发量过高,其他接口也会受到影响,进而导致订单微服务整体不可用。

1.3 服务雪崩

系统采用分布式或微服务的架构模式后,由于网络或者服务自身的问题,一般服务是很难做到100%高可用的。如果一个服务出现问题,就可能会导致其他的服务级联出现问题,这种故障性问题会在整个系统中不断扩散,进而导致服务不可用,甚至宕机,最终会对整个系统造成灾难性后果。
为了最大程度的预防服务雪崩,组成整体系统的各个微服务需要支持服务容错的功能。

1.4 服务容错方案

服务容错在一定程度上就是尽最大努力来兼容错误情况的发生,因为在分布式和微服务环境中,不可避免的会出现一些异常情况,我们在设计分布式和微服务系统时,就要考虑到这些异常情况的发生,使得系统具备服务容错能力。
常见的服务错误方案包含:服务限流、服务隔离、服务超时、服务熔断和服务降级等。
服务容错.png

1.4.1 服务限流

服务限流就是限制进入系统的流量,以防止进入系统的流量过大而压垮系统。其主要的作用就是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务和数据崩溃(如前端缓存大量失效),造成不可用;还可用于平滑请求。
限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流(一般为1s),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。

1.4.2 服务隔离

服务隔离有点类似于系统的垂直拆分,就按照一定的规则将系统划分成多个服务模块,并且每个服务模块之间是互相独立的,不会存在强依赖的关系。如果某个拆分后的服务发生故障后,能够将故障产生的影响限制在某个具体的服务内,不会向其他服务扩散,自然也就不会对整体服务产生致命的影响。
互联网行业常用的服务隔离方式有:线程池隔离和信号量隔离。

1.4.3 服务超时

整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。
服务超时就是在上游服务调用下游服务时,设置一个最大响应时间,如果超过这个最大响应时间下游服务还未返回结果,则断开上游服务与下游服务之间的请求连接,释放资源。

1.4.4 服务熔断

在分布式与微服务系统中,如果下游服务因为访问压力过大导致响应很慢或者一直调用失败时,上游服务为了保证系统的整体可用性,会暂时断开与下游服务的调用连接。这种方式就是熔断。
服务熔断一般情况下会有三种状态:关闭、开启和半熔断。

  • 关闭状态:服务一切正常,没有故障时,上游服务调用下游服务时,不会有任何限制。
  • 开启状态:上游服务不再调用下游服务的接口,会直接返回上游服务中预定的方法。
  • 半熔断状态:处于开启状态时,上游服务会根据一定的规则,尝试恢复对下游服务的调用。此时,上游服务会以有限的流量来调用下游服务,同时,会监控调用的成功率。如果成功率达到预期,则进入关闭状态。如果未达到预期,会重新进入开启状态。

1.4.5 服务降级

服务降级,说白了就是一种服务托底方案,如果服务无法完成正常的调用流程,就使用默认的托底方案来返回数据。例如,在商品详情页一般都会展示商品的介绍信息,一旦商品详情页系统出现故障无法调用时,会直接获取缓存中的商品介绍信息返回给前端页面。

2 整合Sentinel实现限流与容错

2.1 Sentinel

Sentinel 以流量为切入点,从流量控制熔断降级系统负载保护等多个维度保护服务的稳定性。

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

2.1.1 特征

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
  • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

2.1.1 主要特性

Sentinel.png

2.2 安装Sentinel

在微服务项目中整合Sentinel是非常简单的,只需要在项目的pom.xml文件中引入Sentinel的依赖即可,不过在使用Sentinel时,需要安装Sentinel的控制台。

2.2.1 安装Sentinel控制台

Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。

  1. 到链接 https://github.com/alibaba/Sentinel/releases下载Sentinel控制台1.8.4版本,如下所示。

image.png

  1. 下载完成后,在本地启动 Sentinel 控制台,如下所示。
// 本地启动
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar

// CentOS 后台启动
nohup java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar >> /dev/null &
  1. 启动完成

image.png

  1. 访问控制台

在浏览器中输入 http://localhost:8888 访问Sentinel控制台,如下所示。
image.png
默认的用户名sentinel和密码sentinel,登录Sentinel控制台,如下所示。
image.png

2.3 集成Sentinel

  1. 在订单服务shop-order的pom.xml文件中添加Sentinel的相关依赖,如下所示。
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 在订单微服务的shop-order的application.yml文中加入Sentinel相关的配置,如下所示。
spring:
  cloud:
    sentinel:
      transport:
        port: 9999   #指定和Sentinel控制台交互的端口,任意指定一个未使用的端口即可
        dashboard: 127.0.0.1:8888  #Sentinel控制台服务地址
  1. 在 OrderController 增加测试接口
@GetMapping(value = "/test_sentinel")
public String testSentinel(){
    log.info("测试Sentinel");
    return "sentinel";
}
  1. 启动订单微服务,测试http://localhost:8080/order/test_sentinel 接口,如下所示。

image.png

  1. 刷新Sentinel页面,会发现已经显示了订单微服务的菜单,如下所示。

image.png
注意:直接启动订单微服务和Sentinel,会发现Sentinel中没有订单微服务的数据,因为Sentinel是懒加载机制,所以需要访问一下接口,再去访问Sentinel 就有数据了。

2.4 实现限流功能

使用Sentinel为http://localhost:8080/order/test_sentinel接口限流,步骤如下所示。

  1. 在Sentinel控制台找到server-order下的簇点链路菜单,如下所示。

image.png

  1. 在簇点链路列表中找到/test_sentinel,在右侧的操作中选择流控,如下所示。

image.png

  1. 设置流控规则

image.png
配置好之后点击新增按钮。上述配置表示http://localhost:8080/order/test_sentinel接口的QPS为1,每秒访问1次。如果每秒访问的次数超过1次,则会被Sentinel限流。

  1. 使用API测试工具不断请求http://localhost:8080/order/test_sentinel接口,当每秒中访问的次数超过1次时,会被Sentinel限流,如下所示。

image.png
项目中集成了Sentinel并使用Sentinel实现了接口的限流。

3 Feign整合Sentinel实现容错

项目中集成了Sentinel,并使用Sentinel实现了限流,如果订单微服务的下游服务,比如用户微服务和商品微服务出现故障,无法访问时,那订单微服务该如何实现服务容错呢?使用Sentinel就可以轻松实现。

3.1 添加依赖并开启支持

  1. 在订单服务的shop-order的pom.xml文件中添加Sentinel的相关依赖,如下所示。
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

注意:在前边已经添加过此依赖的可跳过该步骤。

  1. 在订单微服务的application.yml文件中添加如下配置开启Feign对Sentinel的支持。
feign:
  sentinel:
    enabled: true

3.2 为远程调用实现容错

  1. 需要在订单服务shop-order中,为远程调用接口实现容错方法。先为用户服务实现容错。在订单服务中新建cn.mawenda.shop.order.feign.fallback 包,并在 cn.mawenda.shop.order.feign.fallback包下创建RemoteUserServiceFallBack类实现RemoteUserService接口,用于调用用户服务的容错类,如下所示。
@Component
public class RemoteUserServiceFallBack implements RemoteUserService {
    @Override
    public User getUser(Long uid) {
        User user = new User();
        user.setId(-1L);
        return user;
    }
}

注意:容错类需要实现一个被容错的接口,并实现这个接口的方法。

  1. 在订单 RemoteUserService 接口上指定容错类,如下所示。
@FeignClient(value = ServerConstant.USER_SERVER, fallback = RemoteUserServiceFallBack.class)
public interface RemoteUserService {

    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);

}

  1. 订单服务重复以上操作,如下所示。
@Component
public class RemoteProductServiceFallBack implements RemoteProductService {

    @Override
    public Product getProduct(Long pid) {
        Product product = new Product();
        product.setId(-1L);
        return product;
    }

    @Override
    public Result<Integer> updateCount(Long pid, Integer count) {
        Result result = new Result();
        result.setCode(1001);
        result.setCodeMsg("触发了容错机制");
        return result;
    }
}

@FeignClient(value = ServerConstant.PRODUCT_SERVER, fallback = RemoteProductServiceFallBack.class)
public interface RemoteProductService {

    /**
     * 获取商品信息
     */
    @GetMapping(value = "/product/get/{pid}")
    Product getProduct(@PathVariable("pid") Long pid);

    /**
     * 更新库存数量
     */
    @GetMapping(value = "/product/update_count/{pid}/{count}")
    Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);

}
  1. 修改订单微服务的业务实现类中提交订单的业务方法,如下所示。
//#####################省略N行代码##########################
User user = remoteUserService.getUser(orderParams.getUserId());
if (user == null){
    throw new RuntimeException("未获取到用户信息: " + JSONObject.toJSONString(orderParams));
}
if (user.getId() == -1){
    throw new RuntimeException("触发了用户微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}

Product product = remoteProductService.getProduct(orderParams.getProductId());
if (product == null){
    throw new RuntimeException("未获取到商品信息: " + JSONObject.toJSONString(orderParams));
}
if (product.getId() == -1){
    throw new RuntimeException("触发了商品微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}

//#####################省略N行代码##########################

Result<Integer> result = remoteProductService.updateCount(orderParams.getProductId(),orderParams.getCount());
if (result.getCode() == 1001){
    throw new RuntimeException("触发了商品微服务的容错逻辑: " + JSONObject.toJSONString(orderParams));
}
if (result.getCode() != HttpCode.SUCCESS){
    throw new RuntimeException("库存扣减失败");
}
//#####################省略N行代码##########################

3.3 测试效果

  1. 停掉所有产品服务,只保留订单服务、用户服务,再次请求创建订单接口,结果如下所示。

image.png

  1. 停掉所有用户服务,只保留订单服务、产品服务,再次请求创建订单接口,结果如下所示。

image.png

  1. 只保留订单服务,再次请求,结果如下所示。

image.png

  1. 项目集成Sentinel成功实现了服务的容错功能。

4 容错扩展

如果想要在订单微服务中获取到容错时的具体信息时,可以按照如下方式实现容错方案。

4.1 实现容错时获取异常

  1. 在订单服务 shop-order 中新建 cn.mawenda.shop.order.feign.fallback.factory 包,在该包中新建RemoteUserServiceFallBackFactory 类,并实现 FallbackFactory 接口,FallbackFactory 接口的泛型指定为 RemoteUserService,源码如下所示。
@Component
public class RemoteUserServiceFallBackFactory implements FallbackFactory<RemoteUserService> {
    @Override
    public RemoteUserService create(Throwable throwable) {
        return new RemoteUserService() {
            @Override
            public User getUser(Long uid) {
                User user = new User();
                user.setId(-1L);
                return user;
            }
        };
    }
}
  1. 在订单服务的 cn.mawenda.shop.order.feign.RemoteUserService 接口上的@FeignClient注解上指定fallbackFactory属性,如下所示。
@FeignClient(value = ServerConstant.USER_SERVER, fallbackFactory = RemoteUserServiceFallBackFactory.class)
public interface RemoteUserService {

    @GetMapping(value = "/user/get/{uid}")
    User getUser(@PathVariable("uid") Long uid);

}
  1. 在订单服务 shop-order 中 cn.mawenda.shop.order.feign.fallback.factory包下新建RemoteProductServiceFallBackFactory 类,并实现 FallbackFactory 接口,FallbackFactory 接口的泛型指定为 RemoteProductService,源码如下所示。
@Component
public class RemoteProductServiceFallBackFactory implements FallbackFactory<RemoteProductService> {
    @Override
    public RemoteProductService create(Throwable throwable) {
        return new RemoteProductService(){
            
            @Override
            public Product getProduct(Long pid) {
                Product product = new Product();
                product.setId(-1L);
                return product;
            }

            @Override
            public Result<Integer> updateCount(Long pid, Integer count) {
                Result result = new Result();
                result.setCode(1001);
                result.setCodeMsg("触发了容错机制");
                return result;
            }
        };
    }
}
  1. 在订单服务的 cn.mawenda.shop.order.feign.RemoteProductService 接口上的@FeignClient注解上指定fallbackFactory属性,如下所示。
@FeignClient(value = ServerConstant.PRODUCT_SERVER, fallbackFactory = RemoteProductServiceFallBackFactory.class)
public interface RemoteProductService {

    /**
     * 获取商品信息
     */
    @GetMapping(value = "/product/get/{pid}")
    Product getProduct(@PathVariable("pid") Long pid);

    /**
     * 更新库存数量
     */
    @GetMapping(value = "/product/update_count/{pid}/{count}")
    Result<Integer> updateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);

}
  1. 测试效果

与本文 3.3 测试效果一样,这里就不在展示了。
注意:使用Sentinel实现服务容错时,fallback和fallbackFactory只能使用其中一种方式实现服务容错,二者不能同时使用。

5 Sentinel核心功能

Sentinel核心功能.png

5.1 流量控制

在高并发、大流量场景下,进入系统的流量如果不加控制的话,系统就很有可能会被流量压垮。所以,在流量正式进入系统之前,需要对流量进行控制,以便使流量均匀、可控的方式进入系统。
Sentinel作为一个非常出色的容错组件,能够将不可控的流量经过处理转化成均匀、可控的流量。

5.2 熔断降级

如果检测到系统中的某个调用链路中某个节点出现故障,比如请求超时、服务宕机或者异常比超出一定阈值时,就会对出现故障的节点的调用频率进行限制,甚至不调用出现故障的节点,让请求能够快速失败并返回,以最大程度避免影响到其他节点的服务而导致系统的级联故障。
Sentinel主要通过 限制并发线程数和响应时间 对资源的访问进行降级。

5.2.1 限制并发线程数进行降级

Sentinel可以通过限制服务节点的并发线程数量,来减少对其他服务节点的影响。例如,当某个服务节点出现故障,例如响应时间变长,或者直接宕机。此时,对服务的直接影响就是会造成请求线程数的不断堆积。如果这些堆积的线程数达到一定的数量后,对当前服务节点的后续请求就会被拒绝,等到堆积的线程完成任务后再开始继续接收新的请求。

5.2.2 通过响应时间进行降级

Sentinel除了可以通过限制并发线程数进行降级外,也能够通过响应时间进行降级。如果依赖的服务出现响应时间过长的情况,则所有对该服务的请求都会被拒绝,直到过了指定的时间窗口之后才能再次访问该服务。

5.3 系统负载保护

Sentinel提供了系统维度的自适应保护能力。当系统的压力和负载比较高的时候,如果还持续让大量的请求进入系统,此时就有可能将系统压垮,进而导致系统宕机。Sentinel会在集群环境下,将本应服务器A承载的流量转发到其他服务器上,比如转发到服务器B上。如果此时服务器B也处于高负载的状态,则Sentinel会提供相应的保护机制,让系统的入口流量和系统的整体负载达到平衡,让系统整体可用,并且能够最大限度的处理请求。

6 Sentinel核心规则

Sentinel的核心规则包括流控规则、熔断规则、热点规则、授权规则和系统规则,每种规则的配置方式不同。
简单的使用在前边已经描述过了,以下是针对Sentinel核心规则更详细的描述与操作实例。
Sentinel核心规则.png

6.1 流控规则

Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性

6.1.1 簇点链路规则

  1. 在Sentinel管理界面,点击簇点链路,可以看到之前访问过的接口,如下所示。

image.png

  1. 点击右侧的流控按钮,会弹出新增轮流空规则的提示框,如下所示。

image.png
各配置项说明如下所示。

  • 资源名:资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。
  • 针对来源:具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。
  • 阈值类型:QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。
  • 单机阈值:与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。
  • 是否集群:选中则表示集群环境,不选中则表示非集群环境。

6.1.2 配置简单限流

  1. 针对 http://localhost:8080/order/test_sentinel 接口进行简单的配置,在新增流控规则里阈值类型选择QPS,单机阈值输入3,表示每秒钟的请求量如果超过3,则会触发Sentinel的限流操作。

image.png

  1. 点击新增按钮后,会为 http://localhost:8080/order/test_sentinel 接口新增一条限流规则,如下所示。

image.png
3.使用测试工具测试 http://localhost:8080/order/test_sentinel 接口,当每秒钟的刷新频率超过3次时,会出现如下所示的提示信息。
image.png

6.1.3 配置流控模式

  1. 点击 http://localhost:8080/order/test_sentinel 接口流控规则后面的编辑按钮,打开编辑流控规则弹出框(如果是首次配置的话,就是新增流控规则弹出框),点击高级选项配置。

image.png

  1. 高级选项内容如下所示。

image.png
Sentinel主要提供了三种流控模式,分别为直接、关联和链路。

  • 直接:默认的流控模式,当接口达到限流条件时,直接开启限流功能。
  • 关联:当关联的资源达到限流条件时,开启限流功能。
  • 链路:当从某个接口请求过来的资源达到限流条件时,开启限流功能。
6.1.3.1 直接流控模式

我们在订单服务集成的就是Sentinel的直接流控模,上面使用的也是直接流控模式,此处不在赘述。

6.1.3.2 关联流控模式
  1. 在订单服务中 cn.mawenda.shop.order.controller.OrderController 类中新增 /test_sentinel2接口,如下所示。
@GetMapping(value = "/test_sentinel2")
public String testSentinel2(){
    log.info("测试Sentinel2");
    return "sentinel2";
}
  1. 添加的接口通过测试工具访问下该接口,使该接口让Sentinel检测到管理界面(后续不在赘述,需自己注意)。

image.png
这里有个小插曲,在新增完接口后,重启订单服务,发现Sentinel里边之前配置的内容就没有了,所以重启订单服务后,这两个接口都重新请求下。
image.png

  1. 进入http://localhost:8080/order/test_sentinel接口配置流控规则的页面,如下所示。

image.png
重启服务后,之前配置的内容没有了,此处我是重新配置的。

  1. 在流控模式中选择关联,在关联资源中输入/test_sentinel2,如下所示。

image.png
点击保存按钮,保存配置。

  1. 打开JMeter,对http://localhost:8080/order/test_sentinel2接口进行测试,使其每秒钟的访问次数大于3,JMeter的具体配置如下所示。

在线程组中配置每秒访问4次。
image.png
Http请求配置完成后,启动请求。
image.png

  1. 测试请求 http://localhost:8080/order/test_sentinel接口,发现已经触发了Sentinel的限流功能。

image.png

  1. 关联流控模式测试完毕。
6.1.3.3 链路流控模式
  1. 在订单服务的cn.mawenda.shop.order.service包中新增SentinelService接口,用于测试流控模式,如下所示。
public interface SentinelService {

    /**
     * 测试方法
     */
    void sendMessage();
}

  1. 在订单微服务的cn.mawenda.shop.order.service.impl包中新增SentinelServiceImpl类,实现SentinelService接口,如下所示。
@Service
public class SentinelServiceImpl implements SentinelService {

    @Override
    @SentinelResource("sendMessage")
    public void sendMessage() {
        System.out.println("测试Sentinel的链路流控模式");
    }

}

在sendMessage()方法上使用了@SentinelResource注解, @SentinelResource注解会在文章后面介绍,这里不再赘述。

  1. 在订单微服务的 cn.mawenda.shop.order.controller 包下新建SentinelController类,用于测试接口,源码如下所示。
@Slf4j
@RestController
public class SentinelController {

    @Autowired
    private SentinelService sentinelService;

    @GetMapping(value = "/request_sentinel1")
    public String requestSentinel1(){
        log.info("测试Sentinel1");
        sentinelService.sendMessage();
        return "sentinel1";
    }
    @GetMapping(value = "/request_sentinel2")
    public String requestSentinel2(){
        log.info("测试Sentinel2");
        sentinelService.sendMessage();
        return "sentinel2";
    }
}
  1. 升级SpringCloud Alibaba的依赖版本到2.2.7.RELEASE,升级SpringCloud版本到Hoxton.SR12,并且加入SpringBoot的管理依赖。主要的修改的是shop-springcloud-alibaba父工程的pom.xml配置,修改后的配置文件如下所示。
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
    <spring.boot.version>2.3.12.RELEASE</spring.boot.version>
    <logback.version>1.1.7</logback.version>
    <slf4j.version>1.7.21</slf4j.version>
    <common.logging>1.2</common.logging>
    <fastjson.version>1.2.51</fastjson.version>
    <mybatis.version>3.4.6</mybatis.version>
    <mybatis.plus.version>3.4.1</mybatis.plus.version>
    <mysql.jdbc.version>8.0.19</mysql.jdbc.version>
    <druid.version>1.1.10</druid.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  1. 升级Nacos,将Nacos注册中心由1.4.3版本升级为2.1.0版本,并进入Nacos的bin目录,输入如下命令启动Nacos。
# win
startup.cmd -m standalone
# mac
startup.sh -m standalone

注意:此处作者环境是mac m2环境,由于nacos2.1.0不兼容,将Nacos源码集成到项目中,新创建了一个shop-nacos服务,如下所示,
image.png
后续会更新集成的教程,暂不赘述,也可以通过docker的方式进行部署,推荐的镜像版本为nacos/nacos-server:v2.1.1-slim。

  1. 在订单服务的application.yml文件中新增链路配置,如下所示。
spring:
  cloud:
    sentinel:
      web-context-unify: false  # 链路配置
  1. 用测试工具分别请求 http://localhost:8080/order/request_sentinel1 和http://localhost:8080/order/request_sentinel2,查看Sentinel中的簇点链路,如下所示。

image.png

  1. 点击sendMessage后面的流控按钮,如下所示。

image.png
image.png
点击新增按钮保存配置,此时,
通过http://localhost:8080/order/request_sentinel1接口调用cn.mawenda.shop.order.service.SentinelService.sendMessage()方法时,如果调用的频率每秒钟超过3次,就会触发Sentinel的限流操作。
通过http://localhost:8080/order/request_sentinel2接口调用cn.mawenda.shop.order.service.SentinelService.sendMessage()方法时,则不受限制。

  1. 在浏览器访问http://localhost:8080/order/request_sentinel1接口,使每秒请求次数超过3,则会触发Sentinel限流操作,如下所示。

image.png

6.1.4 流控效果说明

6.1.4.1 快速失败

快速失败:会直接失败,抛出异常,期间不会做任何其他的处理操作。
image.png

6.1.4.2 Warm Up

从开始阈值到最大QPS阈值会有一个缓冲,可以设置一个预热时长,这个选项比较适合突发瞬时高并发流量的场景,能够将突发的高并发流量转换为均匀、缓慢增长的场景。
image.png

6.1.4.1 排队等待

能够使请求均匀的通过,单机的阈值为每秒通过的请求数量,其余的请求会排队等待。另外,还会设置一个超时时间,当请求超过超时时间未处理时,会被丢弃。
image.png

6.2 熔断规则

降级规则一般情况下,指的是满足某些条件时,对服务进行降级操作。

6.2.1 熔断规则概述

Sentinel主要提供了三个熔断策略,分别为:慢调用比例、异常比例和异常数。
image.png

6.2.2 慢调用比例熔断示例

  1. 首先在浏览器中访问 http://localhost:8080/order/request_sentinel2 接口,在Sentinel的簇点链路里找到/request_sentinel2

image.png

  1. 点击降级按钮,进入降级规则配置界面,按照如下方式进行配置。

image.png
上述配置标识最大响应时长为1ms,比例阈值达到0.1时,会触发熔断操作,熔断时长为2,最小请求数为5。

  1. 点击新增按钮后,不断在浏览器中刷新 http://localhost:8080/order/request_sentinel2 接口,会发现触发Sentinel 熔断操作,如下所示。

image.png

6.2.3 异常比例示例

  1. 在订单微服务的 cn.mawenda.shop.order.controller.SentinelController 类中定义一个成员变量count,用来记录访问数量,同时,增加一个 request_sentinel3() 方法,并部署,用于测试基于异常比例的熔断,如下所示。
private int count = 0;
@GetMapping(value = "/request_sentinel3")
@SentinelResource("request_sentinel3")
public String requestSentinel3(){
    log.info("测试Sentinel4");
    count++;
    //模拟异常,比例为50%
    if (count % 2 == 0){
        throw new RuntimeException("演示基于异常比例熔断");
    }
    return "sentinel2";
}
  1. 首先,在浏览器中访问 http://localhost:18080/order/request_sentinel3 ,在在Sentinel的簇点链路里找到/request_sentinel3。

image.png

  1. 点击降级按钮,进入熔断规则配置界面,按照如下方式进行配置。

image.png
在 cn.mawenda.shop.order.controllerr.requestSentinel3 ()方法中,设置的异常比例为50%,这里在Sentinel的比例阈值中设置的异常比例为0.3,也就是30%,所以,在访问 http://localhost:8080/order/request_sentinel3 接口时,会触发Sentinel的熔断操作。

  1. 点击新增按钮后,不断在浏览器中刷新 http://localhost:8080/order/request_sentinel3 ,会发现触发Sentinel 熔断操作,如下所示。

image.png

6.2.3 异常数示例

  1. 首先在浏览器中访问http://localhost:8080/order/request_sentinel3,在Sentinel的簇点链路里找到/request_sentinel3。

image.png

  1. 点击降级按钮,进入熔断规则配置界面,按照如下方式进行配置。

image.png
上述配置表示,在1秒钟内最少请求2次,当异常数大于1时,会触发熔断操作,熔断的时长为5秒。

  1. 点击新增按钮后,不断在浏览器中刷新 http://localhost:8080/order/request_sentinel3 ,会发现触发Sentinel 熔断操作,如下所示。

image.png

6.3 热点规则

Sentinel的热点规则可以将流量规则控制到具体的参数上。

6.3.1 热点规则概述

Sentinel的热点规则可以根据具体的参数来控制流量规则,适用于根据不同参数进行流量控制的场景。

6.3.2 热点规则示例

  1. 在订单微服务的 cn.mawenda.shop.order.controller.SentinelController 类中增加一个 request_sentinel4() 方法,带有一个String类型的参数header和一个String类型的参数body,如下所示。
@GetMapping(value = "/request_sentinel4")
@SentinelResource("request_sentinel4")
public String requestSentinel4(String header, String body){
    log.info("测试Sentinel4");
    return "sentinel4";
}
  1. 首先在浏览器中访问http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4,如下所示。

image.png

  1. 点击热点按钮,在弹出的热点规则配置框中的参数索引中输入0,单机阈值输入1,统计窗口时长输入1,如下所示。

image.png
表示对requestSentinel4()方法的第一个参数header进行限流,如果每秒钟访问的次数超过1次,则触发限流。

  1. 保存配置后,在浏览器中不断访问http://localhost:8080/order/request_sentinel4?header=header,当每秒访问的频率超过1次时,会触发Sentinel的限流操作,如下所示。

image.png
不断访问 http://localhost:8080/order/request_sentinel4?body=body 接口 ,则不会触发限流操作。
image.png

6.3.3 热点规则高级选项示例

  1. 在弹出的热点规则配置框中打开高级选项,在参数类型中选择java.lang.String,因为在参数索引中输入0,表示的是对header参数限流,而header参数是String类型的。在参数值里输入header,也就是为参数名为header的参数赋值为字符串header。限流阈值为1,如下所示。

image.png

  1. 点击添加按钮后如下所示。

image.png

  1. 点击保存按钮,在浏览器不断刷新http://localhost:8080/order/request_sentinel4?header=header,会触发Sentinel的限流操作。

image.png

6.4 授权规则

授权规则能够根据调用来源判断还否允许执行本次请求。

6.4.1 授权规则概述

在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。

6.4.2 授权规则示例

  1. 在订单微服务shop-order中新建cn.mawenda.shop.order.parser包,并创建MyRequestOriginParser类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser接口,用来处理请求的来源。代码如下所示。
/**
 * @Author: Ma.wenda
 * @Date: 2024-01-30 11:36
 * @Version: 1.0
 **/
@Component
public class MyRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getParameter("serverName");
    }
}
  1. 启动shop-order微服务,在浏览器中访问http://localhost:8080/order/request_sentinel4,在Sentinel的簇点链路里找到/request_sentinel4。

image.png

  1. 点击授权按钮,进入授权规则配置框,按照如下方式进行配置。

image.png
其中,流控应用填写的是test,授权类型为黑名单。这里要结合新建的MyRequestOriginParser类进行理解,MyRequestOriginParser类的parseOrigin()方法如下所示。
image.png
parseOrigin()方法中直接返回了从HttpServletRequest中获取的serverName参数,而在上图中的流控应用中输出的是test,授权类型为黑名单。
所以,如果我们访问http://localhost:8080/order/request_sentinel4?serverName=test的话,是处于黑名单的状态,无法访问。

  1. 点击新增按钮后,不断在浏览器中刷新http://localhost:8080/order/request_sentinel4?serverName=test,会发现无法访问,被Sentinel限流了。

image.png

6.5 系统规则

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

6.5.1 系统规则概述

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

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

6.5.2 系统规则示例

  1. 在订单微服务中新建cn.mawenda.shop.order.handler包,并创建MyUrlBlockHandler类,实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler接口,用来捕获系统级Sentinel异常,代码如下所示。
@Component
public class MyUrlBlockHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
        String msg = null;
        if (e instanceof FlowException) {
            msg = "限流了";
        } else if (e instanceof DegradeException) {
            msg = "降级了";
        } else if (e instanceof ParamFlowException) {
            msg = "热点参数限流";
        } else if (e instanceof SystemBlockException) {
            msg = "系统规则(负载/...不满足要求)";
        } else if (e instanceof AuthorityException) {
            msg = "授权规则不通过";
        }
        // http状态码
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.setContentType("application/json;charset=utf-8");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", 500);
        jsonObject.put("codeMsg", msg);
        response.getWriter().write(jsonObject.toJSONString());
    }
}
  1. 在订单微服务的io.binghe.shop.order.controller.SentinelController类中新增requestSentinel5()方法,如下所示。
@GetMapping(value = "/request_sentinel5")
@SentinelResource("request_sentinel5")
public String requestSentinel5(){
    log.info("测试Sentinel5");
    return "sentinel5";
}
  1. 重启订单微服务后,在浏览器中访问http://localhost:8080/order/request_sentinel5,在Sentinel的簇点链路里找到/request_sentinel5。

image.png

  1. 点击流控按钮,进入流控规则配置框,按照如下方式进行配置。

image.png

  1. 点击新增按钮后,在浏览器中不断刷新http://localhost:8080/order/request_sentinel5,会显示如下信息。

image.png
说明触发了系统规则,捕获到了Sentinel全局异常。

6.6 @SentinelResource注解

使用Sentinel时,可以使用@SentinelResource注解来指定异常处理策略。

6.6.1 @SentinelResource注解概述

在Sentinel中,指定发生异常时的处理策略非常简单,只需要使用@SentinelResource注解即可,@SentinelResource注解的源码如下所示。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {

	//资源名称
    String value() default "";
	//entry类型,标记流量的方向,取值IN/OUT,默认是OUT
    EntryType entryType() default EntryType.OUT;
    int resourceType() default 0;
	//处理BlockException的函数名称,函数要求:
    //1. 必须是 public
    //2.返回类型 参数与原方法一致
    //3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置
    //blockHandlerClass ,并指定blockHandlerClass里面的方法。
    String blockHandler() default "";
	
    //存放blockHandler的类,对应的处理函数必须static修饰。
    Class<?>[] blockHandlerClass() default {};
	
    //用于在抛出异常的时候提供fallback处理逻辑。 fallback函数可以针对所
    //有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
    //1. 返回类型与原方法一致
    //2. 参数类型需要和原方法相匹配
    //3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。
    String fallback() default "";
	
    //存放fallback的类。对应的处理函数必须static修饰。
    String defaultFallback() default "";

	//用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
    //行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
    //1. 返回类型与原方法一致
    //2. 方法参数列表为空,或者有一个 Throwable 类型的参数。
    //3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。
    Class<?>[] fallbackClass() default {};

  	//指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
    Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
    
	//需要trace的异常
    Class<? extends Throwable>[] exceptionsToIgnore() default {};
}

6.6.2 @SentinelResource注解示例

  1. 在订单微服务的cn.mawenda.shop.order.service.SentinelService接口中新增sendMessage2()方法,如下所示。
String sendMessage2();
  1. 在订单微服务的cn.mawenda.shop.order.service.impl.SentinelServiceImpl方法中,实现sendMessage2()方法,并且定义一个成员变量count,用来记录请求sendMessage2()方法的次数,同时定义25%的异常率。在sendMessage2()方法上使用@SentinelResource指定了资源的名称、发生BlockException时进入的方法和发生异常时进入的方法,代码如下所示。
private int count = 0;

@Override
@SentinelResource(
        value = "sendMessage2",
        blockHandler = "blockHandler",
        fallback = "fallback")
public String sendMessage2() {
    count ++;
    //25%的异常率
    if (count % 4 == 0){
        throw new RuntimeException("25%的异常率");
    }
    return "sendMessage2";
}

public String blockHandler(BlockException e){
    log.error("限流了:{}", e);
    return "限流了";
}

public String fallback(Throwable e){
    log.error("异常了:{}", e);
    return "异常了";
}
  1. 在订单微服务的cn.mawenda.shop.order.controller.SentinelController类中新增requestSentinel6()方法,在方法中调用cn.mawenda.shop.order.service.SentinelService接口中的sendMessage2()方法,如下所示。
@GetMapping(value = "/request_sentinel6")
public String requestSentinel6(){
    log.info("测试Sentinel6");
    return sentinelService.sendMessage2();
}
  1. 启动订单服务后,在浏览器中访问http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

image.png

  1. 点击流控按钮,进入流控规则配置界面,按照如下进行配置。

image.png

  1. 点击新增按钮后,在浏览器中刷新http://localhost:8080/order/request_sentinel6,当刷新频率每秒超过2次时,浏览器显示如下信息。

image.png

注意:此处按照冰河大佬的教程是流控配置的/request_sentinel6,我配置为/request_sentinel6时,这个限流的blockHandler没有生效,我试着将流控配置的资源名改为sendMessage2,blockHandler就生效了,我猜测可能是sendMessage2方法配置了SentinelResource注解,但是controller中没有配置造成的,这个后续在查询一下资料。

当刷新的次数是4的倍数时,浏览器会显示如下信息。
image.png

6.7 Sentinel持久化

Sentinel中可以自定义配置的持久化来将Sentinel的配置规则持久化到服务器磁盘,使得重启应用或者Sentinel后,Sentinel的配置规则不丢失。

6.7.1 Sentinel持久化概述

我们之前配置的Sentinel规则在程序重启或者Sentinel重启后就会消失不见,此时就需要我们重新配置。如果这发生在高并发、大流量的场景下是不可接受的,Sentinel中可以自定义配置的持久化来解决这个问题。

6.7.2 实现Sentinel持久化

  1. 在订单微服务shop-order中新建cn.mawenda.shop.order.persistence包,并创建SentinelPersistenceRule类,实现com.alibaba.csp.sentinel.init.InitFunc接口,并在SentinelPersistenceRule类中获取应用的名称,覆写init()方法,源码如下所示。
package cn.mawenda.shop.order.persistence;

import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Value;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Sentinel规则持久化
 * @Author: Ma.wenda
 * @Date: 2024-01-30 15:24
 * @Version: 1.0
 **/
public class SentinelPersistenceRule implements InitFunc {

    private String applicationName = "server-order";
    
    @Override
    public void init() throws Exception {
        String ruleDir = System.getProperty("user.home") + "/sentinel-rules/" + applicationName;
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";

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

        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        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<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);

        // 热点参数规则
        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);
    }

    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);
    }
}

  1. 在订单微服务的resources目录下新建META-INF目录,并在META-INF目录下新建services目录,在services目录下新建名称为com.alibaba.csp.sentinel.init.InitFunc的文件,如下所示。

image.png

  1. 在com.alibaba.csp.sentinel.init.InitFunc文件中添加cn.mawenda.shop.order.persistence.SentinelPersistenceRule类的全类名,如下所示。

image.png

  1. 重启订单微服务后,在浏览器中访问http://localhost:8080/order/request_sentinel6,在Sentinel的簇点链路里找到/request_sentinel6。

image.png

  1. 点击流控按钮进入流控规则页面,配置如下所示。

image.png

  1. 点击新增按钮。进入终端,根据我们SentinelPersistenceRule类中配置的ruleDir路径,进入/Users/用户名 目录,windows环境为C:\Users\用户名,可以发现该目录中多了一个sentinel-rules目录。

image.png

  1. 进入sentinel-rules目录,发现存在一个shop-order目录,如下所示。

image.png

  1. 进入shop-order目录,发现会有很多json文件,这些就是持久化的Sentinel的规则配置,如下所示。

image.png
Sentinel的规则配置是可以持久化到数据库中,作者后续会继续探索,后面会增加扩展篇。

7 总结

主要了解了Sentinel对于服务容错多种方案,以及Sentinel的规则配置的持久化,Sentinel可以通过流控、熔断、热点、授权、系统等多维度来提高系统的容错性,从而保证系统的稳定性。
流控分为直接流控模式、关联流控模式、链路流控模式,直接流控模式可以通过QPS与并发量来配置流控措施,也可以通过关联流控模式来监听另外的关联接口,当关联接口达到阈值时,当前接口触发限流操作,链路流控模式可以通过更细粒度的通过调用链路来控制。
熔断分为慢调用比例熔断、异常比例熔断、异常数熔断。
热点规则 授权规则 系统规则提供了多维度的方式来增加容错性。
通过实现InitFunc接口,来实现Sentinel的持久化,将配置好的规则持久化到了本地磁盘上,也了解了可以将规则持久化到数据库中,后续会继续深入探索。
除此之外,在本篇的学习中,出现了很多小插曲,也了解了很多有意思的事情,我的开发环境为mac m2,在升级Nacos时,由于nacos2.1.0不支持m2环境,我通过docker compose的方式部署了nacos、sentinel以及各微服务,每次改动都需要重新部署,觉得很麻烦,又去了解了nacos源码方式启动,也解决了不兼容的问题,成功在m2环境启动,并且将nacos集成到了我的项目中,shop-nacos微服务,更方便管理,后续还会继续学习,加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值