06-CircuitBreaker断路器

1、介绍

Spring Cloud Circuit breaker provides an abstraction across different circuit breaker implementations. It provides a consistent API to use in your applications allowing you the developer to choose the circuit breaker implementation that best fits your needs for your app.

Spring Cloud断路器提供了一个跨不同断路器的抽象实现。它提供了在您的应用程序中使用的一致的API,允许您的开发人员选择最适合您的应用程序需要的断路器实现。

CircuitBreaker通过具有三种正常状态的有限状态机实现:CLOSED,OPEN和HALF_OPEN以及两个特殊状态DISABLED和FORCED_OPEN。当熔断器关闭时,所有的请求都会通过熔断器。如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率,如果失败率超过阈值,则变为打开状态,如果失败率低于阈值,则变为关闭状态。

06-CircuitBreaker断路器

支持断路器实现有以下几种

  • Netfix Hystrix (Spring Cloud官方已经不予支持)
  • Resilience4J (支持)
  • Sentinel
  • Spring Retry

2、快速开始

pom.xml
maven依赖配置文件,如下:


    4.0.0

        org.springframework.boot
        spring-boot-starter-parent
        2.6.4

    com.mindasoft
    spring-cloud-circuitbreaker-consumer
    0.0.1-SNAPSHOT
    spring-cloud-circuitbreaker-consumer
    Demo project for Spring Boot

        UTF-8
        UTF-8
        1.8
        2021.0.1

            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client

            org.springframework.cloud
            spring-cloud-starter-circuitbreaker-resilience4j

            org.springframework.boot
            spring-boot-starter-web

            org.springframework.boot
            spring-boot-starter-test
            test

                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud.version}
                pom
                import

                org.springframework.boot
                spring-boot-maven-plugin

            nexus-aliyun
            Nexus aliyun
            https://maven.aliyun.com/repository/public
            default

                false

                true

            nexus-aliyun
            Nexus aliyun
            https://maven.aliyun.com/repository/public

                false

                true

比上个项目多了如下配置:


            org.springframework.cloud
            spring-cloud-starter-circuitbreaker-resilience4j

使用resilience4j 实现断路器。

application.xml

server.port=9004

服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

服务名称
spring.application.name=circuitbreaker-customer

SpringCloudCircuitBreakerConsumerApplication

package com.mindasoft;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

import java.time.Duration;

@EnableDiscoveryClient // Eureka Discovery Client 标识
@SpringBootApplication
public class SpringCloudCircuitBreakerConsumerApplication {

    // 开启负载均衡的功能
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public Customizer defaultCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build())
                .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
                .build());
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudCircuitBreakerConsumerApplication.class, args);
    }

}

ConsumerController

package com.mindasoft.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * Created by huangmin on 2017/11/10 14:50.

 */
@RestController
public class ConsumerController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerController.class);

    @Autowired
    private RestTemplate restTemplate; // HTTP 访问操作类
    @Autowired
    private CircuitBreakerFactory circuitBreakerFactory;

    @RequestMapping("/hello")
    public String hello() {
        String providerMsg = circuitBreakerFactory.create("hello")
                .run(() -> restTemplate.getForObject("http://PROVIDER-SERVICE/hello", String.class),
                        throwable -> "CircuitBreaker fallback msg");

        return "Hello,I'm Customer! msg from provider :  " + providerMsg;
    }

}

启动eureka server和本项目,访问http://127.0.0.1:9004/hello
页面打印如下:

Hello,I'm Customer! msg from provider :

CircuitBreaker fallback msg

说明进入到了断路throwable 处理逻辑。

3、Resilience4J介绍

Resilience4j是一款轻量级,易于使用的容错库,其灵感来自于Netflix Hystrix,但是专为Java 8和函数式编程而设计。轻量级,因为库只使用了Vavr,它没有任何其他外部依赖下。相比之下,Netflix Hystrix对Archaius具有编译依赖性,Archaius具有更多的外部库依赖性,例如Guava和Apache Commons Configuration。

Resilience4j提供了以下的核心模块和拓展模块:

核心模块:

  • resilience4j-circuitbreaker: Circuit breaking
  • resilience4j-ratelimiter: Rate limiting
  • resilience4j-bulkhead: Bulkheading
  • resilience4j-retry: Automatic retrying (sync and async)
  • resilience4j-cache: Result caching
  • resilience4j-timelimiter: Timeout handling

Resilience4j记录请求状态的数据结构和Hystrix不同,Hystrix是使用滑动窗口来进行存储的,而Resilience4j采用的是Ring Bit Buffer(环形缓冲区)。Ring Bit Buffer在内部使用BitSet这样的数据结构来进行存储,BitSet的结构如下图所示:

06-CircuitBreaker断路器

每一次请求的成功或失败状态只占用一个bit位,与boolean数组相比更节省内存。BitSet使用long[]数组来存储这些数据,意味着16个值(64bit)的数组可以存储1024个调用状态。

