版本
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);
}