news 2026/5/30 22:55:20

SpringCloud-04-Circuit Breaker断路器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringCloud-04-Circuit Breaker断路器

一、概述

1、分布式系统面临的问题??

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务雪崩:

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

2、问题解决

有问题的节点,快速熔断(快速返回失败处理或者返回默认兜底数据【服务降级】)。

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

二、什么是Circuit Breaker

Circuit Breaker的目的是保护分布式系统免受故障和异常,提高系统的可用性和健壮性。

当一个组件或服务出现故障时,Circuit Breaker会迅速切换到开放OPEN状态(保险丝跳闸断电),阻止请求发送到该组件或服务从而避免更多的请求发送到该组件或服务。这可以减少对该组件或服务的负载,防止该组件或服务进一步崩溃,并使整个系统能够继续正常运行。

同时,CircuitBreaker还可以提高系统的可用性和健壮性,因为它可以在分布式系统的各个组件之间自动切换,从而避免单点故障的问题。

Circuit Breaker只是一套规范和接口,落地实现者是Resilience4J。

健壮

三、Resilience4J

‌Resilience4j 是一个轻量级容错库‌,专为 Java 应用程序设计,用于实现熔断、限流、重试和隔离等机制,以提升分布式系统的稳定性和可用性。‌‌

Resilience4j 通过模块化设计提供多种容错能力,核心组件包括: ‌

  • 熔断器(Circuit Breaker)‌:当服务失败率超过阈值(如 50%)时自动熔断,防止级联故障,支持关闭、开启、半开三种状态转换。‌‌
  • 限流器(Rate Limiter)‌:基于令牌桶算法控制请求速率,例如每秒最多 5 次请求,避免服务过载。‌‌
  • 重试机制(Retry)‌:对失败操作自动重试,适用于网络抖动等临时性故障。‌‌
  • 隔离器(Bulkhead)‌:通过信号量限制并发请求数,防止单个服务故障拖垮整个系统。‌‌

1、Resilience4J服务熔断和服务降级

断路器(Circuit Breaker):

  • 断路器有三个普通状态:关闭(CLOSED)、开启(OPEN)、半开(HALF_OPEN)。
  • 还有两个特殊状态:禁用(DISABLED)、强制开启(FORCED_OPEN)。

熔断器三大状态的改变(原理):

  • 当熔断器关闭时,所有的请求都会通过熔断器。
  • 当失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。 当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率。
  • 如果失败率超过阈值,熔断器则变为打开状态,如果失败率低于阈值,则变为关闭状态。

断路器使用滑动窗口来存储和统计调用的结果。可以选择基于调动数量的滑动窗口或者基于时间的滑动窗口。(即作为断路器开启/关闭的依据)。

  • 基于访问数量的滑动窗口:统计最近N次调用的返回结果:按照访问时的成功率进行判断。eg:访问6次3次失败,就判断被调用服务已经宕机,就不再调用它,立即开启服务熔断 断路器开启状态,进入服务降级(fallback)。
  • 基于时间的滑动窗口:统计最近N秒的调用返回结果:基于时间进行判断。eg:在一定的时间范围内,每次调用时间都超出设定时间,也会立即开启服务熔断状态。

除此之外,熔断器还会有两种特殊状态:disabled(始终允许访问)和forced_open(始终拒绝访问)。

  • 这两个状态不会生成熔断器事件(除状态转换外),并且不会记录事件的成功与失败。
  • 退出这两个状态的唯一方法是触发状态转换或者重置熔断器。

断路器装在消费者、即服务调用者处。

断路器常用配置:

<!--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>

1、案例1

按照COUNT_BASED(计数的滑动窗口)按照错误次数达到多少次之后开启断路

断路器装在消费者、即微服务调用者处。

application.yml配置

# 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: cloud-payment-service: ##代表将上面断路器的配置到消费者 baseConfig: default #默认配置

生产者微服务接口:

