• SpringCloud Alibaba之Sentinel流量治理组件学习笔记


    前言

    本节配套案例代码:Gitee仓库Github仓库

    所有博客文件目录索引:博客目录索引(持续更新)

    学习视频:SpringCloud 教程 已完结(IDEA 2022.1最新版)4K蓝光画质 微服务开发

    PS:本章节中部分图片是直接引用学习课程课件,如有侵权,请联系删除。

    当前项目环境版本:springboot 2.3.12.RELEASEspringcloud alibaba 2.2.7.RELEASESpringCloud Hoxton-SR12

    相关资料

    Sentinel

    Sentinel中文文档

    Sentinel官网

    一、认识Sentinel

    Sentinel是Spring Cloud Alibaba的一个重要组件,类似于spring clound的hystrix,与hystrix-dashboard控制台一样,sentinel-dashboard控制台可以提供对流量的实时监控、在线维护流量规则、熔断规则,前提是微服务整合了sentinel。

    Sentinel 具有以下特征:

    • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。

    • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。

    • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。

    • 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

    二、快速集成Sentinel组件实现流量监控

    可以直接跳过2.1部分,看2.2的集成Sentinel组件。

    2.1、搭建分布式项目

    2.1.1、项目环境介绍

    在集成Sentinel组件前,我们先来准备一些服务来为下面部分的demo测试做准备。

    服务:Nacos服务(注册中心)、用户服务(含远程调用图书服务)、图书服务。

    • 其中使用到的组件包含有:nacos-server、nacos-client、feign。

    image-20220801131016463

    nacos服务启动

    提前运行,并创建服务的命名空间,之后搭建的服务全部注册到这里:

    image-20220801131543274


    2.1.2、book-service服务

    image-20220801131119120

    引入依赖:

    <properties>
        <java.version>1.8java.version>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <spring-boot.version>2.3.12.RELEASEspring-boot.version>
        <spring-cloud-alibaba.version>2.2.7.RELEASEspring-cloud-alibaba.version>
    properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    1、添加服务注册注解:在启动器上SentinelBookserviceApplication

    @EnableDiscoveryClient
    
    • 1

    2、编写配置文件项:application.yaml

    server:
      port: ${SERVER_PORT:8083}
    spring:
      application:
        name: book-service
      cloud:
        nacos:
          server-addr: localhost:8848
          username: nacos
          password: nacos
          discovery:
          	# 指定命名空间和对应的组别以及注册的服务名
            namespace: 477245fa-d5e1-47e0-9580-4a8e268c3f58
            group: DEFAULT_GROUP
            service: book-service
        sentinel: # sentinel配置
          transport:
            dashboard: localhost:8858
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3、控制器对外的公共接口:BookController

    在控制器中对外有两个接口,其中/book/like/{id}接口是给用户控制器来进行远程调用的。

    package com.changlu.sentinelbookservice.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * @Description:
     * @Author: changlu
     * @Date: 12:39 PM
     */
    @RestController
    public class BookController {
    
        @GetMapping("/list")
        public List<String> list() {
            List<String> books = Arrays.asList("西游记", "水浒传", "红楼梦");
            return books;
        }
    
        @GetMapping("/book/like/{id}")
        public String getUserLikeBook(@PathVariable("id")Long id) {
            String book = "哈利波特";
            if (id != 1) {
                book = "西游记";
            }
            return book;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    2.1.3、user-service服务

    image-20220801131939330

    依赖:与2.1.2一致,只不过多了一个远程调用的组件feign:

    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1、开启服务注册以及feign包扫描增强注解,在启动器SentinelUserserviceApplication上添加即可

    @EnableDiscoveryClient
    @EnableFeignClients //开启feign扫描
    
    • 1
    • 2

    2、配置文件:application.yaml

    server:
      port: ${SERVER_PORT:8081}
    spring:
      application:
        name: user-service
      cloud:
        nacos:  # nacos配置
          server-addr: localhost:8848
          username: nacos
          password: nacos
          discovery:
            namespace: 477245fa-d5e1-47e0-9580-4a8e268c3f58
            group: DEFAULT_GROUP
            service: user-service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3、pojo类User:用户对象。com.changlu.sentineluserservice.pojo.User

    package com.changlu.sentineluserservice.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    /**
     * @Description:
     * @Author: changlu
     * @Date: 12:31 PM
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable {
    
        private Long id;
        private String name;
        private String sex;
        private String hobby;
        private String likeBook;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4、添加一个feign接口,之后用于调用BookService:com.changlu.sentineluserservice.feign.UserBookFeign

    package com.changlu.sentineluserservice.feign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    /**
     * @Description:
     * @Author: changlu
     * @Date: 12:34 PM
     */
    @FeignClient(value = "book-service")
    public interface UserBookFeign {
    
        @GetMapping("/book/like/{id}")
        String getUserLikeBook(@PathVariable("id")Long id);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5、控制器:com.changlu.sentineluserservice.controller.UserController

    package com.changlu.sentineluserservice.controller;
    
    import com.changlu.sentineluserservice.feign.UserBookFeign;
    import com.changlu.sentineluserservice.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @Description:
     * @Author: changlu
     * @Date: 12:31 PM
     */
    @RestController
    public class UserController {
    
        @Autowired
        private UserBookFeign userBookFeign;
    
        @GetMapping("/user")
        public User getUser() {
            User user = new User(1L, "changlu", "男", "打篮球", null);
            //远程调用
            String userLikeBook = userBookFeign.getUserLikeBook(user.getId());
            user.setLikeBook(userLikeBook);
            return user;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2.1.4:测试功能

    启动服务

    通过修改配置项来进行启动两个服务,nacos是单独去启动的:

    image-20220801133053085

    image-20220801133130836

    看一下nacos的管理页:

    image-20220801133147344

    测试服务

    测试user-service接口:

    image-20220801133020440

    测试book-service接口:

    image-20220801133028396

    ok都没有问题,那么我们接下来来对两个服务集成sentinel!


    2.2、快速集成Sentinel

    接下来我们想要对两个服务进行流量监控以及熔断、降级相关操作,就可以在我们的项目中引入sentinel依赖!

    2.2.1、启动Sentinel-dashboard服务

    Seninel是一个控制台(Dashboard),其基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

    其主要提供一个轻量级的控制台,它提供机器发现、单机资源实时监控、集群资源汇总,以及规则管理的功能。

    同样Sentinel-dashboard是独立安装和部署的,下载地址:https://github.com/alibaba/Sentinel/releases,目前的话我们下载1.8版本的jar包即可:

    接着我们用IDEA来进行配置jar包启动的一些参数:

    image-20220801133912667

    将其设置在8858端口,对其进行启动,可以看到其本身就是一个springboot项目:

    image-20220801133941872

    准备动作完成之后,我们即可在项目中集成Sentinel,来让dashboard与其能够进行通讯来实现一个检测、熔断等功能。


    2.2.2、user、book服务集成Sentinel(关键)

    1、引入Sentinel依赖:

    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、配置Sentinel的远程地址:application.yaml

    image-20220801134235634

    spring:
      cloud:
        sentinel: # sentinel配置
          transport:
            dashboard: localhost:8858
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时我们就已经集成完成了!

    2.2.3、进入dashboard进行服务监控

    接下来我们去访问Sentinel控制台:http://localhost:8858/#/login

    image-20220801134340593

    用户名和密码分别都是Sentinel,进入之后你可以看到里面没有任何的服务,这是怎么回事呢?

    • 这是正常的现象,哪怕你在服务里面去集成了sentinel,它也并不会在dashboard中看到,只有当你去访问的对应服务的接口后,即可在dashboard中看到啦

    image-20220801134625676

    接着我们尝试访问两个服务的接口:http://localhost:8081/user、http://localhost:8083/list

    • 对于某个服务是集群的,需要你换不同的端口来去访问后,dashboard才能够显示。

    OK,回到控制台,此时你就可以看到了!

    image-20220801135223192


    三、Sentinel的控制台服务功能

    image-20220801135330246

    对于一些服务的数据信息,会存储在指定的日志文件中,可以在运行面板上看到路径:

    image-20220801135549556

    3.1、机器列表

    在这里你可以看到统一服务的集群数量以及相应的ip地址、客户端版本以及健康状态,每10秒会对一个实例来进行心跳检测:

    image-20220801135404781


    3.2、实时监控

    在这里你可以看到最近的一个请求信息,包含响应时间:

    image-20220801140657287


    3.3、簇点链路

    在簇点链路中你可以看到所有关于该服务在启动时已经访问过的一些api接口,其中可以看到对应该接口的一些信息,同时你可以对制定的接口来进行流控(流量控制)、熔断、热点数据(制定请求参数)以及授权。

    image-20220801141014323

    3.4、流量控制

    知识补充(流量阈值算法)

    可参考:SpringCould笔记(二)微服务进阶 Cloud Alibaba

    是否超过流量阈值的判断,这里介绍4种算法:

    1、漏桶算法

    顾名思义,就像一个桶开了一个小孔,水流进桶中的速度肯定是远大于水流出桶的速度的,这也是最简单的一种限流思路。

    说明:访问服务的TPS比较均匀,不会超出最大的TPS,当访问第三方系统时,对方的接口并发数如果限制TPS,那么相对采用令牌桶比较合适。

    好处:有利于提升系统的稳定性。

    坏处:面对突发流量时,漏斗算法无法灵活应对,用户需要消耗大量的时间等待请求被处理。

    2、令牌桶算法

    只能说有点像信号量机制。现在有一个令牌桶,这个桶是专门存放令牌的,每隔一段时间就向桶中丢入一个令牌(速度由我们指定)当新的请求到达时,将从桶中删除令牌,接着请求就可以通过并给到服务,但是如果桶中的令牌数量不足,那么不会删除令牌,而是让此数据包等待。

    说明:相对于漏铜算法,令牌桶的算法更具有优势,除了可以控制客户端请求的TPS,令牌桶还有冗余的令牌应对突发流量,当然应对突然流量,底层的微服务也要足够的健壮性,否则服务很容易被瞬时流量压垮。

    好处:能够灵活应对突发流量,支持高并发场景,并且对流量的控制很平滑,用户体验好。

    坏处:开发者需要通过系统性能、用户习惯等进行令牌桶数量和生成速度的评估。

    3、 固定时间窗口算法

    我们可以对某一个时间段内的请求进行统计和计数,比如在14:1514:16这一分钟内,请求量不能超过100,也就是一分钟之内不能超过100次请求。

    可能出现问题复现:

    20:09:59的时候来了100个请求   20:10:01的时候又来了100个请求
    问题说明:我们其实希望的是60秒内只有100个请求,但是这种情况却是在3秒内出现了200个请求,很明显已经违背了我们的初衷。
    
    • 1
    • 2

    优点:每个时间段的总体性能能够较为精准地进行控制,避免系统崩溃。

    缺点:如果是在时间范围临界点突发大流量,固定窗口算法就无法发挥其作用,有可能会直接激发降级机制,影响用户的后续使用体验。

    4、 滑动时间窗口算法

    滑动时间窗口算法更加灵活,它会动态移动窗口,重新进行计算。

    说明:避免固定时间窗口的临界问题,但是这样显然是比固定窗口更加耗时的。

    好处:通过滑动窗口的方式,将剩余的系统资源交给该时间段的后半部分。

    坏处:只能通过控制时间段来控制请求总量,无法平滑地实现请求流量的控制。

    3.4.1、开启流量控制方式

    进入到簇点链路中,我们选择/user来对其进行流控:

    image-20220801141938970

    阈值类型任意选择一个,单机阈值指的就是每秒的阈值数,若是我们的某个服务是集群的话,那么就要对其进行勾选,这样的话整个集群的每个实例都会增加其控制!

    image-20220801142443119

    流控记录如下:选择的分别是QPS、单机阈值、集群模式、流控是直接模式、效果是快速失败

    image-20220801142214103

    此时我们来尝试进行访问:默认的失败返回内容如下所示

    image-20220801142313272

    image-20220801142326397


    3.4.2、实际测试不同流控效果【基于直接模式】

    对于机器不可能无限制接收并处理请求,对于高并发的情况下,一定要对其来进行限制,我们可以采用流控的方式。

    一旦达到了某个阈值,新的请求就不会再进行处理或者返回预先设定好的内容,这样的话就能够保证服务的可用性。

    在管理界面中包含三种流控效果分别是:快速失败、Warm Up、排队等待

    image-20220801180201730

    方案一:快速拒绝,既然不再接受新的请求,那么我们可以直接返回一个拒绝信息,告诉用户访问频率过高。

    方案二:预热,依然基于方案一,但是由于某些情况下高并发请求是在某一时刻突然到来,我们可以缓慢地将阈值提高到指定阈值,形成一个缓冲保护。

    方案三:排队等待,不接受新的请求,但是也不直接拒绝,而是进队列先等一下,如果规定时间内能够执行,那么就执行,要是超时就算了。

    下面是不同流控效果的测试:

    image-20220801180856329

    ①快速失败

    对user-service服务来设置快速失败,阈值为1:

    image-20220801180759979

    效果:对于快速失败来说,一旦达到阈值,在指定秒数中的请求就会直接返回限流的页面。

    ②预热Warm Up

    效果:预热的话就会在指定的范围内,例如阈值设置为10,阈值逐渐的在指定的秒数内增加到10,也就是说刚开始可能阈值会从1开始然后逐渐增加。

    • 阈值调低,不要让过多的请求访问服务器,导致冲垮服务器,先让服务器一点一点处理,再慢慢加量。

    image-20220801181255966

    我们使用jmeter来进行测试:每秒10个请求,发150个请求

    image-20220801185815293

    可以看到逐步请求能够正常响应的数量在增多:

    image-20220801185837449 image-20220801185846437 image-20220801185852901

    ③排队等待

    效果:请求过多时,让请求匀速的进入后台进行处理。采用漏斗算法,控制流量,设置超时时间,超时的则将请求抛弃,返回错误信息。

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

    image-20220801190457674

    同样我们来使用jmeter来进行测试:

    image-20220801190527546

    image-20220801190534991

    结果:可以看到10个请求,最后有3个响应失败,说明在一定情况下,部分请求加入队列可以对其延迟处理,用以减轻服务器的压力。


    3.4.2、实际测试不同流控模式

    image-20220801192302918

    不同流控模式包含:直接、关联、链路。

    直接:只针对当前的接口,达到指定的阈值就会被限流。

    关联:A关联指定接口B,若是该B接口超过阈值时,会导致A接口也被限流。

    链路:更细粒度的限流,可以精确到具体的方法。

    • 可以限流指定方法来的链路,例如链路1调用方法A,链路2调用方法A,那么我们可以从方法A下手来指定对应的链路来实现限流目的。

    关于测试:关联模式我在测得时候无效,目前还不清楚原因


    3.4.3、链路模式—精准控制(@SentinelResource)

    开启方式

    1、添加配置文件配置:

    image-20220801195753339

    spring:
      cloud:
       	sentinel: # sentinel配置
       	  # 关闭Context收敛,这样被监控方法可以进行不同链路的单独控制
          web-context-unify: false
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、在service中的方法级别添加@SentinelResource("getUserLikeBook")注解

    image-20220801195850734

    @Service
    public class BookServiceImpl implements BookService{
    
        @SentinelResource("getUserLikeBook")
        @Override
        public String getUserLikeBook(Long userId) {
            String book = "哈利波特";
            if (userId != 1) {
                book = "西游记";
            }
            return book;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意:此时两个controller接口都会调用该service的方法,如下;

    image-20220801200009375

    ok,接下来我们就能够来测试链路模式了!

    image-20220801200038159

    我们对这个方法来进行流控,对其选择链路,然后关联/list资源:

    image-20220801200120645

    image-20220801200131165

    3.5、系统规则

    模式如下

    • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load作为启发指标,进行自适应系统保护。当系统 load超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5

    • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。

    • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。

    • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。

    • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

    image-20220802091909712

    这里我们来使用cpu资源率作为示例:

    image-20220802091935738

    image-20220802092148535

    来进行一下压测,可以很明显的看到,当请求数变得很多时,服务的CPU使用率标高达到阈值,就会进行拒绝访问,直接响应异常。

    3.6、热点规则

    热点规则是根据指定的请求参数类型:

    image-20220802094814854

    @GetMapping("/testparams")
    @SentinelResource("testparams")
    public String testParams(@RequestParam(value = "a", required = false)String a,
                             @RequestParam(value = "b", required = false)String b,
                             @RequestParam(value = "c", required = false)String c
                            ) {
        return "a:" + a + ", b:" + b + ", c:" + c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来我们去进行编辑:

    上面的是指定某个参数来进行阈值设定,下面则是指定某个参数来设置值与阈值:

    • 参数索引是从0开始的。

    这里指的是第2个参数阈值为1,第0个参数若是值为1的话且阈值为1就限流:

    image-20220802095008180

    测试1:http://localhost:8082/testparams?a=2

    参数1值为2,不在阈值限定的范围中,所以不会出现限流的情况。

    image-20220802095252928

    测试2:http://localhost:8082/testparams?a=2&b=1

    image-20220802095309414

    抛出的异常类型为:com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException

    image-20220802095344840


    3.7、熔断规则

    在簇点链路中可以去选择指定的路径来指定熔断策略:

    image-20220802111414165

    策略分别包含

    1、慢调用比例:对于指定接口的处理时长达到阈值(最大RT),那么系统会判定它可能出现了服务异常情况,判定为慢调用,在一个统计时长内,如果请求数目大于最小请求数目,并且被判定为慢调用的请求比例已经超过阈值,将触发熔断。

    2、异常比例:这里判断的是出现异常的比例数。

    3、异常数:达到指定的异常数量。

    四、自定义限流响应与异常

    4.1、自定义限流响应

    自定义配置

    场景:当我们对某个接口来设置流控,当达到阈值,就会返回如下异常信息。

    • ①接口流控。

    image-20220802100808114

    接下来我们要对其进行自定义:对user-service服务来进行增强

    1、编写一个自定义限流接口,接口地址也可以自行指定。

    image-20220802100902078

    package com.changlu.sentineluserservice.controller;
    
    import com.alibaba.fastjson.JSONObject;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @Description:
     * @Author: changlu
     * @Date: 9:57 AM
     */
    @RestController
    public class CommonController {
    
        @RequestMapping("/blocked")
        public JSONObject blocked() {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("code", 403);
            jsonObject.put("success", false);
            jsonObject.put("message", "您的请求频率过快,请稍后再试!");
            return jsonObject;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2、配置文件添加阻塞页面路径配置:application.yaml

    image-20220802100924179

    spring:
      cloud:
        sentinel: # sentinel配置
          block-page: /blocked
    
    • 1
    • 2
    • 3
    • 4

    测试

    我们对user-service服务来进行流控:

    image-20220802101056817

    对/user快速刷新两次之后,就会进行重定向到/blocked地址:

    image-20220802101203975

    image-20220802101358366

    4.2、自定义异常处理

    场景:针对于方法级别的流控,一旦超过指定的阈值,那么就会抛出异常。

    • ①指定方法流控。②热点参数控制。

    ①指定方法

    自定义配置

    接下来我们对book-service服务中的service方法来进行自定义异常处理。

    image-20220802102600286

    1、编写一个与对应方法返回参数一致,额外添加一个BlockException异常类型参数

    //替代方案,注意参数和返回值需要保持一致(会一并传入进来),并且参数最后还需要额外添加一个BlockException
    public String blocked(Long userId, BlockException e) {
        return "限流书籍";
    }
    
    • 1
    • 2
    • 3
    • 4

    2、在@SentinelResource中指定异常处理

    //指定blockHandler,也就是被限流之后的替代解决方案,这样就不会使用默认的抛出异常的形式了
    @SentinelResource(value = "getUserLikeBook", blockHandler = "blocked")
    
    • 1
    • 2

    测试

    我们对book-service服务的指定service方法进行流控:

    image-20220802102751780

    之后我们来去尝试调用user-service的/user接口,该接口包含一个远程调用涉及到getUserLikeBook,快速请求刷新一下:

    image-20220802102932748

    指定方法流控超越阈值,成功处理异常!


    ②热点参数控制(实际上与①一致)

    之前我们测试热点参数的时候,可以看到是抛出的异常,而不是走得跳转流控响应,那么如何对热点参数这类的报错进行异常处理呢?

    image-20220802105153681

    @SentinelResource(value = "testparams",
                      blockHandler = "handlerTestHotKey"
                     )
    
    //必须要设置为public,参数要一致,否则会抛出异常
    public String handlerTestHotKey(String a, String b, String c, BlockException exception) {
        return "对不起, 当前流量过高...";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    看下访问效果:

    image-20220802105317801


    ③针对抛出异常来处理

    场景:若是我们的接口会有一些异常抛出,我们也可以对其来进行自定义处理返回!

    • 类似于trycatch以及全局异常捕捉,一般对于这类异常应该不太会专门使用sentinel提供的异常捕捉把(个人认为)。

    示例如下:

    image-20220802105816836

    @GetMapping("/testparams")
    @SentinelResource(value = "testparams",
                      blockHandler = "handlerTestHotKey",
                      fallback = "except",  //fallback指定出现异常时的替代方案
                      exceptionsToIgnore = IOException.class //忽略那些异常,也就是说这些异常出现时不使用替代方案
                     )
    public String testParams(@RequestParam(value = "a", required = false)String a,
                             @RequestParam(value = "b", required = false)String b,
                             @RequestParam(value = "c", required = false)String c) throws RuntimeException {
        throw new RuntimeException("自定义抛出异常!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试效果:

    image-20220802105935035


    五、服务熔断和降级(接口层面)

    5.1、服务熔断知识点

    在整个微服务调用链路出现问题的时候,及时对服务进行降级,以防止问题进一步恶化,如下图所示:

    image-20220802110018740

    举例:例如A接口远程调用B接口,若是B接口不可用,那么请求迟迟不响应,此时又有大量的客户端去调用接口A,若是对于这种情况不进行及时响应的话,就会造成整条链路的问题恶化。

    对于大量请求去调用A,又A调用服务B,此时由于B短时间没有进行响应,那么该线程就会进入阻塞,新来的请求会不断在线程池中开辟新的线程,这种情况会导致线程数不断增加,cpu资源也会被耗尽,对于整个线程不断增加的情况,有一些隔离方案如下:

    1、线程池隔离
    线程池隔离实际上就是对每个服务的远程调用单独开放线程池,比如服务A要调用服务B,那么只基于固定数量的线程池,这样即使在短时间内出现大量请求,由于没有线程可以分配,所以就不会导致资源耗尽了。

    2、 信号量隔离。【Sentinel也正是采用的这种方案实现隔离的。】
    信号量隔离是使用Semaphore类实现的,也是限定指定的线程数量能够同时进行服务调用,但是它相对于线程池隔离,开销会更小一些,使用效果同样优秀,也支持超时等。

    针对于熔断降级:当下游服务因为某种原因变得不可用或响应过慢时,上游服务为了保证自己整体服务的可用性,不再继续调用目标服务而是快速返回或是执行自己的替代方案,这便是服务降级。

    整个过程分为三个状态:

    • 关闭:熔断器不工作,所有请求全部该干嘛干嘛。

    • 打开:熔断器工作,所有请求一律降级处理。

    • 半开:尝试进行一下下正常流程,要是还不行继续保持打开状态,否则关闭。

    这个实际上与我们之前学习的Hystrix思路也是一致的。


    5.2、自定义熔断处理

    ①服务本身熔断异常处理

    处理方案就是添加@SentinelResource,然后去指定``blockHandler = “handlerTestHotKey”`,之后就会去进行响应啦。

    配置如下:实际上与4.1中的①②是一致的。

    @GetMapping("/testparams")
    @SentinelResource(value = "testparams",
                      fallback = "except",  //fallback指定出现异常时的替代方案
                      exceptionsToIgnore = IOException.class //忽略那些异常,也就是说这些异常出现时不使用替代方案
                     )
    public String testParams(@RequestParam(value = "a", required = false)String a,
                             @RequestParam(value = "b", required = false)String b,
                             @RequestParam(value = "c", required = false)String c) throws RuntimeException {
        throw new RuntimeException("自定义抛出异常!");
    }
    
    //参数保持一致,必须要设置为public,否则会抛出异常
    public String handlerTestHotKey(String a, String b, String c, BlockException exception) {
        return "对不起, 当前流量过高...";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ②feign远程调用接口熔断处理

    配置过程

    背景:user-service服务在/user接口中会去调用book-service服务中的某个接口,若是book-service在调用过程中下线了,那么此时使用熔断方案来进行解决。

    image-20220802112725100

    我们就来对user-service的接口来进行改造:

    image-20220802112838354

    1、开启feign的sentinel:application.yaml

    # 开启feign的sentinel流量治理
    feign:
      sentinel:
        enabled: true
    
    • 1
    • 2
    • 3
    • 4

    2、实现指定UserBookFeign的接口:com.changlu.sentineluserservice.feign.UserClientFallback

    package com.changlu.sentineluserservice.feign;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @Description:
     * @Author: changlu
     * @Date: 11:22 AM
     */
    @Component
    public class UserClientFallback implements UserBookFeign{
        @Override
        public String getUserLikeBook(Long id) {
            return "熔断替代方案";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3、在对应的feign接口注解上指定fallback类:

    image-20220802113014160

    @FeignClient(value = "book-service", fallback = UserClientFallback.class)
    
    • 1

    测试

    首先user与book服务都正常,我们来进行调用user的服务:

    image-20220802112433454

    接着将book服务下线,再来请求user服务:

    image-20220802113124395

    image-20220802113134775

    此时熔断方案已生效!


    ③传统的RestTemplate远程调用

    在restTemplate上添加注解@SentinelRestTemplate,然后去配置fallbackClass指定的熔断类即可。

    @Bean
    @LoadBalanced
    @SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class,
                          fallback = "fallback", fallbackClass = ExceptionUtil.class) //这里同样可以设定fallback等参数
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    参考资料

    [1]. SpringBoot集成sentinel

    [2]. 互联网限流算法-漏桶算法 与 令牌桶算法对比差异

    [3]. 7. Alibaba Sentinel 流控效果之排队等待

    [4]. sentinel 控制台讲解-流控规则–流控效果:Warm Up(预热)

    [5]. [Alibaba微服务技术入门]_Sentinel热点参数限流_第14讲

  • 相关阅读:
    建模规范:环境设置
    对象中扩展运算符的作用
    【2023集创赛】加速科技杯作品:高光响应的二硫化铼光电探测器
    Nvidia GPU 入门教程之 09 如何使用 Kaggle API 搜索和下载数据?
    Vue进阶:组件化编程(脚手架) - 万字总结精华整理(持续更新)
    素短语的定义
    CDH Kerberos启动后hue报错Couldn‘t renew kerberos ticket
    uniapp地图围栏代码
    自定义hooks
    C嘎嘎之类和对象中
  • 原文地址:https://blog.csdn.net/cl939974883/article/details/126119316