SpringCloud_服务调用OpenFeign和断路器Resilience4J

news2024/9/21 22:14:22

文章目录

  • 一、负载均衡概论
    • 1、服务器负载均衡
    • 2、客户端负载均衡
    • 3、客户端负载均衡策略(SpringCloudRibbon)
    • 4、客户端负载均衡策略(SpringCloudLoadBalancer)
  • 二、SpringCloudOpenFeign服务调用
    • 1、OpenFeign服务调用的使用
    • 2、OpenFeign服务调用的日志增强
    • 3、OpenFeign服务调用超时机制
  • 三、SpringCloudResilience4J服务断路器1
    • 1、服务雪崩的解决方案
    • 2、SpringCloudResilience4J服务断路器
    • 3、Resilience4J的超时降级 (timelimiter)
    • 4、Resilience4J的重试机制(retry)
    • 5、Resilience4J的熔断降级(circuitbreaker)
    • 6、Resilience4J的慢调用熔断降级(slowcircuitbreaker)
  • 四、SpringCloudResilience4J服务断路器2
    • 1、Resilience4J信号量服务隔离(bulkhead)
    • 2、Resilience4J线程池服务隔离(bulkhead)
    • 3、Resilience4J的服务限流(ratelimiter)
  • 总结(SpringCloud 2022.0.2和SpringBoot 3.0.5)

一、负载均衡概论

1、服务器负载均衡

  1. 在服务集群内设置一个中心化负载均衡器,例如Nginx。发起服务间调用的时候,服务请求并不直接发向目标服务器,而是发给这个全局负载均衡器,它再根据配置的负载均衡策略将请求转发到目标服务。
  2. 优点,服务端负载均衡应用范围很广,它不依赖于服务发现技术,客户端并不需要拉取完整的服务列表;同时,发起服务调用的客户端也不用操心该使用什么负载均衡策略。
  3. 劣势,网络消耗以及复杂度和故障率提升。

2、客户端负载均衡

  1. SpringCloudLoadbalancer采用了客户端负载均衡技术,每个发起服务调用的客户端都存有完整的目标服务地址列表,根据配置的负载均衡策略,由客户端自己决定向哪台服务器发起调用。
  2. 优势,网络开销小并且配置灵活
  3. 劣势,需要满足一个前置条件,发起服务调用的客户端需要获取所有目标服务的地址,这样它才能使用负载均衡规则选取要调用的服务。即客户端负载均衡技术需要依赖服务发现技术来获取服务列表。
  4. 客户端负载均衡,可以使用Eureka来获取微服务实例清单,缓存到本地;然后通过Loadbalancer根据负载均衡策略从服务实例清单列表中选择具体实例。
  5. SpringCloudRibbon,是NetFlix发布的负载均衡器,它有助于http和tcp的客户端行为。可以根据负载均衡算法(轮询、随机或自定义)自动帮助服务消费者请求,默认就是轮询。
  6. SpringCloudLoadBalancer,由于Ribbon已经进入停更维护状态,并且Ribbon 2并不与Ribbon 1相互兼容,所以Spring Cloud全家桶在Spring Cloud Commons项目中,添加了Spring cloud Loadbalancer作为新的负载均衡器,并且做了向前兼容,就算你的项目中继续用 Spring Cloud Netflix 套装(包括Ribbon,Eureka,Zuul,Hystrix等等)让你的项目中有这些依赖,你也可以通过简单的配置,把Ribbon替换成Spring Cloud LoadBalancer。

3、客户端负载均衡策略(SpringCloudRibbon)

  1. RandomRule(随机)
  2. RoundRobinRule(轮询)
  3. RetryRule(重试)
    即失败后,在指定时间内重试。
  4. WeightedResponseTimeRule
    即根据响应时间的长短给权重。响应时间越短,权重越大,也就是被选中的概率越大。
  5. BestAvailableRule
    在过滤掉故障服务后,它会基于过去30分钟的统计结果选取当前并发量最小的服务节点作为目标地址。如果统计结果尚未生成,则采用轮询的方式选定节点。
  6. AvailabilityFilteringRule
    每次AvailabilityFilteringRule(简称AFR)都会请求RobinRule挑选一个节点,然后对这个节点做以下两步检查:是否处于不可用,节点当前的active请求连接数超过阈值,超过了则表示节点目前太忙,不适合接客,就会让RobinRule重新选择一个服务节点。
    如果被选中的server不幸挂掉了,那么AFR会自动重试(次数最多10次),还是没连上的话就会让RobinRule重新选择一个服务节点。
  7. ZoneAvoidanceRule
    复合判断server所在区域的性能和server的可用性选择服务器

4、客户端负载均衡策略(SpringCloudLoadBalancer)

  1. RandomLoadBalancer(随机)
  2. RoundRobinLoadBalancer(默认轮询)

二、SpringCloudOpenFeign服务调用

1、OpenFeign服务调用的使用

