【微服务架构】SpringCloud Alibaba(五):服务容错 Sentinel(sentinel使用、流控规则)

在这里插入图片描述
个人主页:道友老李
欢迎加入社区:道友老李的学习社区

一、SpringCloud Alibaba

Spring Cloud Alibaba是阿里巴巴提供的一站式微服务解决方案,是Spring Cloud体系中的一个重要分支,它将阿里巴巴在微服务领域的实践经验和开源技术进行了整合,为开发者提供了一系列便捷的工具和组件,用于构建分布式微服务应用。以下是其详细介绍:

1、核心组件

  • Nacos:用于服务注册与发现以及配置管理。它可以帮助微服务实例自动注册到注册中心,并能够动态获取配置信息,使应用程序能够灵活地应对配置的变化,无需重启服务。
  • Sentinel:主要用于流量控制、熔断降级等功能。它可以保护微服务免受高并发、流量异常等情况的影响,确保系统在压力下能够稳定运行,避免因个别服务出现问题而导致整个系统崩溃。
  • RocketMQ:是一款高性能、高可靠的分布式消息队列。它在微服务架构中常用于实现异步消息传递、解耦系统组件之间的依赖关系,从而提高系统的整体性能和可扩展性。
  • Seata:致力于提供分布式事务解决方案,确保在分布式系统中数据的一致性。它通过对事务的协调和管理,使得多个微服务之间在进行数据交互时能够遵循ACID原则。

2、优势

  • 一站式解决方案:涵盖了微服务架构中的多个关键领域,包括服务治理、配置管理、流量控制、分布式事务等,开发者无需再从多个不同的开源项目中进行整合,大大降低了微服务架构的搭建和维护成本。
  • 与Spring Cloud生态的深度集成:基于Spring Cloud的编程模型和规范进行开发,使得熟悉Spring Cloud的开发者能够快速上手并轻松集成到现有的Spring Cloud项目中,充分利用Spring Cloud的各种特性和优势。
  • 阿里巴巴的技术实力和实践经验支持:得益于阿里巴巴在大规模分布式系统开发和运营方面的丰富经验,Spring Cloud Alibaba的组件经过了实际生产环境的考验,具有较高的稳定性、性能和可扩展性,能够应对各种复杂的业务场景和高并发流量。

3、应用场景

  • 电商系统:在电商业务中,存在多个微服务,如商品服务、订单服务、库存服务等。Spring Cloud Alibaba可以通过Nacos进行服务注册与发现,使用Sentinel对各个服务的流量进行控制,利用RocketMQ实现异步消息通知,比如下单成功后异步通知库存服务扣减库存,通过Seata保证分布式事务的一致性,确保订单和库存等数据的准确性。
  • 金融系统:金融领域对数据一致性和系统稳定性要求极高。Spring Cloud Alibaba的Seata可以确保在多个金融业务操作之间的分布式事务一致性,如转账操作涉及到两个不同账户服务之间的资金变动。Nacos可以提供配置管理,方便对金融业务的各种配置参数进行动态调整,Sentinel则可以防止因突发的高并发交易对系统造成冲击。
  • 物联网(IoT)平台:物联网场景中,大量的设备会产生实时数据并上传到云端。Spring Cloud Alibaba可以通过Nacos管理各个物联网服务的注册与发现,使用RocketMQ接收和处理大量的设备数据消息,进行异步处理和分发。Sentinel可以对物联网服务的流量进行控制,防止因设备数据突发增长导致系统过载。

二、Sentinel快速开始

在官方文档中,定义的Sentinel进行资源保护的几个步骤:

  1. 定义资源
  2. 定义规则
  3. 检验规则是否生效

2.1 抛出异常的方式定义资源

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

2.2 api的实现

<font color=“red”>dyll-user项目中</font>

  • 引入依赖

    <dependency>
         <groupId>com.alibaba.csp</groupId>
         <artifactId>sentinel-core</artifactId>
         <version>1.8.1</version>
    </dependency>
    
  • 代码测试

@Slf4j
@RestController
public class ApiController {

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

    /**
     * 定义流控规则
     */
    @PostConstruct
    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置受保护的资源
        rule.setResource(RESOURCE_NAME);
        // 设置流控规则 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        rule.setCount(1);
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

缺点:

