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)

大家都在看

  • MySQL基础

    1、select语句及其执行顺序 select 要返回的列或者表达式 from 从中检索数据的表仅在从表选择数据时使用 where 行级过滤 group by 分组说明仅在按组计算…

    Java 2023年6月8日
    063
  • 服务器内存故障预测居然可以这样做!

    作者:vivo 互联网服务器团队- Hao Chan 随着互联网业务的快速发展,基础设施的可用性也越来越受到业界的关注。内存发生故障的故障率高、频次多、影响大,这些对于上层业务而言…

    Java 2023年6月15日
    097
  • Java-API+Kafka实现自定义分区

    1.pom.xml导入kafka依赖包; 2.kafka普通生产者实现方式; 3.kafka带回调函数的生产者; 4.生产者自定义分区; 4.1使用自定义分区 csharp;gut…

    Java 2023年6月9日
    066
  • 程序员的职业素养

    一、专业主义 专业主义不但象征着荣誉和骄傲,而且明确意味着责任和义务,其精髓在于将公司利益视同个人利益,即担当责任。如不能为了交付时效而忽略测试环节,要为不完美承担责任(失误率永远…

    Java 2023年6月9日
    068
  • 穿透Session 0 隔离

    服务(Service)对于大家来说一定不会陌生,它是Windows 操作系统重要的组成部分。我们可以把服务想像成一种特殊的应用程序,它随系统的”开启~关闭”…

    Java 2023年5月30日
    079
  • B站 Java 精选视频(转)

    作者:微信公众号”Java研究所” 大家好,今天帮大家整理了一下b站上java相关的精华视频,供大家使用, 文末有福利!!! 尚硅谷宋红康(强力推荐) 动力…

    Java 2023年5月29日
    059
  • MySQL 千万数据库深分页查询优化,拒绝线上故障!

    文章首发在公众号(龙台的技术笔记),之后同步到博客园和个人网站:xiaomage.info 优化项目代码过程中发现一个千万级数据深分页问题,缘由是这样的 库里有一张耗材 MCS_P…

    Java 2023年6月14日
    086
  • 归并排序

    归并排序 本文分为以下几个部分 问题引入 master 公式 归并排序 写在最后 问题引入 求一串非空数组中的最大值,使用O(n)的时间复杂度。 最直接想到的代码就是直接一次遍历 …

    Java 2023年6月5日
    059
  • 记录eclipse中文出现空格宽度不一致的bug

    起因 不久前更新了 eclipse(2019-03) 版本;突然发现出现了,使用注释使用中出现的空格的间隔大小不一致的问题,具体可以看下图: 遇到这种问题简直逼不能忍,在网上搜一下…

    Java 2023年6月10日
    0104
  • 聊聊消息中心的设计与实现逻辑

    厌烦被消息打扰,又怕突然间的安静; 一、业务背景 微服务的架构体系中,会存在很多基础服务,提供一些大部分服务都可能需要的能力,比如文件管理、MQ队列、缓存机制、消息中心等等,这些服…

    Java 2023年6月15日
    062
  • Spring Ioc源码分析系列–Bean实例化过程(二)

    这篇文章是给上篇填坑的,上篇分析到真正创建Bean的 createBean(beanName, mbd, args)就没有继续深入去分析了,绕得太深,说不清楚。那么这一篇,就续上这…

    Java 2023年6月8日
    061
  • 〖java实例〗利用线程检测变量

    public class Main {</p> <pre><code>static int i = 0; static int iAdd = i…

    Java 2023年6月7日
    062
  • MySQL八:读懂MVCC多版本并发控制

    转载~ mysql在并发的情况下,会引起脏读,幻读,不可重复读等一系列的问题,为解决这些问题,引入了mvcc的机制。本文就详细看看mvcc是怎么解决脏读,幻读等问题的。 1、 数据…

    Java 2023年6月8日
    076
  • Mybatisplus代码自动生成器

    根据数据表自动生成实体类、Mapper、Service、ServiceImpl、Controller 1、pom.xml 导入 MyBatis Plus Generator Vel…

    Java 2023年6月7日
    071
  • 删除链表的中间节点

    删除链表的中间节点 问题重述: 给你一个链表的头节点 head 。 删除 链表的 中间节点 ,并返回修改后的链表的头节点 head 。 长度为 n 链表的中间节点是从头数起第 &a…

    Java 2023年6月7日
    057
  • Spring 基于注解配置bean之简单入门

    Spring 注解配置bean 复习注解相关的知识 啥是注解? 直接是一种特殊的标识符。可在源码或运行阶段起作用。 注解类型 元注解 如 **@Target** 自定义注解 Spr…

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