likes
comments
collection
share

feign 调用服务请求头丢失

作者站长头像
站长
· 阅读数 14

feign 调用服务请求头丢失

  服务 A 通过 Feign 调用服务 B ,服务 B 无法获取服务 A 的 header

排查过程

  1. 先在 feign 调用的方法上打断点,走到 SynchronousMethodHandler

feign 调用服务请求头丢失

  1. 进到 invoke 方法后发现有一个 create 方法,而创建的 RequestTemplate 的 header 是空的

feign 调用服务请求头丢失

  1. 到 executeAndDecode 方法后发现有一个 targetRequest 的方法,不知道做什么用的,而获取响应结果的方法的 Request 的请求头也是空的

feign 调用服务请求头丢失

  1. request 的 header 是空的,所以服务 B 获取不到值

解决方案

  1. 上面发现有一个不知道做什么的方法,传进去一个 RquestTemplate 返回一个 Request,那么点进去feign 调用服务请求头丢失
  2. 我们不知道这个 interceptors 是哪来的,但是初始化默认为空集合,以多年使用 spring 的经验来看,猜测是对 template 进行增强的,那么我们重写该方法。(想要知道从哪里集成的 requestInterceptor 看 FeignClientFactoryBean.java:146,这里不进行讲解)
@Component
public class FeignExecBeforeInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        
        if (requestAttributes != null) {
            HttpServletRequest request = requestAttributes.getRequest();
            // 拿到原始请求头数据
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String header = headerNames.nextElement();
                String headerValue = request.getHeader(header);
                if (StringUtils.isNotEmpty(headerValue)) {
                    template.header(header, headerValue);
                }
            }
        }
    }
}

feign 开启 hystrix 请求头丢失

  feign 开启 hystrix 后 RequestContextHolder.getRequestAttributes() 为 null

排查过程

  1. 断点发现,执行的类由 SynchronousMethodHandler 变为 HystrixInvocationHandler,说明 feign 集成了 hystrix

feign 调用服务请求头丢失

解决方案 1:调整隔离策略

将隔离策略调整为 semaphore 即可(配置对应的类:com.netflix.hystrix.HystrixCommandProperties#HystrixCommandProperties(com.netflix.hystrix.HystrixCommandKey, com.netflix.hystrix.HystrixCommandProperties.Setter, java.lang.String))

hystrix.command.default.execution.isolation.strategy: SEMAPHORE

  但是官网建议:

  一般来说,你应该使用信号量隔离(SEMAPHORE)的唯一场景是调用量太大(每个实例每秒数百次),以至于单独线程的开销太高;这通常仅适用于非网络调用。

github.com/Netflix/Hys…

解决方案 2 :自定义并发策略

  自定义并发策略,将请求头设置到 threadLocal 里

@Slf4j
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

    public RequestAttributeHystrixConcurrencyStrategy() {
        HystrixPlugins.reset();
        HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
    }

    @Override
    public <T> Callable<T> wrapCallable(Callable<T> callable) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return new WrappedCallable<>(callable, requestAttributes);
    }

    static class WrappedCallable<T> implements Callable<T> {

        private final Callable<T> target;
        private final RequestAttributes requestAttributes;

        public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
            this.target = target;
            this.requestAttributes = requestAttributes;
        }

        @Override
        public T call() throws Exception {
            try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
                return target.call();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        }
    }
}

  Hystrix 只允许有一个并发策略

最终解决方案

实用技巧:Hystrix传播ThreadLocal对象(两种方案) - 简书 (jianshu.com)