springcloud第4季 使用resilience4j实现服务流量治理(熔断,降级,限流,隔离)

一  前言

1.1 断路器介绍

断路器是一种开关装置,当某个服务单元发生故障后,通过断路器向调用方返回一个符合预期,可处理的备选响应。保证服务不会被长时间,不必要的占用,从而避免在分布式系统故障的蔓延、乃至雪崩。

断路器使用的判断策略有:基于访问数量的滑动窗口和基于时间的滑动窗口。

基于访问数量的滑动窗口统计最近N次调用的返回结果。

基于时间的滑动窗口统计N秒调用的返回结果。

1.2 断路器的工作流程

1.当满足一定的峰值和失败率达到一定条件后,断路器将会进入open状态,服务熔断。

当open的时候,所有请求都不会调用主业务逻辑方法,而是直接走fallbackmethod兜底的方法,服务降级,一段时间后,断路器会从open状态变为half_open半开状态,会放几个请求过去:

如果成功,断路器会关闭close,如果失败;继续开启,重复上述动作。

1.3 断路器与resilience4j的关系

circuit break 是一种规范和接口,resilience4j是具体实现

resilience4j  等于 resilience   for  java

resilience   弹性,张力

1.4 Resilience4j

resilience4j是一个轻量级的容错框架,能够实现断路器,限流器,重试等功能。

二  基于统计次数实现的熔断

2.1 消费端

满足50%错误后,触发熔断并给出服务降级,告知调用者服务不可用,此时就算是输入正确的访问地址也无法调用服务。它还在断路中(open状态),一会过度到半开并继续正确地址访问,慢慢切换到close状态,可以正常访问链路回复。

2.1.1 pom文件

1.pom文件内容的配置

        <!--resilience4j-circuitbreaker-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>
        <!-- 由于断路保护等需要AOP实现,所以必须导入AOP包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.1.2.application配置

内容

# feign日志以什么级别监控哪个接口
#logging:
#  level:
#    com:
#      jurf:
#        ms:
#          api:
#            sb:
#              PayFeginApi: debug
# Resilience4j CircuitBreaker 按照次数:COUNT_BASED 的例子
#  6次访问中当执行方法的失败率达到50%时CircuitBreaker将进入开启OPEN状态(保险丝跳闸断电)拒绝所有请求。
#  等待5秒后,CircuitBreaker 将自动从开启OPEN状态过渡到半开HALF_OPEN状态,允许一些请求通过以测试服务是否恢复正常。
#  如还是异常CircuitBreaker 将重新进入开启OPEN状态;如正常将进入关闭CLOSE闭合状态恢复正常处理请求。
resilience4j:
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
        slidingWindowType: COUNT_BASED # 滑动窗口的类型
        slidingWindowSize: 6 #滑动窗⼝的⼤⼩配置COUNT_BASED表示6个请求,配置TIME_BASED表示6秒
        minimumNumberOfCalls: 6 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。如果minimumNumberOfCalls为10,则必须最少记录10个样本,然后才能计算失败率。如果只记录了9次调用,即使所有9次调用都失败,断路器也不会开启。
        automaticTransitionFromOpenToHalfOpenEnabled: true # 是否启用自动从开启状态过渡到半开状态,默认值为true。如果启用,CircuitBreaker将自动从开启状态过渡到半开状态,并允许一些请求通过以测试服务是否恢复正常
        waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
        permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。在半开状态下,CircuitBreaker将允许最多permittedNumberOfCallsInHalfOpenState个请求通过,如果其中有任何一个请求失败,CircuitBreaker将重新进入开启状态。
        recordExceptions:
          - java.lang.Exception
    instances:
      ms-provider:
        baseConfig: default

2.1.3.controller配置

@RestController
public class OrderCircuitController {
    @Resource
    private PayCircuitFegin payCircuitFegin;

    @GetMapping(value = "/feign/pay/circuit/{id}")
    @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback")
    public String myCircuitBreaker(@PathVariable("id") Integer id)
    {
        return payCircuitFegin.myCircuit(id);
    }
    //myCircuitFallback就是服务降级后的兜底处理方法
    public String myCircuitFallback(Integer id,Throwable t) {
        // 这里是容错处理逻辑,返回备用结果
        return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
    }
}

2.1.4.启动类

@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
@EnableFeignClients(basePackages="com.jurf.ms")//启用feign客户端,定义服务+绑定接口,以声明式的方法优雅而简单的实现服务调用
public class FeginApp8081
{
    public static void main( String[] args )
    {
        SpringApplication.run(FeginApp8081.class,args);
        System.out.println( "Hello World!" );
    }
}

2.2 fegin工具api

