SpringCloud集成resilience4j导致seata全局事务标识丢失

204 阅读2分钟

版本

SpringCloud: 2023.0.0
Springboot: 3.2.4
Seata: 2.0.0

解决方案

在pom.xml添加如下依赖

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

resilience4j使用线程池模式

public class SeataPropagator implements ContextPropagator<String> {

    @Override
    public Supplier<Optional<String>> retrieve() {
        return () -> Optional.ofNullable(RootContext.getXID());
    }

    @Override
    public Consumer<Optional<String>> copy() {
        return s -> s.ifPresent(RootContext::bind);
    }

    @Override
    public Consumer<Optional<String>> clear() {
        return s -> RootContext.unbind();
    }
}

配置文件添加如下配置:

spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true
      client:
        config:
          default:
            connect-timeout: 1500
            read-timeout: 5000
            logger-level: basic
      circuitbreaker:
        alphanumeric-ids:
          enabled: true
        enabled: true
        group:
          enabled: true
    circuitbreaker:
      resilience4j:
        enableSemaphoreDefaultBulkhead: false

resilience4j:
  thread-pool-bulkhead:
    configs:
      default:
        context-propagators:
          - *.*.*.SeataPropagator

备注:此方法不适合信号量模式,当开启信号量模式时,配置SeataPropagator不会调用

org.springframework.cloud.circuitbreaker.resilience4j.Resilience4jBulkheadProvider#decorateCallable

public <T> Callable<T> decorateCallable(final String id, final Map<String, String> tags,
       final Callable<T> callable) {
    Resilience4jBulkheadConfigurationBuilder.BulkheadConfiguration configuration = configurations
          .computeIfAbsent(id, defaultConfiguration);
    
    if (useSemaphoreBulkhead(id)) {
       # 开启信号量模式会走下面逻辑,不会调用ContextPropagator.decorateRunnable,
       # 导致SeataPropagator不起作用
       Bulkhead bulkhead = bulkheadRegistry.bulkhead(id, configuration.getBulkheadConfig(), tags);
       return Bulkhead.decorateCallable(bulkhead, callable);
    }
    else {
       ThreadPoolBulkhead threadPoolBulkhead = threadPoolBulkheadRegistry.bulkhead(id,
             configuration.getThreadPoolBulkheadConfig(), tags);
       return () -> threadPoolBulkhead.decorateCallable(callable).get().toCompletableFuture().get();
    }
}

信号量模式

@Configuration
public class Resilience4JConfig {

    @Bean
    public Customizer<Resilience4JCircuitBreakerFactory> customizer(List<ContextPropagator> contextPropagators) {
        return factory -> factory.configureExecutorService(new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,
                TimeUnit.SECONDS,
                new SynchronousQueue<>()) {
            @Override
            public void execute(@NotNull Runnable command) {
                super.execute(ContextPropagator.decorateRunnable(contextPropagators, command));
            }
        });
    }

    @Bean
    public ContextPropagator<String> seataContextPropagator() {
        return new ContextPropagator<String>() {
            @Override
            public Supplier<Optional<String>> retrieve () {
                return () -> Optional.ofNullable(RootContext.getXID());
            }

            @Override
            public Consumer<Optional<String>> copy () {
                return s -> s.ifPresent(RootContext::bind);
            }

            @Override
            public Consumer<Optional<String>> clear () {
                return s -> RootContext.unbind();
            }
        };
    }
}

配置文件添加如下配置:

spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true
      client:
        config:
          default:
            connect-timeout: 1500
            read-timeout: 5000
            logger-level: basic
      circuitbreaker:
        alphanumeric-ids:
          enabled: true
        enabled: true
        group:
          enabled: false
    circuitbreaker:
      resilience4j:
        # 开启信号量模式
        enableSemaphoreDefaultBulkhead: true
        
resilience4j:
  circuitbreaker:
    configs:
      default:
        register-health-indicator: true
        sliding-window-size: 50
    instances:
      myService:
        slidingWindowType: COUNT_BASED           # 滑动窗口类型,基于请求数
        slidingWindowSize: 10                   # 窗口大小
        failureRateThreshold: 50                # 失败率阈值
        waitDurationInOpenState: 10s            # 熔断器打开后的等待时间
        permittedNumberOfCallsInHalfOpenState: 5 # 半开状态下允许的调用数
        minimumNumberOfCalls: 10                # 开启统计的最小调用数
        automaticTransitionFromOpenToHalfOpenEnabled: true # 自动从打开切换到半开状态

注意:此方案不能开启分组模式;即spring.cloud.openfeign.group.enabled=false 原因如下:

org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandler

public Object invoke(final Object proxy, final Method method, final Object[] args) {
    ...

    String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
    # 此处会根据是否开启分组来选择调用方法,来获取CircuitBreaker
    CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
          : factory.create(circuitName);
    Supplier<Object> supplier = asSupplier(method, args);
    if (this.nullableFallbackFactory != null) {
       Function<Throwable, Object> fallbackFunction = throwable -> {
          Object fallback = this.nullableFallbackFactory.create(throwable);
          try {
             return this.fallbackMethodMap.get(method).invoke(fallback, args);
          }
          catch (Exception exception) {
             unwrapAndRethrow(exception);
          }
          return null;
       };
       return circuitBreaker.run(supplier, fallbackFunction);
    }
    return circuitBreaker.run(supplier);
}
org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory

# 未开启分组时调用此方法,executorService为Resilience4JConfig中配置的线程池
public org.springframework.cloud.client.circuitbreaker.CircuitBreaker create(String id) {
    Assert.hasText(id, "A CircuitBreaker must have an id.");
    Resilience4JCircuitBreaker resilience4JCircuitBreaker = create(id, id, this.executorService);
    return tryObservedCircuitBreaker(resilience4JCircuitBreaker);
}

# 开启分组后,会调用下面的方法,Resilience4JConfig中配置的线程池不再起作用。
@Override
public org.springframework.cloud.client.circuitbreaker.CircuitBreaker create(String id, String groupName) {
    Assert.hasText(id, "A CircuitBreaker must have an id.");
    Assert.hasText(groupName, "A CircuitBreaker must have a group name.");
    final ExecutorService groupExecutorService = executorServices.computeIfAbsent(groupName,
          group -> Executors.newCachedThreadPool());
    Resilience4JCircuitBreaker resilience4JCircuitBreaker = create(id, groupName, groupExecutorService);
    return tryObservedCircuitBreaker(resilience4JCircuitBreaker);
}