之前使用RestTemplate比较麻烦,改用OpenFeign进行服务的调用会更得心应手。

  1. 在cloud父工程下,创建一个子模块cloud-consumer-openfeign-order80
    即用openfeign服务调用的order模块去替换原先的RestTemplate服务调用的order模块。

  2. 在POM文件中添加依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud</artifactId>
            <groupId>com.zzx</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-consumer-openfeign-order80</artifactId>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
        </properties>
        <dependencies>
            <!-- 引入OpenFeign依赖  -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <!--  引入Eureka client依赖 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- actuator监控信息完善 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    

    即比原先多了一个OpenFeign依赖。

  3. 在resources目录下创建application.yml文件

    server:
      port: 80
    eureka:
      instance:
        # 注册的实例名
        instance-id: cloud-consumer-openfeign-order80
      client:
        service-url:
          # Eureka server的地址
          #单机
          #defaultZone: http://localhost:7001/eureka/
          #集群
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
    spring:
      application:
        #设置应用名
        name: cloud-order-consumer
    
    

    即修改了该消费者在Eureka的注册的实例名。

  4. 创建主启动类OrderFeignMain80

    package com.zzx;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @Slf4j
    @SpringBootApplication
    //开启openfeign服务调用
    @EnableFeignClients
    public class OrderFeignMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderFeignMain80.class,args);
            log.info("***** OrderFeignMain80 启动成功 ******");
        }
    }
    
    

    此时多了一个开启Openfeign服务调用的注解

  5. 在com.zzx包中创建一个service包,在该包下创建OpenFeign服务调用的接口PaymentFeignService

    package com.zzx.service;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    /**
     * 支付远程调用OpenFeign接口
     */
    //根据服务生产者的名字调用
    @FeignClient("CLOUD-PAYMENT-PROVIDER")
    public interface PaymentFeignService {
        @GetMapping("/payment/index")
        String index();
    }
    
    

    即将服务的应用名给到OpenFeign,再由它去获取Eureka中对应的实例的uri,通过该uri再拼接上GetMapping中的url即可调用服务对应的方法。

  6. 在com.zzx包中创建一个controller包,在该包下创建OpenFeign服务调用的控制类OrderController

    package com.zzx.controller;
    
    import com.zzx.service.PaymentFeignService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("order")
    public class OrderController {
        @Autowired
        private PaymentFeignService paymentFeignService;
        /**
         * openfeign远程服务调用
         */
        @RequestMapping("index")
        public String index(){
            return paymentFeignService.index();
        }
    }
    
    

    即可以通过浏览器访问并映射到该Controller对应地址的方法上,该Controller对应的方法就会调用该OpenFeign接口的对应方法,该接口方法就会通过OpenFeign调用Eureka对应实例的方法,最后返回结果到浏览器。

  7. 测试
    1)在浏览器中打开:localhost:80/order/index

2、OpenFeign服务调用的日志增强

  1. OpenFeign的日志级别:
    1)NONE:默认的,不显示任何日志;
    2)BASIC:仅记录请求方法、URL、响应状态码及执行时间;
    3)HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
    4)FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

  2. 在子模块openfeign的com.zzx包下,创建config包,在该包下创建类OpenFeignConfig

    package com.zzx.config;
    
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class OpenFeignConfig {
        /**
         * 日志级别定义
         */
        @Bean
        Logger.Level feignLoggerLevel(){
            return Logger.Level.FULL;
        }
    }
    
    

    即将该类指定为配置类,创建一个方法返回Feign的日志级别对象,指定为Bean对象交给SpringIOC容器。

  3. 在application.yml文件中添加如下

    logging:
      level:
        com.zzx.service: debug
    

    com.zzx.service是openfeign接口所在的包名。

  4. 测试
    1)在浏览器中访问:localhost:80/order/index
    在这里插入图片描述

3、OpenFeign服务调用超时机制

  1. 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
    在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
    利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。

  2. 在子模块openfeign中的yml文件中,添加如下超时时间的配置

    spring:
      application:
        #设置应用名
        name: cloud-order-consumer
      cloud:
        #默认超时时间
        openfeign:
          client:
            config:
              default:
                #连接超时时间
                connectTimeout: 2000
                #读取超时时间
                readTimeout: 2000
    
  3. 在子模块payment中的com.zzx.controller包下的PaymentController类中,添加如下代码

    @GetMapping("timeout")
        public String timeout() throws InterruptedException {
            TimeUnit.SECONDS.sleep(5);
            return "payment timeout";
        }
    

    即用来模拟超时的方法,让openfeign调用。

  4. 在子模块openfeign中的com.zzx.service包下的PaymentFeignService类中,添加如下代码

    @GetMapping("/payment/timeout")
    String timeout();
    
  5. 在子模块openfeign中的com.zzx.controller包下的OrderController类中,添加如下代码

    @GetMapping("/timeout")
        public String timeout() {
            return paymentFeignService.timeout();
        }
    

    即调用提供者Payment的超时方法。

  6. 测试
    1)重启服务提供者和消费者
    2)浏览器访问:http://localhost:80/order/timeout
    3)控制台报读取数据超时错误在这里插入图片描述

三、SpringCloudResilience4J服务断路器1