计算失败率需要填满环形缓冲区。例如,如果环形缓冲区的大小为10,则必须至少请求满10次,才会进行故障率的计算,如果仅仅请求了9次,即使9个请求都失败,熔断器也不会打开。但是CLOSE状态下的缓冲区大小设置为10并不意味着只会进入10个 请求,在熔断器打开之前的所有请求都会被放入。

当故障率高于设定的阈值时,熔断器状态会从由CLOSE变为OPEN。这时所有的请求都会抛出CallNotPermittedException异常。当经过一段时间后,熔断器的状态会从OPEN变为HALF_OPEN,HALF_OPEN状态下同样会有一个Ring Bit Buffer,用来计算HALF_OPEN状态下的故障率,如果高于配置的阈值,会转换为OPEN,低于阈值则装换为CLOSE。与CLOSE状态下的缓冲区不同的地方在于,HALF_OPEN状态下的缓冲区大小会限制请求数,只有缓冲区大小的请求数会被放入。

除此以外,熔断器还会有两种特殊状态:DISABLED(始终允许访问)和FORCED_OPEN(始终拒绝访问)。这两个状态不会生成熔断器事件(除状态装换外),并且不会记录事件的成功或者失败。退出这两个状态的唯一方法是触发状态转换或者重置熔断器。

熔断器关于线程安全的保证措施有以下几个部分:

  • 熔断器的状态使用AtomicReference保存的
  • 更新熔断器状态是通过无状态的函数或者原子操作进行的
  • 更新事件的状态用synchronized关键字保护
  • 意味着同一时间只有一个线程能够修改熔断器状态或者记录事件的状态。

使用注解配置@CircuitBreaking

首先在POM文件中添加:


            org.springframework.boot
            spring-boot-starter-aop

spring-boot-starter-aop必须添加,否则annotation不会生效。

application.xml

server.port=9004

服务注册中心地址
eureka.client.service-url.defaultZone=http://localhost:9000/eureka/

服务名称
spring.application.name=circuitbreaker-customer

断路器配置
resilience4j.circuitbreaker:
  configs:
    default:
      ringBufferSizeInClosedState: 1 # 熔断器关闭时的缓冲区大小
      ringBufferSizeInHalfOpenState: 1 # 熔断器半开时的缓冲区大小
      waitDurationInOpenState: 60000 # 熔断器从打开到半开需要的时间
      failureRateThreshold: 100 # 熔断器打开的失败阈值
      eventConsumerBufferSize: 5 # 事件缓冲区大小
      registerHealthIndicator: true # 健康监测
      automaticTransitionFromOpenToHalfOpenEnabled: true # 是否自动从打开到半开,不需要触发
  instances:
    providerService: # 断路器策略的命名
      baseConfig: default #继承基础配置

直接在配置文件中,添加断路器配置。JavaConfig模式的配置我们就去掉了。

SpringCloudCircuitBreakerConsumerApplication

package com.mindasoft;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient // Eureka Discovery Client 标识
@SpringBootApplication
public class SpringCloudCircuitBreakerConsumerApplication {

    // 开启负载均衡的功能
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudCircuitBreakerConsumerApplication.class, args);
    }

}

我们将远程调用服务,独立出来。
ProviderService

public interface ProviderService {

    String hello();
}

package com.mindasoft.serivce;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * 功能描述:
 *
 * @author huangmin
 * @date 2022/3/1 10:04 上午
 */
@Service
public class ProviderServiceImpl implements ProviderService {
    @Autowired
    private RestTemplate restTemplate; // HTTP 访问操作类

    @Override
    @CircuitBreaker(name = "providerService", fallbackMethod = "fallback")
    public String hello() {
        String providerMsg = restTemplate.getForObject("http://PROVIDER-SERVICE/hello", String.class);
        return providerMsg;
    }

    private String fallback(Exception ex) {
        return "Recovered: " + ex.toString();
    }

}

ConsumerController也需要修改一下:

@RestController
public class ConsumerController {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerController.class);

    @Autowired
    private ProviderService providerService;

    @RequestMapping("/hello")
    public String hello() {

        String providerMsg = providerService.hello();
        return "Hello,I'm Customer! msg from provider :  " + providerMsg;
    }

}

改造完成,然后启动服务,访问hello时,得到以下结果:

第一次访问:
Hello,I'm Customer! msg from provider :

Recovered: java.lang.IllegalStateException: No instances available for PROVIDER-SERVICE

#第二次访问:
Hello,I'm Customer! msg from provider :

Recovered: io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'providerService' is OPEN and does not permit further calls

其他功能

resilience4j还有其他功能,如断路器一样使用注解@Retry、@Bulkhead、@RateLimiter、@Timelimiter,简单的配置如下。

resilience4j.retry:
    configs:
        default:
            maxAttempts: 3
            waitDuration: 100
    instances:
        providerService:
            baseConfig: default

resilience4j.bulkhead:
    configs:
        default:
            maxConcurrentCalls: 100
    instances:
        providerService:
            maxWaitDuration: 10ms
            maxConcurrentCalls: 20