  • 业务侵入性很强,需要在controller中写入非业务代码.
  • 配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则

2.3 注解方式定义资源

@SentinelResource注解实现

在定义了资源点之后,我们可以通过Dashboard来设置限流和降级策略来对资源点进行保护。同时还能 通过@SentinelResource来指定出现异常时的处理策略。

@SentinelResource用于定义资源,并提供可选的异常处理和fallback 配置项。其主要参数如下:

属性作用
value资源名称
entryTypeentry类型,标记流量的方向,取值IN/OUT,默认是OUT
blockHandler处理BlockException的函数名称,函数要求:<br>1.必须是public<br/>2.返回类型 参数与原方法一致<br/>3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置blockHandlerClass,并指定blockHandlerClass里面的方法。
blockHandlerClass存放blockHandler的类,对应的处理函数必须static修饰。
fallback用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:<br/>1.返回类型与原方法一致<br/>2.参数类型需要和原方法相匹配<br/>3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。
fallbackClass存放fallback的类。对应的处理函数必须static修饰。
defaultFallback用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:<br/>1.返回类型与原方法一致<br/>2.方法参数列表为空,或者有一个Throwable类型的参数。<br/>3.默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。
exceptionsToIgnore指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
exceptionsToTrace需要trace的异常
  • 引入依赖

    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-annotation-aspectj</artifactId>
        <version>1.8.1</version>
    </dependency>
    
  • 切面支持

    @Configuration
    public class SentinelAspectConfiguration {
    
        @Bean
        public SentinelResourceAspect sentinelResourceAspect() {
            return new SentinelResourceAspect();
        }
    }
    
  • 代码编写

    <font color=“red”>注意 这里面必须把以前的@PostConstruct导入的规则给注释掉,否则可能有冲突</font>

@Slf4j
@RestController
public class SentinelResourceController {

    //http://localhost:8001/testAspect/12
    @GetMapping("/testAspect/{id}")
    @SentinelResource(value = "testAspect",
            fallback = "fallback",fallbackClass = CommonException.class,
            blockHandler = "handleException",blockHandlerClass = CommonException.class
    )
    public Result testAspect(@PathVariable("id") Integer id){
        if(Integer.compare(id,Integer.parseInt("0")) == -1){
            throw new IllegalArgumentException("参数异常");
        }
        log.info("处理业务信息");
        return Result.ok("测试注解方式限流正常");
    }


    @PostConstruct
    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置受保护的资源
        rule.setResource("testAspect");
        // 设置流控规则 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        rule.setCount(1);
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

@Slf4j
public class CommonException {
    public static Result fallback(Integer id,Throwable e){
        log.error("出现业务异常");
        return Result.error(-1,"===业务异常==");
    }

    public static Result handleException(Integer id, BlockException e){
        log.error("触发限流机制");
        return Result.error(-2,"====触发限流机制==");
    }
}

这里的规则,需要我们自己编写,并且我们这里

2.4 整合springboot

  1. 引入依赖

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    
  2. 修改配置

    spring: 
    	cloud:
    		sentinel: 
    			transport:
                    port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
                    dashboard: localhost:8080 # 指定控制台服务的地址
    

2.5 引入控制台

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

第1步:下载jar包,解压到文件夹

https://github.com/alibaba/Sentinel/releases

第2步:启动控制台

# 直接使用jar命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar

把dashboard自己也当成一个资源加入到了dashboard中来进行监控,如果不想把dashboard自己加入控制台监控可以使用简单启动指令如下:

java -Dserver.port=8080 -jar sentinel-dashboard-1.8.1.jar

第3步:访问控制台

用户可以通过如下参数进行配置:

-Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel;

-Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel;

-Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;

访问http://localhost:8080/#/login ,默认用户名密码: sentinel/sentinel

<font color=“red”>Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;</font>

image.png

补充:了解控制台的使用原理

Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上, 即在微服务中指定控制台的地址, 并且还要开启一个跟控制台传递数据的端口, 控制台也可以通过此端口调用微服务中的监控程序获取微服务的各种信息。

image.png

三、Sentinel规则(dashboard)

3.1 流控规则

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

Field说明默认值
resource资源名,即限流规则的作用对象
count限流阈值
grade限流阈值类型(QPS 或并发线程数)QPS 模式
limitApp流控针对的调用来源default,代表不区分调用来源
strategy调用关系限流策略:直接、链路、关联直接
controlBehavior流量控制效果(直接拒绝、Warm Up、匀速排队)直接拒绝
clusterMode是否集群限流

流量控制,其原理是监控应用流量的QPS(每秒查询率)并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

第1步: 点击【簇点链路】,我们就可以看到访问过的接口地址,然后点击对应的【流控】按钮,进入流控规则配置页面。新增流控规则界面如下:

image.png

  • **资源名:**唯一名称,默认是请求路径,可自定义。

  • **针对来源:**Sentinel可以针对调用者进行限流,填写【微服务名】,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)。

  • 阈值类型/单机阈值:

    • QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候,进行限流。
    • 线程数:当调用该接口的线程数达到阈值的时候,进行限流。
  • **是否集群:**暂不需要集群

接下来我们以QPS为例来研究限流规则的配置。

3.1.1 简单配置

QPS
  1. 代码

    //com.dyll.controller.SimpleController
    @RestController
    public class SimpleController {
        @GetMapping("/simple/qps")
        public String qps(){
            //模拟一次网络延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return  "简单测试qps";
        }
    
    }
    