1、服务雪崩的解决方案

  1. 服务雪崩:服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
    造成雪崩的原因:
    1)服务提供者不可用(硬件故障、程序bug、缓存击穿、用户大量请求)
    2)重试加大流量(用户重试,代码逻辑重试)
    3)服务调用者不可用(同步等待造成的资源耗尽)
  2. 服务熔断
    熔断就跟保险丝一样,当一个服务请求并发特别大,服务器已经招架不住了,调用错误率飙升,当错误率达到一定阈值后,就将这个服务熔断了。熔断之后,后续的请求就不会再请求服务器了,以减缓服务器的压力。
  3. 服务降级
    1)服务降级 fallback
    概念:服务器繁忙,请稍后重试,不让客户端等待并立即返回一个友好的提示。
    2)使用场景:当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
    当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
    3)出现服务降级的情况:
    程序运行异常
    超时
    服务熔断触发服务降级
    线程池/信号量打满也会导致服务降级
  4. 服务隔离
    1)线程池隔离
    将用户请求线程和服务执行线程分割开来,同时约定了每个服务最多可用线程数。
    支持超时,可直接返回
    支持熔断,当线程池到达maxSize后,再请求会触发fallback接口进行熔断
    隔离原理:每个服务单独使用线程池
    可以是异步,也可以是同步。看调用的方法
    资源消耗大,大量线程的上下文切换,容易造成机器负载高
    2)信号量隔离
    它和线程池技术一样,控制了服务可以被同时访问的并发数量。
    不支持超时,如果阻塞,只能通过调用协议(如:socket超时才能返回)
    支持熔断,当信号量达到maxConcurrentRequests后。再请求会触发fallback
    隔离原理:通过信号量的计数器
    支持同步调用,不支持异步
    资源消耗小,只是个计数器
  5. 服务限流
    1)限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。
    2)限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。
    3)流量控制
    网关限流:防止大量请求进入系统,消息队列Mq实现流量消峰
    用户交流限流:提交按钮限制、点击频率限制等

2、SpringCloudResilience4J服务断路器

  1. Resilience4J服务断路器
    1)Hystrix,这个断路器是SpringCloud中最早支持的一种容错方案,现在这个断路器已经处于维护状态,已经不再更新了,Hystrix依赖了Archaius ,Archaius本身又依赖很多第三方包,例如 Guava、Apache Commons Configuration 等。
    2)Resilience4J是一个轻量级的容错组件,其灵感来自于Hystrix,但主要为Java 8和函数式编程所设计,也就是我们的lambda表达式。轻量级体现在其只用 Vavr 库(前身是 Javaslang),没有任何外部依赖。
  2. Resilience4J的可用性功能:
    1)resilience4j-circuitbreaker:熔断
    2)resilience4j-ratelimiter:限流
    3)resilience4j-bulkhead:隔离
    4)resilience4j-retry:自动重试
    5)resilience4j-cache:结果缓存
    6)resilience4j-timelimiter:超时处理
    在使用Resilience4j的过程中,不需要引入所有的依赖,只引入需要的依赖即可。
  3. 断路器CircuitBreaker通常存在三种状态(CLOSE、OPEN、HALF_OPEN),并通过一个时间或数量窗口来记录当前的请求成功率或慢速率,从而根据这些指标来作出正确的容错响应。
    6种状态:
    1)CLOSED: 关闭状态,代表正常情况下的状态,允许所有请求通过,能通过状态转换为OPEN
    2)HALF_OPEN: 半开状态,即允许一部分请求通过,能通过状态转换为CLOSED和OPEN
    3)OPEN: 熔断状态,即不允许请求通过,能通过状态转为为HALF_OPEN
    4)DISABLED: 禁用状态,即允许所有请求通过,出现失败率达到给定的阈值也不会熔断,不会发生状态转换。
    5)METRICS_ONLY: 和DISABLED状态一样,也允许所有请求通过不会发生熔断,但是会记录失败率等信息,不会发生状态转换。
    6)FORCED_OPEN: 与DISABLED状态正好相反,启用CircuitBreaker,但是不允许任何请求通过,不会发生状态转换。
  4. 主要的3种状态
    1)closed -> open : 关闭状态到熔断状态, 当失败的调用率(比如超时、异常等)默认50%,达到一定的阈值服务转为open状态,在open状态下,所有的请求都被拦截。
    2)open-> half_open: 当经过一定的时间后,CircubitBreaker中默认为60s服务调用者允许一定的请求到达服务提供者。
    3)half_open -> open: 当half_open状态的调用失败率超过给定的阈值,转为open状态
    half_open -> closed: 失败率低于给定的阈值则默认转换为closed状态

