依赖

本文源码分析pom依赖如下,
spring cloud 版本 2023.0.0

<dependencyManagement>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>2023.0.0</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
</dependencyManagement>

circuitbreaker 版本 3.1.0

  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
      <version>3.1.0</version>
  </dependency>
自动装配入口
org.springframework.cloud.openfeign.FeignAutoConfiguration
.CircuitBreakerPresentFeignTargeterConfiguration.circuitBreakerFeignTargeter

详细配置代码:

@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(CircuitBreaker.class)
	@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.enabled", havingValue = "true")
	protected static class CircuitBreakerPresentFeignTargeterConfiguration {

		@Bean
		@ConditionalOnMissingBean(CircuitBreakerFactory.class)
		public Targeter defaultFeignTargeter() {
			return new DefaultTargeter();
		}
		
// @A0
		@Bean
		@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
		@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled",
				havingValue = "false")
		public CircuitBreakerNameResolver circuitBreakerNameResolver() {
			return new DefaultCircuitBreakerNameResolver();
		}

		@Bean
		@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
		@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled",
				havingValue = "true", matchIfMissing = true)
		public CircuitBreakerNameResolver alphanumericCircuitBreakerNameResolver() {
			return new AlphanumericCircuitBreakerNameResolver();
		}

// @A1
		@SuppressWarnings("rawtypes")
		@Bean
		@ConditionalOnMissingBean
		@ConditionalOnBean(CircuitBreakerFactory.class)
		public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,
				@Value("${spring.cloud.openfeign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,	CircuitBreakerNameResolver circuitBreakerNameResolver) {
			return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,
					circuitBreakerNameResolver);
		}

		static class DefaultCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
			@Override
			public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
				return Feign.configKey(target.type(), method);
			}
		}

		static class AlphanumericCircuitBreakerNameResolver extends DefaultCircuitBreakerNameResolver {
			@Override
			public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
				return super.resolveCircuitBreakerName(feignClientName, target, method).replaceAll("[^a-zA-Z0-9]", "");
			}
		}
	}

下面介绍一下这些核心类

Targeter类

circuitBreakerFeignTargeter 方法会返回一个 @A1:Targeter 对象 ,依赖的参数是CircuitBreakerFactory circuitBreakerFactory,这个CircuitBreakerFactory 参数是在 org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JAutoConfiguration中定义的,配置如下:

	@Bean
	@ConditionalOnMissingBean(CircuitBreakerFactory.class)
	public Resilience4JCircuitBreakerFactory resilience4jCircuitBreakerFactory(
			CircuitBreakerRegistry circuitBreakerRegistry, TimeLimiterRegistry timeLimiterRegistry,
			@Autowired(required = false) Resilience4jBulkheadProvider bulkheadProvider,
			Resilience4JConfigurationProperties resilience4JConfigurationProperties) {
		Resilience4JCircuitBreakerFactory factory = new Resilience4JCircuitBreakerFactory(circuitBreakerRegistry,
				timeLimiterRegistry, bulkheadProvider, resilience4JConfigurationProperties);
		customizers.forEach(customizer -> customizer.customize(factory));
		return factory;
	}

上文提到的 @A1:Targeter 对象 是一个FeignCircuitBreakerTargeter实例,这个类主要作用是在org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject起作用,创建OpenFeign代理对象,最终执行的代码是:feign.ReflectiveFeign#newInstance(feign.Target, C)

    public <T> T newInstance(Target<T> target, C requestContext) {
        ReflectiveFeign.TargetSpecificationVerifier.verify(target);
        Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = this.targetToHandlersByName.apply(target, requestContext);
// @B
        InvocationHandler handler = this.factory.create(target, methodToHandler);
        T proxy = (T)Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);

        for(InvocationHandlerFactory.MethodHandler methodHandler : methodToHandler.values()) {
            if (methodHandler instanceof DefaultMethodHandler) {
                ((DefaultMethodHandler)methodHandler).bindTo(proxy);
            }
        }
        return proxy;
    }

@B 位置的InvocationHandler handler 真实类型是FeignCircuitBreakerInvocationHandler
从这个方法,就能看出openfeign的乾坤了;说实话,这细节太多 记也记不住;

CircuitBreakerNameResolver 类

自动装配CircuitBreakerPresentFeignTargeterConfiguration定义了一个CircuitBreakerNameResolver类 // @A0 ,这个类的核心作用是定义哪些类、方法能共用一个断路器;

框架默认注入的是DefaultCircuitBreakerNameResolver,在没有开启circuitBreakerGroupEnabled的情况下,这个类会把一个方法定义使用一个断路器;

如果开启circuitBreakerGroupEnabled,在代码执行的时候会首先找方法的配置,方法没有配置则找feignClientName对应的配置;

