项目场景:
SpringCloud OpenFeign + Resilience4j + Seata 构建的微服务应用
问题描述
应用集成 SpringCloud OpenFeign 并使用 Resilience4j 开启熔断保护后,Seata 开启全局事务后,相关业务 Feign 调用时 Header 中未成功写入 XID
原因分析:
Seata XID 传播:
1、Seata 开启全局事务后会将 xid 写入执行当前业务的 Tomcat 线程的 ThreadLocal 中;
2、Seata 会实现 OpenFeign 的 RequestInterceptor 自动将当前线程的 ThreadLocal 中的 XID 写入本次 Feign 调用的 Header 中;
Resilience4j 熔断保护 Feign 调用:
1、Openfign 如果使用了 Resilience4j 熔断保护,默认采用线程池隔离模式,每次 Feign 调用都是用的 Resilience4j 的线程进行的,而非 Tomcat 的线程;
2、因为接入了 resilience4j 熔断保护现在 feign 调用使用了 resilience4j 的线程来执行,导致 seata 在使用 RootContext.getXID() 时值为 null,故无法成功将已开启的全局事务的 xid 添加到 feign header 中;
解决方案:
1、pom.xml 添加 resilience4j-bulkhead 依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-bulkhead</artifactId>
</dependency>
2、应用中添加 SeataPropagator 配置类实现 resilience4j ContextPropagator 完成将 tomcat 线程中的 ThreadLocal 中的 xid 拷贝到 resilience4j 的线程中
package cn.xxx.common.transaction;
import io.github.resilience4j.core.ContextPropagator;
import io.seata.core.context.RootContext;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* seata xid 传播处理
*
* @author jxh
* @version 1.0.0
* @since 2023/11/15 16:18
*/
public class SeataPropagator implements ContextPropagator<String> {
@NotNull
@Override
public Supplier<Optional<String>> retrieve() {
return () -> Optional.ofNullable(RootContext.getXID());
}
@NotNull
@Override
public Consumer<Optional<String>> copy() {
return s -> s.ifPresent(RootContext::bind);
}
@NotNull
@Override
public Consumer<Optional<String>> clear() {
return s -> RootContext.unbind();
}
}
3、应用配置文件添加 resilience4j 配置
resilience4j:
thread-pool-bulkhead:
configs:
default:
context-propagators:
- cn.xxx.common.transaction.SeataPropagator
4、大功告成~