3、Resilience4J的超时降级 (timelimiter)

  1. 在cloud父工程下创建一个子模块cloud-consumer-resilience4j-order80

  2. 在该子模块的POM文件中,添加如下依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloud</artifactId>
            <groupId>com.zzx</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-consumer-resilience4j-order80</artifactId>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
        </properties>
        <dependencies>
            <!--  引入Eureka 客户端依赖   -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <!--   引入服务调用依赖 OpenFigen   -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
            </dependency>
            <!-- actuator监控信息完善 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <!-- 引入断路器依赖resilience4j -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
            </dependency>
        </dependencies>
    
    
    </project>
    

    springcloud新版本不需要引入io.github.resilience4j的包。

  3. 在该子模块中的java目录中创建包com.zzx,并在该包下创建主启动类OrderResilience4JMain80

    package com.zzx;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * 主启动类
     */
    @EnableFeignClients
    @SpringBootApplication
    @Slf4j
    public class OrderResilience4JMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderResilience4JMain80.class,args);
            log.info("***** OrderResilience4JMain80服务 启动成功 *****");
        }
    }
    
    
  4. 在该子模块中的包com.zzx下,创建包service,并在该包下创建OpenFeign服务调用的接口PaymentOpenFeignService

    package com.zzx.service;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    /**
     * 支付远程调用OpenFeign接口
     */
    //根据服务生产者的名字调用
    @FeignClient("CLOUD-PAYMENT-PROVIDER")
    public interface PaymentOpenFeignService {
        @GetMapping("/payment/timeout")
        String timeout();
    }
    
    

    调用支付服务提供者的测试超时方法。

  5. 在resources目录中创建application.yml文件,代码如下

    server:
      port: 80
    eureka:
      instance:
        # 注册的实例名
        instance-id: cloud-consumer-resilience4j-order80
      client:
        service-url:
          # Eureka server的地址
          #单机
          #defaultZone: http://localhost:7001/eureka/
          #集群
          defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
    spring:
      application:
        #设置应用名
        name: cloud-order-consumer
    #超时机制
    resilience4j:
      timelimiter:
        instances:
          delay:
            # 设置超时时间2s
            timeout-duration: 2
    
    
    

    即配置resilience4j的超时机制。

  6. 在该子模块中的包com.zzx下,创建包controller,并在该包下创建控制层类OrderController

    package com.zzx.controller;
    
    import com.zzx.service.PaymentOpenFeignService;
    import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.concurrent.CompletableFuture;
    import java.util.function.Supplier;
    
    /**
     * 订单控制层
     */
    @Slf4j
    @RestController
    @RequestMapping("order")
    public class OrderController {
        @Autowired
        private PaymentOpenFeignService paymentOpenFeignService;
    
        /**
         * 测试超时降级
         *
         * @return
         */
        @GetMapping("timeout")
        @TimeLimiter(name = "delay", fallbackMethod = "timeoutfallback")
        public CompletableFuture<String> timeout() {
            log.info("*********进入方法*********");
            //异步操作
            CompletableFuture<String> timeout = CompletableFuture.supplyAsync((Supplier<String>)()->paymentOpenFeignService.timeout());
            log.info("*********离开方法*********");
            return timeout;
        }
    
        /**
         * 超时服务降级方法
         * @param e
         * @return
         */
        public CompletableFuture<ResponseEntity> timeoutfallback(Exception e){
            e.printStackTrace();
            return CompletableFuture.completedFuture(ResponseEntity.ok("读取超时"));
        }
    }
    
    

    即使用OpenFeign调用支付服务提供者的测试超时方法。此时异步操作返回类型为CompletableFuture<String>,使用注解@TimeLimiter来实现超时,注解中的name属性的值跟yml文件的resilience4j.timelimiter.instances的下一个属性名相同,然后在yml文件中该属性名的下一个属性,也就是在设置超时时间的属性中设置超时时间的值。
    fallbackMethod属性,即设置超时的时候回调的方法名,该方法可以使服务降级等,即直接返回一个友好提示信息。
    supplyAsync需要使用函数作为参数,此时应用lambda箭头函数()->的方式。

  7. 访问浏览器进行测试:http://localhost/order/timeout
    在这里插入图片描述

4、Resilience4J的重试机制(retry)

  1. 在该子模块中的包com.zzx.controller下的控制层类OrderController中,添加重试机制的方法retry

    /**
     * 重试
     * @return
     */
    @GetMapping("retry")
    @Retry(name = "backendA")
    public CompletableFuture<String> retry() {
        log.info("*********进入方法*********");
        //异步操作
        CompletableFuture<String> timeout = CompletableFuture.supplyAsync((Supplier<String>)()->paymentOpenFeignService.timeout());
        log.info("*********离开方法*********");
        return timeout;
    }
    

    此时使用resilience4j的retry注解,name的属性值与yml文件中对应。

  2. 在yml文件中添加重试机制的配置

    resilience4j:
      #超时机制
      timelimiter:
        instances:
          delay:
            # 设置超时时间2s
            timeout-duration: 2
      #重试机制
      retry:
        instances:
          backendA:
            #最大重试次数
            max-attempts: 3
            #固定的重试间隔
            wait-duration: 10s
            enable-exponential-backoff: true
            exponential-backoff-multiplier: 2
    

    每隔10s重试一次,最多重试3次。

  3. 测试重试机制
    1)此时重启子模块cloud-consumer-resilience4j-order80,并且关闭子模块支付提供者payment8001服务。因为当连接不上服务提供者时,会进行重试。
    2)浏览器访问:http://localhost/order/retry