如果我们想让同一个@FeignClient 标记的类使用同一个CircuitBreaker,就可以向容器注入一个自定义CircuitBreakerNameResolver并实现方法即可;

@Configuration
public class Resilience4JFeignConfig {

    /**
     * 框架默认把方法作为断路器名称
     * 这里自定义把 FeignClient 作为断路器名称
     */
    @Bean
    public CircuitBreakerNameResolver feignClientGroupResolver() {
        //
        return (feignClientName, target, method) -> feignClientName;
    }
}

使用CircuitBreakerNameResolver解析断路器名字的代码在:
org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandler#invoke
有兴趣可以看看

在这里插入图片描述

断路器调用入口

feign调用CircuitBreaker栈:

调用栈截图:1、代理类被调用; 2、java.lang.reflect.InvocationHandler子类FeignCircuitBreakerInvocationHandler执行
在这里插入图片描述

FeignCircuitBreakerInvocationHandler后续调用栈:

org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandler.invoke  → org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory.create
↓
org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreaker.run
↓
org.springframework.cloud.circuitbreaker.resilience4j.Resilience4jBulkheadProvider.run

如果要细细研究,就只能打断点跟踪了;

resilience4j核心模块
核心模块 核心作用 一句话场景
Circuit Breaker (熔断器) 当服务故障率或慢请求率超过阈值时,自动“熔断”请求,防止故障像雪球一样越滚越大 下游数据库连接超时率达50%,直接熔断快速失败,防止应用线程池被耗尽。
Rate Limiter (限流器) 基于令牌桶算法,限制单位时间内的请求数量,保护服务不被突发流量冲垮 短信验证码接口每秒只允许处理10个请求,超出部分直接拒绝。
Retry (重试) 对因网络抖动等瞬时故障失败的操作,按照指数退避等策略自动重试 调用第三方API偶发超时,自动重试2次,大概率能成功。
Bulkhead (隔离/舱壁) 限制对某个服务的并发请求数,资源隔离。就算一个服务慢了,也不会耗尽整个应用的线程池 文件导出功能耗时较长,限制其最多10个线程并发,不影响其他接口
Time Limiter (超时控制) 为操作设置最大执行时间,超时则直接中断并抛出异常,避免线程无限阻塞 调用外部支付接口,超过3秒未响应就超时放弃,释放资源。
Fallback (降级) 当熔断、限流、超时等触发时,执行备用逻辑(如返回缓存数据或默认值) 推荐服务挂了,返回一个默认的热门商品列表给用户。

接上面 Resilience4JCircuitBreakerFactory.create 方法,这个方法会根据当前配置,创建一个Resilience4JCircuitBreaker对象;从这里看出来每次请求都会创建一个Resilience4JCircuitBreaker对象,感觉流程上可以优化;

我们看看 org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreaker 的run方法

	public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
		final Map<String, String> tags = Map.of(CIRCUIT_BREAKER_GROUP_TAG, this.groupName);
		Optional<TimeLimiter> timeLimiter = loadTimeLimiter();
		io.github.resilience4j.circuitbreaker.CircuitBreaker defaultCircuitBreaker = registry.circuitBreaker(this.id,
				this.circuitBreakerConfig, tags);
		circuitBreakerCustomizer.ifPresent(customizer -> customizer.customize(defaultCircuitBreaker));
		if (bulkheadProvider != null) {
       
// @C
			if (executorService != null) { 
				Supplier<Future<T>> futureSupplier = () -> executorService.submit(toRun::get);
				/* conditionally wrap in time-limiter */
				Callable<T> timeLimitedCall = timeLimiter
// @C1
						.map(tl -> TimeLimiter.decorateFutureSupplier(tl, futureSupplier))
						.orElse(() -> futureSupplier.get().get());
// @C2
				Callable<T> bulkheadCall = bulkheadProvider.decorateCallable(this.groupName, tags, timeLimitedCall);
// @C3
				Callable<T> circuitBreakerCall = io.github.resilience4j.circuitbreaker.CircuitBreaker
						.decorateCallable(defaultCircuitBreaker, bulkheadCall);
				return getAndApplyFallback(circuitBreakerCall, fallback);
			}
			else {
				Callable<T> bulkheadCall = bulkheadProvider.decorateCallable(this.groupName, tags, toRun::get);
				Callable<T> circuitBreakerCall = io.github.resilience4j.circuitbreaker.CircuitBreaker
						.decorateCallable(defaultCircuitBreaker, bulkheadCall);
				return getAndApplyFallback(circuitBreakerCall, fallback);
			}
		} else {
			if (executorService != null) {
				Supplier<Future<T>> futureSupplier = () -> executorService.submit(toRun::get);
				/* conditionally wrap in time-limiter */
				Callable<T> restrictedCall = timeLimiter
						.map(tl -> TimeLimiter.decorateFutureSupplier(tl, futureSupplier))
						.orElse(() -> futureSupplier.get().get());
				Callable<T> callable = io.github.resilience4j.circuitbreaker.CircuitBreaker
						.decorateCallable(defaultCircuitBreaker, restrictedCall);
				return getAndApplyFallback(callable, fallback);
			}
			else {
				Supplier<T> decorator = io.github.resilience4j.circuitbreaker.CircuitBreaker
						.decorateSupplier(defaultCircuitBreaker, toRun);
				return getAndApplyFallback(decorator, fallback);
			}
		}
	}