2.3  服务端

2.3.1  结构

2.3.2 业务类的编写

@RestController
public class PayCircuitController {
    //=========Resilience4j CircuitBreaker 的例子
  @GetMapping(value = "/pay/circuit/{id}")
    public String myCircuit(@PathVariable("id") Integer id)
    {
        System.out.println("调到服务端: 参数为:"+id);
        if(id == -4) throw new RuntimeException("----circuit id 不能负数");

        if(id == 9999)
        {
            try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        return "Hello, circuit! inputId:  "+id+" \t 随机值:" + IdUtil.simpleUUID();
    }
}

2.3.3 启动类

@SpringBootApplication
@MapperScan("com.jurf.ms.provider9091.mapper") //import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient
@RefreshScope // 动态刷新
public class App9091
{
    public static void main( String[] args )
    {
        SpringApplication.run(App9091.class,args);
        System.out.println( "Hello World!" );
    }
}

2.4 测试验证

2.4.1 基础服务启动

1.确保注册中心consul服务启动起来;

2.确保zipkin服务启动起来:

D:\>java -jar zipkin-server-2.23.4-exec.jar

2.4.2 应用服务启动

1.启动服务

2.访问测试

1.异常地址:http://localhost:8081/feign/pay/circuit/-4

 2.正常地址:http://localhost:8081/feign/pay/circuit/123

当触发失败率达到50%时候,正常的访问的地址也报,服务降级提示

三 基于时间的熔断

3.1 逻辑思想

3.2 application配置

配置内容

resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 10s #神坑的位置,timelimiter 默认限制远程1s,超于1s就超时异常,配置了降级,就走降级逻辑
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 50 #设置50%的调用失败时打开断路器,超过失败请求百分⽐CircuitBreaker变为OPEN状态。
        slowCallDurationThreshold: 2s #慢调用时间阈值,高于这个阈值的视为慢调用并增加慢调用比例。
        slowCallRateThreshold: 30 #慢调用百分比峰值,断路器把调用时间⼤于slowCallDurationThreshold,视为慢调用,当慢调用比例高于阈值,断路器打开,并开启服务降级
        slidingWindowType: TIME_BASED # 滑动窗口的类型
        slidingWindowSize: 2 #滑动窗口的大小配置,配置TIME_BASED表示2秒
        minimumNumberOfCalls: 2 #断路器计算失败率或慢调用率之前所需的最小样本(每个滑动窗口周期)。
        permittedNumberOfCallsInHalfOpenState: 2 #半开状态允许的最大请求数,默认值为10。
        waitDurationInOpenState: 5s #从OPEN到HALF_OPEN状态需要等待的时间
        recordExceptions:
          - java.lang.Exception
    instances:
      ms-provider:
        baseConfig: default

3.3 测试

3.3.1 基础服务启动

1.确保注册中心consul服务启动起来;

2.确保zipkin服务启动起来:

D:\>java -jar zipkin-server-2.23.4-exec.jar

3.3.2 应用服务启动

1.服务启动

2.测试

1.开启异常4个窗口:http://localhost:8081/feign/pay/circuit/9999

2.开启一个正常接口: http://localhost:8081/feign/pay/circuit/123

3.出现4个异常都慢调用,触发30%,则进行服务降级,影响到正常访问也无法访问

四  隔离

4.1 隔离

Resilence4j 提供了两种隔离的方式,可以限制并发执行的数量:

1.实现SemaphoneBulkhead 信号量舱壁

2.实现FixedThreadPoolBulkhead(固定线程池舱壁)

作用:用来限制对下游服务的最大并发数量的限制。

4.2  基于信号量的实操

4.2.1 消费端的配置

1.pom文件

       <!--resilience4j-bulkhead-->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-bulkhead</artifactId>
        </dependency>

2.配置文件

  ####resilience4j bulkhead 的例子
resilience4j:
    bulkhead:
      configs:
        default:
          maxConcurrentCalls: 1 # 隔离允许并发线程执行的最大数量
          maxWaitDuration: 200ms # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback
      instances:
        ms-provider:
          baseConfig: default
    timelimiter:
      configs:
        default:
          timeout-duration: 20s

3.业务类

    /**
     *(船的)舱壁,隔离
     * @param id
     * @return
     */
    @GetMapping(value = "/feign/pay/bulkhead999/{id}")
    @Bulkhead(name = "ms-provider",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE)
    public String myBulkhead(@PathVariable("id") Integer id)
    {
        return payCircuitFegin.myBulkhead(id);
    }
    public String myBulkheadFallback(Throwable t)
    {

        return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";
    }

4.2.2  openfign的公用接口

1.接口代码