5、Resilience4J的熔断降级(circuitbreaker)

  1. JMETER的安装使用:https://blog.csdn.net/weixin_49076273/article/details/126022329

  2. 在yml文件中配置熔断机制

    #熔断机制
    resilience4j.circuitbreaker:
        configs:
          default:
            # 熔断器打开的失败阈值30%
            failureRateThreshold: 30
            # 默认滑动窗口大小,circuitbreaker使用基于计数和时间范围欢动窗口聚合统计失败率
            slidingWindowSize: 10
            # 计算比率的最小值,和滑动窗口大小去最小值,即当请求发生5次才会计算失败率
            minimumNumberOfCalls: 5
            # 滑动窗口类型,默认为基于计数的滑动窗口
            slidingWindowType: TIME_BASED
            # 半开状态允许的请求数
            permittedNumberOfCallsInHalfOpenState: 3
            # 是否自动从打开到半开
            automaticTransitionFromOpenToHalfOpenEnabled: true
            # 熔断器从打开到半开需要的时间
            waitDurationInOpenState: 2s
            recordExceptions:
              - java.lang.Exception
        instances:
          backendA:
            baseConfig: default
    
  3. 在该子模块中的包com.zzx.controller下的控制层类OrderController中,添加熔断机制的方法circuitbreaker

    /**
         * 熔断降级
         * @return
         */
        @GetMapping("circuitbreaker")
        @CircuitBreaker(name = "backendA",fallbackMethod = "circuitbreakerfallback")
        public String circuitbreaker() {
            log.info("*********进入方法*********");
            String index = paymentOpenFeignService.index();
            log.info("*********离开方法*********");
            return index;
        }
        public String circuitbreakerfallback(Exception e){
            e.printStackTrace();
            return "服务器繁忙,请稍后重试";
        }
    
  4. 用Jmeter进行测试:
    在配置完参数后,点击运行即可。
    在这里插入图片描述
    此时有10个http请求该地址,先把payment8001服务关闭,使错误率提高,让cirvuitbreaker熔断器的状态切换为打开,即不再接收http请求。过了2秒又切换为半开状态,此时半开最多只能接收3个请求,如果此时3个请求错误率还是很高,则又会切换到打开状态。等2秒又切换回半开,直到3个请求的成功率达到阈值,才会将状态切换为关闭。
    然后这里使用了熔断降级,所以,每次当状态为打开时,后面2秒的http请求都会返回一个友好提示信息。直接半开或关闭状态才继续接收http请求去访问服务提供者。

6、Resilience4J的慢调用熔断降级(slowcircuitbreaker)

  1. 在yml文件中配置慢调用熔断降级

      #慢调用熔断降级
      backendB:
        # 熔断器打开的失败阈值
        failureRateThreshold: 50
        # 慢调用时间阈值 高于这个阈值的
        slowCallDurationThreshold: 2s
        # 慢调用百分比阈值,断路器调用时间大于slow
        slowCallRateThreshold: 30
        slidingWindowSize: 10
        slidingWindowType: TIME_BASED
        minimumNumberOfCalls: 2
        permittedNumberOfCallsInHalfOpenState: 2
        waitDurationInOpenState: 2s
        eventConsumerBufferSize: 10
    

    与熔断降级有一些不同,慢调用熔断降级,顾名思义有慢调用的配置,也就是熔断器在http请求超时的配置。
    该配置中,即http请求超过2s的比例高于等于30%或者失败的阈值高于50%,就开启熔断器。

  2. 在该子模块中的包com.zzx.controller下的控制层类OrderController中,添加慢调用熔断降级的方法slowcircuitbreaker

    /**
     * 慢调用熔断降级
     * @return
     */
    @GetMapping("slowcircuitbreaker")
    @CircuitBreaker(name = "backendB",fallbackMethod = "slowcircuitbreakerfallback")
    public String slowcircuitbreaker() {
        log.info("*********进入方法*********");
        String index = paymentOpenFeignService.timeout();
        log.info("*********离开方法*********");
        return index;
    }
    public String slowcircuitbreakerfallback(Exception e){
        e.printStackTrace();
        return "正在处理,请稍等";
    }
    
  3. 测试
    1)使用Jmeter测试该url映射接口
    在这里插入图片描述
    2)JMeter在第一次发送http请求时,慢调用使熔断器处于关闭状态,2s后打开半开状态。在第二次发送http请求时,熔断器处于半开状态,这时候在访问两次http请求后,不会访问服务提供者payment8001,而是直接返回友好提示信息。
    在这里插入图片描述

四、SpringCloudResilience4J服务断路器2

1、Resilience4J信号量服务隔离(bulkhead)

  1. 在消费者服务的yml文件中配置信号量隔离

    resilience4j:
     #信号量隔离
      bulkhead:
       instances:
        backendA:
        # 隔离允许并发线程执行的最大数量
         maxConcurrentCalls: 5
        # 当达到并发调用数量时,新的线程的阻塞时间
         maxWaitDuration: 20ms
    
    
  2. 在该子模块中的包com.zzx.controller下的控制层类OrderController中,添加信号量隔离的方法bulkhead

     /**
     * 信号量隔离
     * @return
     */
    @GetMapping("bulkhead")
    @Bulkhead(name = "backendA",type = Bulkhead.Type.SEMAPHORE)
    public String bulkhead() {
        log.info("*********进入方法*********");
        String index = paymentOpenFeignService.timeout();
        log.info("*********离开方法*********");
        return index;
    }
    
  3. 测试
    1)重启消费者服务后,配置访问的url接口进行测试
    在这里插入图片描述

