微服务中 Feign调用以及网关Zuul的请求头相关问题

微服务中 Feign调用以及网关Zuul的请求头与字段相关问题

文章目录

  • 微服务中 Feign调用以及网关Zuul的请求头与字段相关问题
    • 前言
    • 1. Feign远程调用请求头丢失
      • 1.1 原因分析
      • 1.2 解决方案1
      • 1.3 解决方案2
    • 2. Zuul对请求头的加工

前言

在Spring Cloud中使用Feign进行远端调用时,会发生请求头信息的丢失,下游服务无法获取到上游服务请求头的问题。在搭配Zuul网关后,可能会需要使用前置过滤器对上游服务的请求头信息进行修改、添加或过滤 再转至下游服务。

1. Feign远程调用请求头丢失

在不进行任何配置的请求下,Feign在进行远程调用的时候,会发现下游服务无法获取到上游服务发送的请求头,而在许多时候,请求头包含了一些重要信息,如 关联性ID、授权认证等等。

1.1 原因分析

Feign发起远程调用,实际上是通过@HystrixCommand命令去发起的,而了解Hystrix的调用隔离策略可知,Hsyrix有两种隔离策略,一种SEMAPHORE(信号量)这是一个轻量级的隔离策略,当上游服务发生远程调用时,会直接在原调用线程中直接发起;另一种Thread(线程)这是一个默认的隔离策略,也是Hystrix官方大力推荐地策略,使用THREAD策略会另起一个子线程来进行远程调用,这样远程调用可能发生的错误或异常也不会想到原调用线程。

引起下游服务无法接收到请求头信息的原因有二:其一,在上游服务接收到请求头新信息之后,并没有把请求头信息再封装到远程调用的request中,这里可以通过加入一个拦截器FeignInterceptor来添加;其二,即使添加了FeignInterceptor也会发现下游服务依然无法获取到请求头信息,这是由于 FeignInterceptor中,在封装请求头信息到request的时候就已经丢失了,在获取请求头信息时,一般是通过 RequestContextHolder进行获取的,而这个类会将RequestContext请求上下文封装到ThreadLocal中以供使用,而由于Hystrix的默认隔离策略,子线程与父线程之间是无法传递ThreadLocal对象的

1.2 解决方案1

知道了原因就可以有两个解决方案,最简单粗暴但不推荐的就是改变Hystrix的隔离策略,将默认的Thread策略改为SEMAPHORE策略,但是既然官方极力不推荐这种,也只做了解吧。

① FeignInterceptor拦截器,添加需要转发的请求头信息

/**
 * @Author: Jam
 * @Date: 2020/5/30 13:12
 */
@Component
public class FeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            //header消息传递 本文示例 传递四个请求头信息
            template.header("tmx-correlation-id",request.getHeader("tmx-correlation-id"));
            template.header("tmx-auth-token",request.getHeader("tmx-auth-token"));
            template.header"tmx-user-id",request.getHeader("tmx-user-id"));
            template.header("tmx-org-id",request.getHeader("tmx-org-id"));
        }
    }
}

② 修改@HystrixCommand的执行策略

@HyscrixCommand(commandProperties={@HysrixProperty(name="execution.isolation.strategy",value="SEMAPHORE")})
public void RemoteCall(){
    return feignClient.get();
}

以上为通过修改Hystrix的隔离策略为SEMAHORE来解决Feign调用中无法传递请求头。但不推荐,不推荐,不推荐!

1.3 解决方案2

为了使用Thread策略来执行Hystrix调用,那么就要将父线程(原调用线程)的RequestContext传递到子线程中,Hystrix提供了并发策略机制,完成上下文的传播,所以只要通过自定义并发策略 就可以让子线程获得请求头信息。tips:Hystrix只允许一个并发策略,所以自定义并发策略时,原先会存在一个用以处理安全的并发策略,所以要把他们进行合并。

① 自定义并发策略FeignConfig,继承HystrixConcurrencyStrategy类

@Component
public class FeignConfig extends HystrixConcurrencyStrategy {
    private static final Logger log = LoggerFactory.getLogger(FeignConfig.class);
    private HystrixConcurrencyStrategy delegate; 