  2. 流控规则

    image.png

  3. 快速访问/simple/qps 接口,观察效果。

此时发现,当QPS > 1的时候,服务就不能正常响应,而是返回Blocked by Sentinel (flow limiting)结果。

http://localhost:8001/simple/qps

image.png

<font color=“red”>BlockException异常统一处理</font>

springwebmvc接口资源限流入口在HandlerInterceptor的实现类AbstractSentinelInterceptor的preHandle方法中,对异常的处理是BlockExceptionHandler的实现类

sentinel 1.7.1 引入了sentinel-spring-webmvc-adapter.jar

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

package com.dyll.handle;

@Slf4j
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        log.info("BlockExceptionHandler BlockException================"+e.getRule());

        Result r = null;

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

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

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

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

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

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

    }
}
线程数

并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。

【为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。】

<font color=“red”>Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。</font>

  1. 代码

    //com.dyll.controller.SimpleController#testThread
    @GetMapping("/simple/thread")
    public String testThread(){
        //模拟一次网络延时
        try {
            Thread.sleep(2*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  "简单测试线程";
    }
    
  2. 流控规则

    image.png

  3. 快速访问/simple/thread接口,观察效果。

    image.png

3.1.2 配置流控模式

点击上面设置流控规则的**【编辑】按钮,然后在编辑页面点击【高级选项】**,会看到有流控模式一栏。

image.png

sentinel共有三种流控模式,分别是:

  • 直接(默认):指定来源对于该资源的访问达到限流条件时,开启限流。
  • 关联:当与该资源设置了关联的资源达到限流条件(来源+阈值类型+单机阈值)时,开启限流 [适合做应用让步]
  • 链路:当从某个上游资源接口访问过来的流量达到限流条件时,开启限流。

下面呢分别演示三种模式:

直接流控模式

直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控模式。

关联流控模式

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_dbwrite_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategyRuleConstant.STRATEGY_RELATE 同时设置 refResourcewrite_db。这样当写库操作过于频繁时,读数据的请求会被限流。

image.png

  1. 代码

    @RestController
    public class FlowControllerModleContoller {
    
        @GetMapping("/flowModle/read")
        public String readRead(Integer orderId){
            return  "读资源";
        }
        @GetMapping("/flowModle/write")
        public String relation2(Integer orderId){
            return  "写资源";
        }
    }
    
  2. 流控规则

    image.png

  3. jmeter测试

    image.png

    image.png

  4. 查看监控图

    image.png

链路流控模式

链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对 来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细

image.png

  1. 代码

    编写controller

    //com.dyll.controller.FlowControllerModleContoller
    @GetMapping("/linkModle/test1")
    public String test1(Integer userId){
        userService.getUser();
        return  "成功";
    }
    @GetMapping("/linkModle/test2")
    public String test2(Integer userId){
        userService.getUser();
        return  "成功";
    }
    

    编写service

    @Service
    public class UserService {
    
        @SentinelResource(value = "getUser")
        public String getUser(){
            return "获取到用户信息";
        }
    }
    
  2. 流控规则

    image.png

  3. 添加配置

    我们需要将web-context-unify参数设置为false,将其配置为false既可以根据不同的URL进行链路限流,如果不配置将不会生效

    image.png

  4. jmeter测试

    image.png

    image.png

  5. 访问/linkModle/test1

image.png

3.1.3 流控效果

当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:快速失败(直接拒绝)、Warm Up(预热)、匀速排队(排队等待)。对应 FlowRule 中的 controlBehavior 字段。

image.png

快速失败(默认)

<font color = “red”>当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException</font>这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

Warm Up

它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值。

即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

<font color=“red”>冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。 </font>

通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:

image.png

场景主要用于启动需要额外开销的场景,例如建立数据库连接等。

  1. 代码
@RestController
public class FlowControlEffectController {

    @RequestMapping("/warmup")
    public String testWarmUp(){
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "测试流控效果===热启动";
    }
}

2、流控规则

image.png

3、jmeter测试

image.png

4、查看监控结果

查看你会发现首先会达到3 (阈值的1/3)然后等3秒后会达到阈值10

image.png

排队等待

让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。

该方式的作用如下图所示:

image.png

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

<font color=“red”>注意:匀速排队模式暂时不支持 QPS > 1000 的场景。</font>【因为他的单位毫秒,所以最多是1秒通过一个,也就是1000qps】

  1. 代码

    com.dyll.controller.FlowControlEffectController#testRateLimiter

    @RequestMapping("/rateLimiter")
    public String testRateLimiter(){
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "测试流控效果===匀速排队";
    }
    
  2. 流控规则

    image.png

  3. jmeter测试

    image.png

  4. 查看监控图

    image.png

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道友老李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值