2、Resilience4J线程池服务隔离(bulkhead)

  1. 在消费者服务的yml文件中配置线程池隔离

    #线程池隔离
    esilience4j:
      thread-pool-bulkhead:
        instances:
          backendA:
            # 最大线程池大小
            maxThreadPoolSize: 4
            # 核心线程池大小
            coreThreadPoolSize: 2
            # 队列容量
            #queueCapacity: 2
    
  2. 在该子模块中的包com.zzx.controller下的控制层类OrderController中,添加线程池隔离的方法future

     /**
     * 线程池隔离
     * @return
     */
    @GetMapping("thread")
    @Bulkhead(name = "backendA",type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture future() throws InterruptedException {
        log.info("*********进入方法*********");
        TimeUnit.SECONDS.sleep(5);
        String index = paymentOpenFeignService.timeout();
        log.info("*********离开方法*********");
        return CompletableFuture.supplyAsync(()->"信号量");
    }
    

3、Resilience4J的服务限流(ratelimiter)

  1. 在消费者服务的yml文件中配置服务限流

    resilience4j:
      ratelimiter:
       instances:
        backendA:
        # 限流周期时长。    默认:500纳秒
         limitRefreshPeriod: 5s
        # 周期内允许通过的请求数量。    默认:50
         limitForPeriod: 2
    
    
  2. 在该子模块中的包com.zzx.controller下的控制层类OrderController中,添加服务限流的方法rateLimiter

    /**
     * 限流
     * @return
     */
    @GetMapping("rate")
    @RateLimiter(name = "backendA")
    public String rateLimiter() throws InterruptedException {
        log.info("*********进入方法*********");
        TimeUnit.SECONDS.sleep(5);
        //异步操作
        String index = paymentOpenFeignService.index();
        log.info("*********离开方法*********");
        return index;
    }
    
  3. 测试服务限流
    1)使用JMeter测试
    在这里插入图片描述
    在这里插入图片描述
    此时刚好5秒通过2次。