@RestController public class PayCircuitController { //=========Resilience4j CircuitBreaker 的例子 @GetMapping(value = "/pay/circuit/{id}") public String myCircuit(@PathVariable("id") Integer 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();//流水号 } }

Feign接口:

@GetMapping(value = "/pay/circuit/{id}") public String myCircuit(@PathVariable("id") Integer id);

消费者微服务接口:

需要引入@CircuitBreaker注解以及服务降级方法

@GetMapping(value = "/feign/pay/circuit/{id}") @CircuitBreaker(name = "cloud-payment-service", fallbackMethod = "myCircuitFallback") // 客户端要调用微服务的名称 服务降级,调用下面的myCircuitFallback() public String myCircuitBreaker(@PathVariable("id") Integer id){ return payFeignApi.myCircuit(id);//调用Feign } //myCircuitFallback()就是服务降级后的兜底处理方法:如果熔断之后/或者报错就会调用这个方法来返回结果!!! public String myCircuitFallback(Integer id,Throwable t) { // 这里是容错处理逻辑,返回备用结果 return "myCircuitFallback,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~";//不要让服务调用者等待并立即返回1个友好提示, fallback }

2、案例2

按照TIME_BASED(时间的滑动窗口)

断路器装在消费者、即微服务调用者处。

建议:不要混合使用,建议使用COUNT_BASED的方式

# Resilience4j CircuitBreaker 按照时间:TIME_BASED 的例子 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: cloud-payment-service: baseConfig: default

2、Resilience4J舱壁隔离BulkHead

舱壁隔离:依赖隔离&负载保护:用来限制下游服务的最大并发数量的限制。

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

  • SemaphoreBulkhead(信号量舱壁):使用了信号量。
  • FixedThreadPoolBulkhead(固定线程池舱壁):使用了有界队列和固定大小线程池。

信号量舱壁(SemaphoreBulkhead) 舱壁装在消费者、调用者处

原理:

  • 当信号量有空闲时,进入系统的请求会直接获取信号量并开始业务处理。
  • 当信号量全被占用时,接下来的请求将会进入阻塞状态,SemaphoreBulkhead提供了一个阻塞计时器。
  • 如果阻塞状态的请求在阻塞计时内无法获取到信号量则系统会拒绝这些请求。
  • 若请求在阻塞计时内获取到了信号量,那将直接获取信号量并执行相应的业务处理。
<!--resilience4j-bulkhead--> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-bulkhead</artifactId> </dependency>

application.yml

  • 属性配置: maxConcurrentCalls:舱壁允许的最大并行执行量,默认值是25
  • maxWaitDuration:尝试进入饱和舱壁时,应阻塞线程的最长时间,默认值是0
###resilience4j bulkhead 的例子 resilience4j: bulkhead: configs: default: maxConcurrentCalls: 2 # 隔离允许并发线程执行的最大数量 maxWaitDuration: 1s # 当达到并发调用数量时,新的线程的阻塞时间,我只愿意等待1秒,过时不候进舱壁兜底fallback instances: cloud-payment-service: baseConfig: default timelimiter: configs: default: timeout-duration: 20s

生产者微服务接口:

//=========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){ try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } return "Hello, bulkhead! inputId: "+id+" \t " + IdUtil.simpleUUID(); }

OpenFeign接口:

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

消费者微服务接口:

消费者模块Controller 需要加入舱壁隔离注解@Bulkhead+注意:type = Bulkhead.Type.SEMAPHORE 以及服务降级方法fallbackMethod

// 案例:演示消费者/客户端80通过Feign接口调用8001舱壁服务 //(船的)舱壁,隔离 @GetMapping(value = "/feign/pay/bulkhead/{id}") //舱壁隔离注解 @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadFallback",type = Bulkhead.Type.SEMAPHORE) // 服务提供者模块 服务降级:如果失败,就执行下面的myBulkheadFallback()方法来返回给消费者/客户端 指定隔离的实现方式 public String myBulkhead(@PathVariable("id") Integer id){ return payFeignApi.myBulkhead(id); } public String myBulkheadFallback(Throwable t){ return "myBulkheadFallback,隔板超出最大数量限制,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~"; }

3、固定线程池舱壁(FixedThreadPoolBulkhead)

FixedThreadPoolBulkhead的功能与SemaphoreBulkhead一样也是用于限制并发执行的次数的,但是二者的实现原理存在差别而且表现效果也存在细微的差别。FixedThreadPoolBulkhead使用一个固定线程池和一个等待队列来实现舱壁。 当线程池中存在空闲时,则此时进入系统的请求将直接进入线程池开启新线程或使用空闲线程来处理请求。 当线程池中无空闲时时,接下来的请求将进入等待队列, 若等待队列仍然无剩余空间时接下来的请求将直接被拒绝, 在队列中的请求等待线程池出现空闲时,将进入线程池进行业务处理。

另外:ThreadPoolBulkhead只对CompletableFuture方法有效,所以我们必创建返回CompletableFuture类型的方法 FixedThreadPoolbulkhead的配置项:

