• Nacos使用(三)


    阅前须知,本文是基于mall学习教程的学习笔记。

    通过前面两章节,基本搭建了通过nacos实现注册中心和配置中心的基础服务,同时也了解到了nacos的功能强大。这个时候我们引入新的需求,针对一个服务如果不能够灵活的限制请求流量,就很容易超出服务负载能力,同时如果出现服务挂掉的情况我们也要提供一套完备的后置处置机制。

    所以我们引入Sentinel来解决该问题,总之最终目的就是让服务能够保持平稳运行。

    一. 安装Sentinel控制台

    为了方便管理Sentinel服务配置,所以我们通过依赖官方的后台服务进行统一管理,类似于rocketmq-admin以及dubbo-admin的dashboard管理界面,所以我们首先下载Sentinel控制台,访问官方GitHub地址进行下载:https://github.com/alibaba/Sentinel/releases

    image-20220815102031115

    这里我们下载1.8.5版本,选择sentinel-dashboard-1.8.5.jar进行下载,使用java -jar进行启动,默认启动端口是8080,我们也可以自定义启动端口:

    java -jar sentinel-dashboard-1.8.5.jar --server.port=8081
    
    • 1

    启动完成后,地址栏访问http://localhost:8081/,打开如下页面,默认账号密码均为sentinel:

    image-20220815111003461

    image-20220815111041311

    这个时候我们打开发现是空的界面,因为sentinel服务还没有启动,接下来我们新建一个sentinel服务。

    二. Sentinel-限流

    满足灵活配置请求流量限额的要求。

    首先我们创建一个nacos-sentinel-service模块,引入如下pom依赖,

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>2021.0.1.0version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
    
    <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>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>
    
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.24version>
            <scope>providedscope>
        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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    application.yml配置文件如下,其中需要用到前一章中的nacos-user-service模块:

    server:
      port: 8401
    spring:
      application:
        name: nacos-sentinel-service
      cloud:
        nacos:
          discovery:
            # 配置Nacos地址
            server-addr: localhost:8848
        sentinel:
          transport:
            # 配置sentinel dashboard地址
            dashboard: localhost:8080
            port: 8719
    service-url:
      user-service: http://nacos-user-service
    management:
      endpoints:
        web:
          exposure:
            include: '*'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    根据资源名称限流

    创建限流Controller类,需要注意blockHandler中指定的值需要与异常类的方法名保持一致,例如下方的handlerException

    @RestController
    @RequestMapping("/rateLimit")
    public class RateLimitController {
    
        @GetMapping("/byResource")
        @SentinelResource(value = "byResource", blockHandler = "handlerException")
        public Result byResource(){
            return Result.of("按资源名称限流");
        }
    
        @GetMapping("/byUrl")
        @SentinelResource(value = "byUrl", blockHandler = "handlerException")
        public Result byUrl(){
            return Result.of("按url限流");
        }
    
        public Result handlerException(BlockException exception){
            return Result.of(exception.getClass().getCanonicalName());
        }
    }
    
    @Data
    public class Result<T> {
    
        /**
         * 请求是否成功
         */
        private Boolean isSuccess;
    
        /**
         * 失败编号
         */
        private String errCode;
    
        /**
         * 失败信息
         */
        private String errMessage;
    
        /**
         * 业务数据
         */
        @JsonInclude(JsonInclude.Include.NON_NULL)
        private T data;
    
        public Result() {
    
        }
    
        /**
         * 请求成功
         *
         * @return
         */
        public static Result buildSuccess() {
            Result result = new Result();
            result.setIsSuccess(true);
            return result;
        }
    
        /**
         * 请求失败
         *
         * @param errCode
         * @param errMessage
         * @return
         */
        public static Result buildFailure(String errCode, String errMessage) {
            Result result = new Result();
            result.setIsSuccess(false);
            result.setErrCode(errCode);
            result.setErrMessage(errMessage);
            return result;
        }
    
        /**
         * 成功后返回数据
         *
         * @param data
         * @param 
         * @return
         */
        public static <T> Result<T> of(T data) {
            Result<T> singleResponse = new Result();
            singleResponse.setIsSuccess(true);
            singleResponse.setData(data);
            return singleResponse;
        }
    }
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    关键就是使用@SentinelResource注解来指定限流策略,以及使用blockHandler进行异常处理。

    首先我们启动nacos,随后启动该服务。启动成功后,我们访问如下地址:http://localhost:8151/rateLimit/byResource

    返回如下信息,模拟这是一次正常的请求返回资源:

    {
        isSuccess: true,
        errCode: null,
        errMessage: null,
        data: "按资源名称限流"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个时候我们再观察Sentinel控制台,发现我们的nacos-sentinel-service成功注册上去了,并能够在左侧菜单栏中看到很多模块内容。

    image-20220815111252519

    同时在nacos中也能够看到注册服务的信息:

    image-20220815111442077

    接下来我们设置一个简单的测试限流策略,左侧菜单栏选择流控规则->资源名与Controller中的@SentinelResource设置的value属性保持一致->设置阈值类型为QPS,单机阈值设置为1

    image-20220815111719664

    设置完毕后,我们继续访问http://localhost:8151/rateLimit/byResource,如果访问间隔较长的话还是正常返回内容,如果刷新频率过快的话就能够触发我们的限流策略,并返回如下内容:

    {
        isSuccess: true,
        errCode: null,
        errMessage: null,
        data: "com.alibaba.csp.sentinel.slots.block.flow.FlowException"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    根据Url限流

    我们设置限流规则,如下:

    image-20220815133456336

    快速访问http://localhost:8151/rateLimit/byUrl,则会被sentinel流控捕获返回以下信息,同时在实时监控面板中能够展现拒绝QPS图表折线图信息

    image-20220815133416391

    image-20220815133639907

    自定义异常返回

    当流控被捕获的时候,我们想要自定义一个异常返回,有两种方式:

    1. 通过@SentinelResource注解中指定blockHandler的方法名来进行自定义,例如上面指定的handlerException方法:

      public Result handlerException(BlockException exception){
          return Result.of("这是一个自定义的流控捕获异常哦~");
      }
      
      • 1
      • 2
      • 3

      image-20220815134439533

    2. 自定义单独的异常处理类

      新建一个CustomBlockHandler:

      public class CustomBlockHandler {
      
          public Result handleException(BlockException exception){
              return Result.of("CustomBlockHandler->自定义异常处理");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      在controller中的@SentinelResource注解增加一个blockHandlerClass属性

      /**
      * 自定义通用的限流处理逻辑
      */
      @GetMapping("/customBlockHandler")
      @SentinelResource(value = "customBlockHandler", blockHandler = "handleException",blockHandlerClass = CustomBlockHandler.class)
      public Result blockHandler() {
          return Result.of("自定义限流");
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

    高级选项

    实际上还有高级选型设置流控效果,默认的是快速失败即直接返回失败,还有另外两种Warm Up以及排队等待。

    image-20220815113035230

    1. 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
    2. Warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
    3. 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长

    留个小问题,我们可以观察到设置好的限流策略如果重启应用之后就全部消失了,那么我们该如何解决这个问题呢,我们先独立思考以下,后续关注文末的扩展内容即可得到解答。

    三. Sentinel-熔断

    支持对服务间调用进行保护,对故障应用进行熔断操作,满足出现服务挂掉的情况我们也要提供一套完备的后置处置机制。

    首先定义一个RestTemplate包装类:

    @Configuration
    public class RibbonConfig {
    
        @Bean
        @SentinelRestTemplate
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义一个fallback-controller,使用@SentinelResource注解分别测试熔断处理,通过fallback指定异常触发方法,同时还可以设置扩展属性exceptionsToIgnore来忽略对应异常的熔断处理。

    @Slf4j
    @RestController
    @RequestMapping("/breaker")
    public class CircleBreakerController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Value("${service-url.user-service}")
        private String userServiceUrl;
    
        @RequestMapping("/fallback/{id}")
        @SentinelResource(value = "fallback",fallback = "handleFallback")
        @GetMapping("/{id}")
        public String getUser(@PathVariable Long id) {
            return restTemplate.getForObject(userServiceUrl + "/user/{1}", String.class, id);
        }
    
        @RequestMapping("/fallbackException/{id}")
        @SentinelResource(value = "fallbackException",fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class})
        public String fallbackException(@PathVariable Long id) {
            if (id == 1) {
                throw new IndexOutOfBoundsException();
            } else if (id == 2) {
                throw new NullPointerException();
            }
            return restTemplate.getForObject(userServiceUrl + "/user/{1}", String.class, id);
        }
    
        public String handleFallback(Long id) {
            return "handleFallback->服务降级返回" + id;
        }
    
        public String handleFallback2(@PathVariable Long id, Throwable e) {
            log.error("handleFallback2 id:{},throwable class:{}", id, e.getClass());
            return "handleFallback2->服务降级返回" + id;
        }
    }
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38

    访问http://localhost:8151/breaker/fallback/4,这个时候nacos-user-service模拟一个异常,就会触发handleFallback:handleFallback->服务降级返回4。

    测试exceptionsToIgnore属性,此处设置忽略NullPointerException的熔断触发,我们访问http://localhost:8151/breaker/fallbackException/1,能够正常触发熔断处理返回:handleFallback2->服务降级返回1

    这个时候我们访问http://localhost:8151/breaker/fallbackException/2触发被忽略的NullPointerException,则不会被熔断降级处理:

    image-20220817132557777

    四.扩展-结合Feign

    由于Sentinel也结合了Feign,所以此处我们使用feign来进行熔断处理。

    首先,我们添加关于feign的pom依赖:

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

    在application.yml注解中增加以下内容,表示打开Sentinel对Feign的支持:

    feign:
      sentinel:
        enabled: true #打开sentinel对feign的支持
    
    • 1
    • 2
    • 3

    启动类上增加@EnableFeignClients注解

    @EnableFeignClients
    @EnableDiscoveryClient
    @SpringBootApplication
    public class NacosSentinelServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(NacosSentinelServiceApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建UserService接口,定义对nacos-user-service服务的调用,关键在于@FeignClient注解的使用,指定value值为nacos-user-service表示指向对应的该服务,同时fallback指向UserFallbackService,表示当触发熔断服务的时候会流转到该类进行处理。

    同时需要关注此处在方法上方要指定对应的mapping映射地址:

    @FeignClient(value = "nacos-user-service",fallback = UserFallbackService.class)
    public interface UserService {
        
    	@GetMapping("/user/{id}")
        String getUser(@PathVariable Long id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们继续创建UserFallbackService类实现UserService接口,用于处理服务降级逻辑:

    @Component
    public class UserFallbackService implements UserService{
        @Override
        public String getUser(Long id) {
            return "触发FeignClient熔断,进行服务降级处理";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在UserFeignController中使用UserService,通过feign来调用nacos-user-service服务中的接口:

    @RestController
    @RequestMapping("/userFeign")
    public class UserFeignController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("/{id}")
        public String getUser(@PathVariable Long id) {
            return userService.getUser(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    访问http://localhost:8151/user/4,若nacos-user-service服务触发异常则会返回UserFallbackService中的熔断处理:触发FeignClient熔断,进行服务降级处理

    五. 扩展-使用nacos存储规则

    由于sentinel的限流规则是存储在内存中,故程序重启或者关闭则将会重置规则。

    我们引入nacos的存储规则来解决该问题。

    img

    1. 配置中心创建规则,并推送至客户端
    2. sentinel控制台从配置中心获取规则

    添加如下pom依赖:

    <dependency>
        <groupId>com.alibaba.cspgroupId>
        <artifactId>sentinel-datasource-nacosartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    调整配置文件的sentinel内容为,表示配置dataId=nacos-sentinel-service-sentinel,groupId=DEFAULT_GROUP的配置信息,并注册到nacos上:

    spring:
      application:
        name: nacos-sentinel-service
      cloud:
        sentinel:
          transport:
            # sentinel dashboard
            dashboard: localhost:8081
            # 客户端ip
            client-ip: localhost
            port: 8719
          datasource:
            ds1:
              nacos:
                server-addr: localhost:8848
                dataId: ${spring.application.name}-sentinel
                groupId: DEFAULT_GROUP
                data-type: json
                rule-type: flow
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们打开nacos控制台,并在配置管理->配置列表中增加配置项,并添加如下配置:

    [
        {
            "resource": "/rateLimit/byUrl",
            "limitApp": "default",
            "grade": 1,
            "count": 1,
            "strategy": 0,
            "controlBehavior": 0,
            "clusterMode": false
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220817142653724

    参数解释:

    resource:资源名称;
    limitApp:来源应用;
    grade:阈值类型,0表示线程数,1表示QPS;
    count:单机阈值;
    strategy:流控模式,0表示直接,1表示关联,2表示链路;
    controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
    clusterMode:是否集群。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们通过启动sentinel控制台服务以及nacos-sentinel-service服务,就可以在sentinel控制台中看到对应nacos配置信息上的流控配置:

    image-20220817145559707

    快速访问做了流控的服务地址http://localhost:8151/rateLimit/byUrl,这个时候就能够看到限流效果了:Blocked by Sentinel (flow limiting)

    我们再做个测试,直接在nacos上将count属性从原先1改变为10,这个时候回到sentinel控制台刷新一下就会发现阈值被同步更新了,证明流控规则更改成功。

    image-20220817145711403

    这个时候我们就不用担心sentinel规则会在应用重启后消失了,因为通过nacos我们就能够很方便的管理接口流控,并能够随时调整流控规则。

    参考资料:

  • 相关阅读:
    用Java语言创建的Spring Boot项目中,如何传递List集合呢?
    Taurus.MVC-Java 版本打包上传到Maven中央仓库(详细过程):1、JIRA账号注册
    使用Dokcer中的Mysql导入sql文件
    ADBMS1818芯片资料介绍(1)
    外包干了6天,技术明显退步。。。
    Domino Volt 1.0.5中的可视化流程设计器
    java微信小程序 新生入学报到系统#计算机毕业设计
    市场拓展招聘:完整指南
    TensorFlow开源项目
    VSCode 设置代码格式化
  • 原文地址:https://blog.csdn.net/imVainiycos/article/details/126386327