resilience4j.thread-pool-bulkhead:
    configs:
        default:
            maxThreadPoolSize: 4
            coreThreadPoolSize: 2
            queueCapacity: 2
    instances:
        providerService:
            baseConfig: default
        providerServiceB:
            maxThreadPoolSize: 1
            coreThreadPoolSize: 1
            queueCapacity: 1

resilience4j.ratelimiter:
    configs:
        default:
            registerHealthIndicator: false
            limitForPeriod: 10
            limitRefreshPeriod: 1s
            timeoutDuration: 0
            eventConsumerBufferSize: 100
    instances:
        providerService:
            baseConfig: default
        providerServiceB:
            limitForPeriod: 6
            limitRefreshPeriod: 500ms
            timeoutDuration: 3s

resilience4j.timelimiter:
    configs:
        default:
            cancelRunningFuture: false
            timeoutDuration: 2s
    instances:
        providerService:
            baseConfig: default

Original: https://www.cnblogs.com/mpiter/p/15978947.html
Author: 技术那点事儿
Title: 06-CircuitBreaker断路器

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581106/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • JVM学习笔记之垃圾收集算法【四】

    一、什么是垃圾回收? 垃圾回收(英语:Garbage Collection,缩写为 GC),在计算机科学中是一种自动的存储器管理机制。当一个电脑上的动态存储器不再需要时,就应该予以…

    Java 2023年6月5日
    0132
  • 关于C语言的学习 01

    include void main() //定义主函数 { //main 函数体开始 函数声明部分 C语言的各种语句 } //main函数结束 代码输出HelloWorld! 例子…

    Java 2023年6月5日
    099
  • 【WPF】WPF的二维路径标记语法绘图PathGeometry

    我们用Path来实现一条直线: <Path Stroke="Black" StrokeThickness="1" > <P…

    Java 2023年5月29日
    095
  • SAP S4HANA 2021 Fully-Activated Appliance 虚拟机版分享

    该版本内置了四个Client: 1、000:SAP初始Client,原则上不能动; 2、100:只激活了US/DE两套BP,带大量Demo数据,可做数据参考; 3、200:复制Cl…

    Java 2023年5月30日
    089
  • java中nio知识梳理及实例

    最近,我学习了NITY的相关知识,涉及到NiO的相关知识,所以我梳理了NiO的基础。 1. 核心组件 读写是JavaNIO中最基本的操作。您可以创建缓冲区,从通道读取数据,同时将数…

    Java 2023年6月7日
    069
  • spring boot配置

    spring.application.name=XXXXXserver.port=9515 health显示down的问题endpoints.health.sensitive= f…

    Java 2023年5月30日
    077
  • Skywalking-01:Skywalking介绍

    Skywalking介绍 Application performance monitor tool for distributed systems, especially desi…

    Java 2023年6月5日
    073
  • 无重复字符的最长子串

    https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/ 上链接为Leeco…

    Java 2023年6月5日
    070
  • 限流常见方案

    限流常见方案 我歌月徘徊,我舞影零乱。醒时相交欢,醉后各分散。 一、限流思路 常见的系统服务限流模式有:熔断、服务降级、延迟处理和特殊处理四种。 1、熔断 将熔断措施嵌入到系统设计…

    Java 2023年6月5日
    073
  • [学习笔记] Java读取用户输入

    在程序的实际运行过程中,我们很可能会要求用户输入数据以继续运行程序; java.util包提供的Scanner类就可用于读取用户输入; 创建Scanner对象 使用next()方法…

    Java 2023年6月5日
    050
  • jvm造轮子

    博客内容来源于 刘欣老师的课程,刘欣老师的公众号 码农翻身 博客内容来源于 Java虚拟机规范(JavaSE7) 博客内容的源码 https://gitee.com/zumengj…

    Java 2023年6月8日
    0115
  • Array and Linkedlist区别与操作的时间复杂度(转载)

    转载自:http://blog.csdn.net/tm_wb/article/details/6319146 数组链表堆栈和队列 数组链表堆栈和队列是最基本的数据结构,任何程序都会…

    Java 2023年5月29日
    094
  • LeetCode刷题

    1.53最大和的连续子数组 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 class…

    Java 2023年6月5日
    073
  • Java 及 Kotlin 中的可变参数 vararg

    本文地址 目录 目录 Java 及 Kotlin 中的可变参数 vararg Java 中可变参数的规则 可变参数的本质是数组 Java 中对应数组 T[] Kotlin 中可能对…

    Java 2023年5月29日
    096
  • 【译】使用Java Locale进行国际化

    在开发软件应用程序时,我们经常强调尊重用户语言和地理区域的重要性和价值。允许用户用他们自己的语言与软件交流可能会大大促进软件的销售。在 Java 语言环境中, Locale则用来阐…

    Java 2023年5月29日
    085
  • 自定义线程池的创建与@Async注解使用

    1.创建线程池配置 2.使用 @Async介绍 在Spring中,基于@Async标注的方法,称之为异步方法;这些方法将在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完…

    Java 2023年6月8日
    091
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球