    public FeignConfig() {
        try {
            this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegate instanceof FeignConfig) {
                return;
            }
            HystrixCommandExecutionHook commandExecutionHook =
                    HystrixPlugins.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy =
                    HystrixPlugins.getInstance().getPropertiesStrategy();
            this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
            // 插件重置
            HystrixPlugins.reset();
            //设置自定义的并发策略
            HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
            HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
        } catch (Exception e) {
            log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
        }
    }

    private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
                                                 HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) {
        if (log.isDebugEnabled()) {
            log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
                    + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
                    + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
            log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
        }
    }

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

    @Override
    public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize,
                                            HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
                unit, workQueue);
    }

    @Override
    public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
        return this.delegate.getBlockingQueue(maxQueueSize);
    }

    @Override
    public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
        return this.delegate.getRequestVariable(rv);
    }

    //一个实现Callable的静态类,这里完成父子线程的上下文传播
    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();
            }
        }
    }
}

FeignConfig需要做两件事,要注册注册一个新的并发策略,所以要获取所有Hystrix组件,重置之后,再重新设置,具体操作详见public Feign()方法,关键方法 WrappedCallable方法中的call()方法,这个方法会在@HystrixCommand之前被调用,以此Hystrix管理的子线程即可获取到父线程的RequestAttributes进而获取请求头信息。

② FeignInterceptor拦截器,添加需要转发的请求头信息,代码与1.2中的代码一致,不再赘述,不要漏了这个拦截器类!自定义的并发策略只是完成了父子线程RequestContext的传播问题。

2. Zuul对请求头的加工

本文采用的@EnableZuulProxy,如果是其他启动类配置,可能会有不适用的情况,未进行尝试了深究 故方案仅测试了@EnableZuulProxy的配置下。

默认情况下,Zuul会直接转发来自及上游的请求(请求头信息也会全部携带),而Zuul不允许直接添加或修改请求中的HTTP请求首部,如果想要添加并且在以后的过滤器能再访问他,需要通过RequestContext.getZuulRequestHeaders和 .addZuulRequestHeaders()进行获取和添加。这个方法将维护一个单独的HTTP首部映射,在Zuul服务器调用目标服务时,包含在ZuulRequestHeader映射中的数据将被合并。

示例:添加请求头信息、修改请求头信息、过滤(删除)请求头信息。

@Component
public class TrackingFilter extends ZuulFilter {
    private static final int FILTER_ORDER=0;
    private static final boolean SHOULD_FILTER=true;
    private static final Logger logger= LoggerFactory.getLogger(TrackingFilter.class);

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    private boolean isCorrelationIdPresent(){
        HttpServletRequest request=RequestContext.getCurrentContext().getRequest();
        return request.getHeader("tmx-correlation-id") != null;
    }

    @Override
    public Object run() throws ZuulException {
        //run()方法是每次服务通过过滤器时执行的代码
        if(isCorrelationIdPresent()){
            logger.info("tmx-correlation-id found in tracking filter{}.",RequestContext.getCurrentContext().getRequest().getHeader("tmx-correlation-id"));
            //验证Zuul是否接受到 tmx-correlation-id请求头
            RequestContext.getCurrentContext().addZuulRequestHeader("tmx-correlation-id","123");
            //修改 tmx-correlation-id请求头的值为 123
            RequestContext.getCurrentContext().addZuulRequestHeader("tmx-zuul","123");
            //添加 tmx-zuul 请求头 值为 123
        }else{
            logger.info("没有啊亲");
        }
        RequestContext ctx=RequestContext.getCurrentContext();
        logger.info("Processing incoming request for{}.",ctx.getRequest().getRequestURI());
        return null;
    }

    @Override
    public String filterType() {
        return PRE_TYPE; 
 //   String ERROR_TYPE = "error"; 
 //   String POST_TYPE = "post";
 //  String PRE_TYPE = "pre";  设置为前置过滤器
 //   String ROUTE_TYPE = "route";
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }
}

上述代码中通过 addZuulRequestHeader 给请求中 添加以及修改了请求头信息(如果发现Zuul对请求头的操作没有生效,检查获取上下文的位置,确保是在run方法中进行的获取而不是在类属性里),若需要过滤掉请求头的部分信息不转发至下游,可以通过修改 application.yml文件。

zuul:
  ignored-headers: tmx-user-id #多个时,用“,”间隔开

参考书籍:《Spring微服务实战》
参考博客:feign调用session丢失解决方案

你可能感兴趣的:(笔记)