总结(SpringCloud 2022.0.2和SpringBoot 3.0.5)

  1. 1)客户端负载均衡,可以使用Eureka来获取微服务实例清单,缓存到本地;然后通过Loadbalancer根据负载均衡策略从服务实例清单列表中选择具体实例。
    2)服务端负载均衡,发起服务间调用的时候,服务请求并不直接发向目标服务器,而是发给这个全局负载均衡器,它再根据配置的负载均衡策略将请求转发到目标服务。
    3)客户端负载均衡,SpringCloudRibbon在Ribbon2已经停更,并且1和2不兼容,后面SpringCloud将SpringCloudLoadBalancer作为新的客户端负载均衡器。
  2. 1)SpringCloudOpenFeign的作用是服务调用,并且支持SpringMVC的标准注解。
    OpenFeign服务调用的流程,即先创建一个OpenFeign的接口,并为其指定应用名,以及拼接上方法上对应的url;在Controller中自动装配后,再调用对应的接口方法即可。
    2)OpenFeign服务调用的日志级别有4个,分别是NONE、BASIC、HEADERS、FULL。
    设置OpenFeign的日志级别,需要定义一个配置类,在类中定义一个方法,返回OpenFeign的日志级别,将该方法上方添加@Bean注解,交给SpringIOC容器管理;并在yml文件中配置即可。
    3)设置超时时间,首先需要在服务消费者的yml文件中配置OpenFeign的超时时间;然后通过消费者调用提供者,提供者超时则会报错。
  3. 避免造成雪崩的方案
    1)服务熔断,调用错误率飙升,当错误率达到一定阈值后,就将这个服务熔断了。熔断之后,后续的请求就不会再请求服务器了,以减缓服务器的压力。
    2)服务降级 fallback,不让客户端等待并立即返回一个友好的提示。触发服务降级的情况有程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级。
    3)服务隔离,主要目的是为了解决服务与服务之间的影响。有信号量隔离和线程池隔离两种方式。线程池隔离支持的功能更多,但是资源消耗也更大。信号量隔离支持的功能比线程池隔离少,但是资源消耗也小。
    4)服务限流,主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。
    服务熔断和服务隔离都属于出错后的容错处理机制,而限流模式则可以称为预防模式。
  4. 1)服务断路器Resilience4J,提供了6个避免服务雪崩的功能,熔断、限流、隔离、自动重试、结果缓存、超时处理。
    Resilience4J只使用Vavr库,没有外部依赖。使用过程中只需要按需引入即可。
    2)CircubitBreaker断路器有6种状态,分别是CLOSED、HALF_OPEN、OPEN、DISABLED、METRICS_ONLY、FORCED_OPEN;
    主要的状态有3种,分别是CLOSED、HALF_OPEN、OPEN;closed可以转换为open,open转换为half_open,half_open转换为open或closed状态。
    CircubitBreaker断路器最开始的状态为关闭状态。
    3)Resilience4J的超时降级,Resilience4J需要使用注解@TimeLimiter,此时name需要跟yml文件的resilience4j.timelimiter.instances的下一个属性名相同,并在下一个属性中设置超时时间;它的fallbackMethod属性是超时的时候回调的方法名可以处理服务降级等操作,即直接返回一个友好提示信息。
    异步操作时,返回类型为CompletableFuture<String>,supplyAsync需要使用函数作为参数,此时应用lambda箭头函数()->的方式去调用OpenFeign对应的方法即可。
    并且还需要引入spring-cloud整合resilience4j的包即可。新版本的SpringCloud不需要引入io.github.resilience4j中的包;但是老版本的则需要引入该包。
    4)Resilience4J的重试机制,即使用resilience4j中的retry注解,name属性值与yml文件对应的值绑定;并且配置retry重试机制的yml文件即可。
    5)熔断机制降级,即需要在yml文件中配置熔断机制;在URL映射方法上使用@CircuitBreaker注解,以及降级的方法名。当http请求达到一定数量,并且错误率达到阈值时,则将熔断器的状态切换打开,过了设置的时间后,切换为半开,此时接收设置的http数量,如果错误率还是达到阈值,则再次切换为打开状态,直到成功率达到阈值,再切换为关闭状态。当处于打开状态时,http请求会得到一个友好提示,而不是访问payment8001服务。当处于半开或关闭的状态才会访问payment8001服务。
    6)慢调用熔断降级,在调用服务提供者payment8001时,如果调用时间超时并且超时率达到阈值,则打开熔断器,过了指定的时间后,打开半开状态。半开状态设置只接收2次http请求,如果成功率达到阈值,则关闭熔断器;或者达到失败率则开启熔断器;否则一直处于半开状态。在打开状态时,所有http请求不访问服务提供者payment8001,而是直接返回友好提示信息;关闭状态时,所有http请求都直接访问服务提供者payment8001。
  5. Resilience4J在这里不知道是不是版本bug的原因,服务隔离bulkhead目前配置是不生效的,也就是说信号量和服务隔离配置不生效。
    1)信号量服务隔离的基本原理就是一个计数器,它控制线程的并发数不超过一个指定数额,超过的话就进行隔离。使用@Bulkhead注解。
    2)线程池服务隔离的基本原理就是通过指定核心线程池数和最大线程池数以及队列容量等来控制线程的并发数,从而实现服务的隔离。使用@Bulkhead注解。
    3)服务限流,即指定服务的周期时间,以及在周期时间内通过的请求数量。使用@RateLimiter注解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/465546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vector - CAPL - 检查DUT发出与dbc定义一致

目录 ChkCreate_UndefinedMessageReceived 代码示例 有回调函数 无回调函数 Trace报文 报告显示 ChkCreate_UndefinedMessage

一维离散小波变换原理和代码实现

基本原理&#xff1a; 离散小波变换&#xff1a;对连续小波变换的尺度因子和时移动因子采用不同的离散条件进行离散&#xff0c;得到Discrete Wavelet Transform(DWT)。降低计算量的同时&#xff0c;保持连续小波变换的光滑性、紧支性、对称性。 离散小波函数&#xff1a; ψ…

脚本函数基础

shell脚本编程系列 函数是一个脚本代码块&#xff0c;可以为其命名并在脚本中的任何位置重用它。每当需要在脚本中使用该代码块时&#xff0c;直接写函数名即可。称作调用函数。 创建函数 方式1&#xff1a; function name {commands }name定义了该函数的唯一名称&#xff0…

私有部署、重构企业软件,第四范式发布大模型“式说”

大模型领域再添重要一员&#xff01; 4月26日&#xff0c;第四范式首次向公众展示其大模型产品「式说3.0」&#xff0c;并首次提出AIGS战略&#xff08;AI-Generated Software&#xff09;&#xff1a;以生成式AI重构企业软件。式说将定位为基于多模态大模型的新型开发平台&…

ImageJ 用户手册——第五部分(菜单命令Process)

这里写目录标题 ImageJ 用户手册——第五部分29. Process29.1 Smooth29.2 Sharpen29.3 Find Edges29.4 Find Maxima29.5 Enhance Contrast29.6 Noise29.6.1 Add Noise29.6.2 Add Specified Noise29.6.3 Salt and Pepper29.6.4 Despeckle29.6.5 Remove Outliers29.6.6 Remove Na…

Shell编程条件语句 if case (愿此行终抵群星)

一、Shell条件语句 Shell环境根据命令执行后的返回状态值&#xff08;$?&#xff09;来判断是否执行成功&#xff0c;当返回值为0(真 true)时表示成功&#xff0c;返回值为非0值&#xff08;假 false)时表示失败或异常。 test 命令 更多操作可使用 man test 查看 1.条件表达式…

深度学习论文分享(二)Data-driven Feature Tracking for Event Cameras