  • maxThreadPoolSize:配置最大线程池的大小,默认值是Runtime.getRuntime().availableProcessors
  • coreThreadPoolSize:配置核心线程池的大小,默认值是Runtime.getRuntime().availableProcessors - 1
  • queueCapacity:配置队列的容量,默认值是100。
  • keepAliveDuration:当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间,默认值是20ms。

使用步骤: 在服务消费端pom文件加入相关依赖(同上方的信号舱舱壁隔离的一样的)

###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: cloud-payment-service: baseConfig: default # spring.cloud.openfeign.circuitbreaker.group.enabled 请设置为false 新启线程和原来主线程脱离

消费端Controller 需要加入舱壁隔离注解@Bulkhead,注意:type = Bulkhead.Type.THREADPOOL, 且服务降级方法返回值类型必须为CompletableFuture

//案例:演示消费者/客户端通过Feign接口调用舱壁服务(2)treadPool //(船的)舱壁,隔离 @GetMapping(value = "/feign/pay/bulkhead/{id}") //舱壁隔离注解 @Bulkhead(name = "cloud-payment-service",fallbackMethod = "myBulkheadPoolFallback",type = Bulkhead.Type.THREADPOOL) // 服务提供者模块 服务降级:如果失败,就执行下面的myBulkheadFallback()方法来返回给消费者/客户端 指定隔离的实现方式:THREADPOOL public CompletableFuture<String> myBulkheadTHREADPOOL(@PathVariable("id") Integer id){ System.out.println(Thread.currentThread().getName()+"\t"+"enter the method!!!"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t"+"exist the method!!!"); return CompletableFuture.supplyAsync(() -> payFeignApi.myBulkhead(id) + "\t" + " Bulkhead.Type.THREADPOOL"); } public CompletableFuture<String> myBulkheadPoolFallback(Integer id,Throwable t)//在fallback中多加个Throwable t即可{ return CompletableFuture.supplyAsync(() -> "Bulkhead.Type.THREADPOOL,系统繁忙,请稍后再试-----/(ㄒoㄒ)/~~"); }

4、Resilience4J限流

限流是频率的控制,限制最大访问流量。系统能提供的最大并发是有限的,同时来的请求又太多,就需要限流。 就是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速,以保护应用系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理。

比如商城秒杀业务,瞬时大量请求涌入,服务器忙不过就只好排队限流了,和去景点排队买票和去医院办理业务排队等号道理相同。

感觉限流类似于舱壁:舱壁是没有可用位置/线程就返回兜底。限流是没有位置就等待。

常见限流算法:

