背景

在升级Springboot版本的时候,之前使用的是ContextHolder

threadLocal=ContextHolder.getInstance().getContextHolder();
Map<String, String> parMap=new HashMap<>();
parMap.put("Key","Value");
threadLocal.set(parMap);

获取一个map保存传递信息

原理是 使用Feign的拦截器

if(parMap!=null&&parMap.size()>0&&parMap.containsKey("Key")){
template.header("Key","Value");
}else{
logger.info(Thread.currentThread().getName()+"为空。");
}

Feign的每次调用之前获取Key放到请求头上面

这种方法存在线程安全问题,需改造

  1. 引入依赖

    <dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
    </dependency>
  2. 实现ContextPropagator

    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:
    thread-pool-bulkhead:
    configs:
    default:
    context-propagators:
    - cn.xxx.common.transaction.SeataPropagator

Q:为什么是使用新方法了而不是继续使用ContextHolder

A:ContextHolder使用TransmittableThreadLocal储存需要传递到下游的请求头,这在使用Hystrix或者不开启熔断器的时候有用

但当项目升级到使用Resilience4j作为熔断器之后,这种传递方式就不好用了,因为接入了 Resilience4j 熔断保护现在 Feign 调用使用了
Resilience4j 的线程来执行,导致 ThreadLocal.get(“Key”) 时值为 null,故无法把Val添加到 Feign Header 中

implements ContextPropagator 重写了copy方法

在copy方法里进行设置当前静态Context,Context可以获取到当前线程的保存的请求头参数,拷贝到Resilience4j线程中用来传递需要的信息

延展阅读

ThreadLocal和Resilience4J

Feign调用开启Hystrix时无法获取ThreadLocal

Resilience4j 开启熔断保护后 Feign 调用时 Seata XID 未成功写入 Header