深度学习论文分享&#xff08;二&#xff09;Data-driven Feature Tracking for Event Cameras&#xff08;CVPR2023&#xff09; 前言Abstract1. Introduction2. Related Work3. Method3.1. Feature Network3.2. Frame Attention Module3.3. Supervision 4. Experiments5. Con…

焕新时刻,移动云品牌升级燃动十一城

4月25日&#xff0c;在2023移动云大会上&#xff0c;移动云品牌形象全方位焕新&#xff0c;启用新品牌LOGO和品牌标语&#xff0c;在政府领导、院士专家、行业大咖等3000多位参会嘉宾见证下&#xff0c;吹响品牌进阶新号角。 24日晚&#xff0c;移动云品牌焕新亮灯仪式率先在苏…

C语言入门篇——指针篇

目录 1、指针 1.1内存地址 1.2基地址 1.3指针变量 2、指针类型 2.1指针-整数 2.2指针的解引用 3、特殊指针 3.1野指针 3.2空指针 4、指针运算 4.1指针-指针 4.2指针的关系运算 5、指针和数组 6、二级指针 7、指针数组 1、指针 1.1内存地址 内存是电脑上特别重…

剑指offer 26树的子结构

文章目录 题目详情分析判断A中有出现和B相同的结构和节点值递归主体&#xff0c;解决问题 完整代码总结 题目详情 树的子结构 输入两棵二叉树A和B&#xff0c;判断B是不是A的子结构。(约定空树不是任意一个树的子结构) B是A的子结构&#xff0c; 即 A中有出现和B相同的结构和节…

合并两个有序链表

文章目录 1.题目描述2.解题思路方法1&#xff1a;方法2&#xff1a; 1.题目描述 题目链接&#xff1a;力扣21&#xff0c;合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 2.解题思路 方法1&#xff1a;…

【python】多进程和多线程

【Python】多线程 概念 同一时间内能够执行多个任务 执行方式 并发&#xff1a;多个任务交替去运行 并发&#xff1a;多喝CPU中&#xff0c;多个任务是在多个CPU上运行的 一般来说&#xff1a;并发和并行是同时存在的&#xff0c;是操作系统自动调用的 实现方式 多进程实…

系统集成项目管理工程师 笔记(第九章:项目成本管理)

文章目录 9.1.1 成本与成本管理概念 329项目成本管理的过程 9.1.2 相关术语 331成本的类型&#xff08;6种&#xff09;应急储备与管理储备 9.2.3 项目成本管理计划制订的 输出 3349.3.1 项目成本估算的主要相关因素 335项目估算还需要考虑但 容易被忽视的 主要因素 9.3.2 项目…

随机森林(Random Forest)简单介绍

文章目录 随机森林&#xff08;Random Forest&#xff09;简单介绍1. 介绍2. 工作原理2.1 随机森林的基本原理2.1.1 随机采样2.1.2. 随机选特征2.1.3. 多数表决 2.2 随机森林的建模过程2.2.1. 建立多颗决策树2.2.2. 特征随机选择2.2.3. 样本随机选择2.2.4. 决策树训练与生成2.2…

发现【Stable-Diffusion WEBUI】的神奇插件:离线自动翻译中文提示词

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;提示词翻译&#xff08;Prompt Translator&#xff09;&#xff08;1.1&#xff09;尚需改进 &#xff08;零&#xff09;前言 本篇主要提到了WEBUI的提示词翻译插件&#xff0c;之前我说不喜欢联网的插件&a…

【单目标优化算法】沙猫群优化算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【011】pandas处理数据的作用_#py

pandas操作 1. 导入数据库2. 修改行列名2.1 修改列名2.2 修改行名 3. 按条件筛选3.1 根据条件筛选&#xff0c;提取所有信息3.2 根据条件筛选&#xff0c;提取某列或者某几列 4. 按某行某列提取信息4.1 获取信息4.2 更改具体的值 5. 合并5.1 单列并入大数据中&#xff08;方法一…

根据虚拟地址,如何求出页号和偏移量?

方法掌握 虚拟地址划分成虚拟页号和虚拟页偏移量。 物理地址同样可划分为物理页号和物理页偏移量 如何划分&#xff0c;关键点在于页面的大小。 假设给你一个十进制表示的地址20000&#xff0c;一个页面的大小为4KB&#xff0c;那么如何找出地址20000的具体位置呢&#xff1f…

消息队列介绍

一、消息队列(MQ)概述 消息队列&#xff08;Message Queue&#xff09;&#xff0c;是分布式系统中重要的组件&#xff0c;其通用的使用场景可以简单地描述为&#xff1a; 当不需要立即获得结果&#xff0c;但是并发量又需要进行控制的时候&#xff0c;差不多就是需要使用消息队…

如何获取“上帝”的系统版本

如何获取“上帝”的系统版本 在对技术支持过程中&#xff0c;每个公司都难免会遇到这样一个困扰&#xff1a;我们的程序明明在内部环境中测试的好好的&#xff0c;但是到了客户手里总会出现各种问题。 从以往累计的经验来看&#xff0c;这类问题通常和客户的系统环境有关。所…