这个方法解答了springcloud对resilience4j封装的核心包装逻辑:
我们分析一下上面代码 @C 最复杂的分支(其他分支都是当前最复杂分支的简化):
@C1: 使用TimeLimiter对FeignCircuitBreakerInvocationHandler的asSupplier逻辑进行装饰
@C2:使用Bulkhead对逻辑进行装饰
@C3:使用CircuitBreaker对逻辑进行装饰

最终执行顺序:
CircuitBreaker(校验断路器状态) → Bulkhead(可选线程池和信号量两种方式实现) → TimeLimiter → CircuitBreaker 线程池 → FeignCircuitBreakerInvocationHandler业务逻辑

这里有一个细节:TimeLimiter 必须基于线程池才能发挥作用,因为TimeLimiter核心的api是通过future.get判断超时,需要依赖新线程;

复杂的情况下涉及到两个线程池,加上tomcat容器线程池就是3个了,所以要精心设计配置,否则就会出现线程不合理影响性能,cpu调度线程资源消耗加重;

配置说明及示例

以下是cloud开启circuitbreaker配置和openfeign超时配置

spring:
  cloud:
    # openfeign配置
    openfeign:
      # @FeignClient的fallback 生效
      circuitbreaker:
        enabled: true
      # openfeign超时设置
      client:
        config:
          default:
            # 日志级别
            loggerLevel: FULL
            # 请求超时时间(ms)
            connectTimeout: 3000
            # 响应超时时间(ms)
            readTimeout: 2000
          # 根据服务名字匹配的
          xxx-server:
            # 
            loggerLevel: FULL
            # (ms)
            connectTimeout: 3000
            # (ms)
            readTimeout: 4000
    # 对应配置文件 Resilience4JConfigurationProperties
    circuitbreaker:
      resilience4j:
        # 超时控制权完全交还给 Feign
        disableTimeLimiter: true
        # 关闭circuitbreaker线程池
        disableThreadPool: true

熔断器规则配置:

# resilience4j circuitbreaker断路器配置。配合@CircuitBreaker注解使用
resilience4j.circuitbreaker:
  configs:
    #熔断机制的默认设置
    default:
      #状态收集器类型
      #COUNT_BASED:根据数量计算,slidingWindowSize为次数
      #TIME_BASED:根据时间计算,slidingWindowSize为秒数
      slidingWindowType: TIME_BASED
      # 时间窗口的大小为60秒
      slidingWindowSize: 60
      # 在单位时间窗口内调用失败率达到 n% 后会启动断路器
      failureRateThreshold: 40
      # 在单位时间窗口内最少需要多少次调用才能开始进行统计计算
      minimumNumberOfCalls: 10
      # 进入halfOpen状态时,可以被调用次数,就算这些请求的失败率,低于设置的失败率变为close状态,否则变为open。
      permittedNumberOfCallsInHalfOpenState: 10
      # 允许断路器自动由打开状态转换为半开状态
      # 是否自动进入halfOpen状态,默认false-一定时间后进入halfOpen,ture-需要通过接口执行。
      automaticTransitionFromOpenToHalfOpenEnabled: true
      # 断路器打开状态转换为半开状态需要等待的时间
      waitDurationInOpenState: 5s
      # 消费数据处理的缓存数据量大小(如果事件产生速度大于消费速度,缓冲区可以临时存储事件,防止事件丢失)
      eventConsumerBufferSize: 4000
      # 哪些异常都当作失败来处理(会被fallback)
      recordExceptions:
        - org.springframework.web.client.HttpServerErrorException
        - java.io.IOException
        - java.util.concurrent.TimeoutException
        - java.lang.IllegalArgumentException
        - feign.RetryableException
        - java.net.SocketTimeoutException
        - feign.FeignException.ServiceUnavailable
      # 哪些异常直接忽略
      ignoreExceptions:
        - java.lang.IllegalStateException

  # 自定义熔断器实例
  instances:
    # 如果有自定义短路逻辑,则可以单独配置。
    xxx-server:
      #基于默认断路器
      baseConfig: default

over~~

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