    /**
     * Resilience4j Bulkhead 的例子
     * @param id
     * @return
     */
    @GetMapping(value = "/pay/bulkhead/{id}")
    public String myBulkhead(@PathVariable("id") Integer id);

2.截图

4.2.3  消费提供端

1.工程结构

2.业务类

    //=========Resilience4j bulkhead 的例子
    @GetMapping(value = "/pay/bulkhead/{id}")
    public String myBulkhead(@PathVariable("id") Integer id)
    {
        if(id == -4) throw new RuntimeException("----bulkhead id 不能-4");

        if(id == 9999)
        {
            System.out.println("99999");
            try { TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return "Hello, bulkhead! inputId:  "+id+" \t " + IdUtil.simpleUUID();
    }

4.2.4 测试验证

4.2.4.1 基础服务启动

 1.确保注册中心consul服务启动起来;

2.确保zipkin服务启动起来:

D:\>java -jar zipkin-server-2.23.4-exec.jar

4.2.4.2 应用服务启动

1.默认配置概述

2.测试方式

访问地址:

http://localhost:8081/feign/pay/bulkhead999/9999

多打开几个请求这个地址的窗口,这里打开5个

其中第6个,最后一个访问地址:http://localhost:8081/feign/pay/bulkhead999/44

可以看到出现了,服务降级提示。

4.3  基于线程池实现隔离

4.3.1 消费端

1.pom文件

       <!--resilience4j-bulkhead-->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-bulkhead</artifactId>
        </dependency>

2.配置文件

####resilience4j bulkhead -THREADPOOL的例子
resilience4j:
  timelimiter:
    configs:
      default:
        timeout-duration: 10s #timelimiter默认限制远程1s,超过报错不好演示效果所以加上10秒
  thread-pool-bulkhead:
    configs:
      default:
        core-thread-pool-size: 1
        max-thread-pool-size: 1
        queue-capacity: 1
    instances:
      ms-test666:
        baseConfig: default

3.业务controller类:注意@Bulkhead注解的name为要调用提供服务的名称

    @GetMapping(value = "/feign/pay/bulkhead/{id}")
    @Bulkhead(name = "ms-test666",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id)
    {
        System.out.println(Thread.currentThread().getName()+"\t"+"---开始进入");
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName()+"\t"+"---准备离开");

        return CompletableFuture.supplyAsync(() -> payTest.myBulkhead(id)+"\t"+"Bulkhead.Type.THREADPOOL");
    }
    public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t)
    {
        return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~");
    }
}

4.3.2 服务提供端

4.3.3  测试验证

4.3.3.1 基础服务启动

 1.确保注册中心consul服务启动起来;

2.确保zipkin服务启动起来:

D:\>java -jar zipkin-server-2.23.4-exec.jar

4.3.3.2 应用服务启动

访问地址:

http://localhost:8081/feign/pay/bulkhead/1

 http://localhost:8081/feign/pay/bulkhead/2

 http://localhost:8081/feign/pay/bulkhead/3

 http://localhost:8081/feign/pay/bulkhead/4

依次进行访问:

1.

2.

3.

4.第4个开始出现,服务降级

五  限流

5.1 限流常用策略

客户端常用的限流: 漏斗,漏桶,计数器,滑动窗口

服务端限流:主动丢弃部分不重要内容或者延迟处理的方式。

1.漏桶算法

5.2 实操案例

5.2.1 工程结构

5.2.2 消费端的配置

1.pom文件

        <!--resilience4j-ratelimiter-->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-ratelimiter</artifactId>
        </dependency>

2.配置文件

####resilience4j ratelimiter 限流的例子
resilience4j:
  ratelimiter:
    configs:
      default:
        limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数
        limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod
        timeout-duration: 1 # 线程等待权限的默认等待时间
    instances:
      ms-provider:
        baseConfig: default

3.业务类

    @GetMapping(value = "/feign/pay/ratelimit/{id}")
    @RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback")
    public String myBulkheadCircuitFegin(@PathVariable("id") Integer id)
    {
        return payCircuitFegin.myRatelimit(id);
    }
    public String myRatelimitFallback(Integer id,Throwable t)
    {
        return "你被限流了,禁止访问/(ㄒoㄒ)/~~";
    }

5.2.3 openfegin的API配置

5.2.4 服务提供端的配置

1.业务类配置

5.2.5 测试验证

5.2.5.1 基础服务启动

 1.确保注册中心consul服务启动起来;

2.确保zipkin服务启动起来:

D:\>java -jar zipkin-server-2.23.4-exec.jar

5.2.5.2 应用服务启动

1.启动服务

2.访问页面

多刷新几次:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值