  • 漏斗算法:让请求像水流过漏斗一样,请求的处理效率是固定的,来不及处理的请求在“漏斗”中等待。漏斗算法对于存在突发特性的流量来说缺乏效率。
  • 令牌桶算法:准备一定的令牌数,请求通过时,如果令牌有剩余,该请求就领取一个令牌,并进入处理,处理完后释放令牌。没有令牌剩余的时候就是到等待队列中等待令牌。这种方法是SpringCloud默认使用的算法。
  • 滚动时间窗:取一段时间内,允许固定数量的请求进入,如果这段时间内超过该固定数量,就拒绝或者排队,等下一个时间段进入。但是间隔临界的一段时间内的请求超过系统限制,可能导致系统被压垮。
  • 滑动时间窗:滑动事件窗口是把固定时间片段进行划分,并且随着时间移动,移动方式为开始时间点变为时间列表中的第二个时间点,结束时间点增加一个时间点,不断重复。达到一种滑片滑动的效果。
<!--resilience4j-ratelimiter--> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-ratelimiter</artifactId> </dependency>
####resilience4j ratelimiter 限流的例子 resilience4j: ratelimiter: configs: default: limitForPeriod: 2 #在一次刷新周期内,允许执行的最大请求数 limitRefreshPeriod: 1s # 限流器每隔limitRefreshPeriod刷新一次,将允许处理的最大请求数量重置为limitForPeriod timeout-duration: 1 # 线程等待权限的默认等待时间 instances: cloud-payment-service: baseConfig: default

消费端微服务接口,加入@RateLimiter注解,写好服务提供者和兜底方法

//案例:演示消费端通过Feign接口调用服务提供者 @GetMapping(value = "/feign/pay/ratelimit/{id}") @RateLimiter(name = "cloud-payment-service",fallbackMethod = "myRatelimitFallback") // 服务提供者 兜底方法 public String myBulkhead(@PathVariable("id") Integer id){ return payFeignApi.myRatelimit(id); } public String myRatelimitFallback(Integer id,Throwable t){ return "你被限流了,禁止访问/(ㄒoㄒ)/~~"; } //之后,在客户端访问这个地址,如果1s内访问次数大于2次就会被限流,返回兜底方法
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/29 22:08:16

PyTorch-CUDA-v2.8镜像支持WSL2子系统运行

PyTorch-CUDA-v2.8镜像支持WSL2子系统运行 在如今深度学习项目日益复杂的背景下&#xff0c;一个稳定、高效且开箱即用的开发环境&#xff0c;往往决定了从想法到落地的速度。对于许多在 Windows 平台上工作的开发者而言&#xff0c;长期以来面临的一大挑战是&#xff1a;如何在…

作者头像 李华
网站建设 2026/5/30 14:01:22

道法自然:终极的生态智慧与生活美学

《三脉合一:儒释道与中国人的精神密码》 第二部分 :道家 自由的艺术 第七篇(收官) 导语: 如果你感觉人生像是一个不断报错、发热过高的系统,也许不是你的CPU(能力)不够强,而是你的操作系统(心法)一直在对抗底层的物理规律。 一、 那个让你“越高效越崩溃”的早晨 …

作者头像 李华
网站建设 2026/5/28 22:17:44

清华镜像站加速pip install pytorch:真实可用源地址分享

清华镜像站加速 pip install PyTorch&#xff1a;真实可用源地址分享 在深度学习项目启动的前几个小时&#xff0c;你是否曾盯着终端里缓慢爬行的 pip install torch 进度条无奈等待&#xff1f;明明显卡是 RTX 4090&#xff0c;下载速度却只有几十 KB/s——问题不出在硬件&am…

作者头像 李华
网站建设 2026/5/29 22:21:18

近视防控不用硬扛!适合学生的实用指南,家长收藏好

当下学生群体的近视问题愈发普遍&#xff0c;学业压力带来的持续用眼需求&#xff0c;让很多孩子的视力健康频频亮起红灯&#xff0c;也让家长们陷入焦虑&#xff0c;总觉得近视防控就是一场需要咬牙硬扛的持久战&#xff0c;一边担心孩子视力下滑&#xff0c;一边又无力改变日…

作者头像 李华
网站建设 2026/5/29 21:31:00

PyTorch autograd机制剖析:理解反向传播GPU加速原理

PyTorch autograd机制剖析&#xff1a;理解反向传播GPU加速原理 在深度学习模型的训练过程中&#xff0c;梯度计算和参数更新的速度直接决定了研发效率。随着Transformer、扩散模型等大规模架构成为常态&#xff0c;单靠CPU已难以支撑合理的迭代周期。PyTorch 凭借其灵活的 aut…

作者头像 李华
网站建设 2026/5/28 16:13:30

JiyuTrainer支持WandB日志同步:增强实验可视化能力

JiyuTrainer 集成 WandB&#xff1a;让模型训练“看得见、管得住、传得开” 在现代 AI 开发中&#xff0c;我们早已过了“跑通一个 loss 就欢呼”的时代。如今的团队面对的是上百次实验并行、数十个 GPU 节点调度、跨时区协作评审——如果还靠本地日志文件和口头汇报来推进项目…

作者头像 李华