详解feign调用session丢失解决方案

最近在做项目的时候发现,微服务使用feign相互之间调用时,存在session丢失的问题。例如,使用Feign调用某个远程API,这个远程API需要传递一个鉴权信息,我们可以把cookie里面的session信息放到Header里面,这个Header是动态的,跟你的HttpRequest相关,我们选择编写一个拦截器来实现Header的传递,也就是需要实现RequestInterceptor接口,具体代码如下:

@Configuration
@EnableFeignClients(basePackages = "com.xxx.xxx.client")
public class FeignClientsConfigurationCustom implements RequestInterceptor { 

 @Override
 public void apply(RequestTemplate template) { 

  RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  if (requestAttributes == null) {
   return;
  } 

  HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
  Enumeration<String> headerNames = request.getHeaderNames();
  if (headerNames != null) {
   while (headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    Enumeration<String> values = request.getHeaders(name);
    while (values.hasMoreElements()) {
     String value = values.nextElement();
     template.header(name, value);
    }
   }
  } 

 } 

} 

经过测试,上面的解决方案可以正常的使用;

但是,当引入Hystrix熔断策略时,出现了一个新的问题:

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

此时requestAttributes会返回null,从而无法传递session信息,最终发现RequestContextHolder.getRequestAttributes(),该方法是从ThreadLocal变量里面取得对应信息的,这就找到问题原因了,是由于Hystrix熔断机制导致的。 
Hystrix有2个隔离策略:THREAD以及SEMAPHORE,当隔离策略为 THREAD 时,是没办法拿到 ThreadLocal 中的值的。

因此有两种解决方案:

方案一:调整格隔离策略:

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

这样配置后,Feign可以正常工作。

但该方案不是特别好。原因是Hystrix官方强烈建议使用THREAD作为隔离策略! 可以参考官方文档说明。

方案二:自定义策略

记得之前在研究zipkin日志追踪的时候,看到过Sleuth有自己的熔断机制,用来在thread之间传递Trace信息,Sleuth是可以拿到自己上下文信息的,查看源码找到了 
org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixConcurrencyStrategy 
这个类,查看SleuthHystrixConcurrencyStrategy的源码,继承了HystrixConcurrencyStrategy,用来实现了自己的并发策略。

/**
 * Abstract class for defining different behavior or implementations for concurrency related aspects of the system with default implementations.
 * <p>
 * For example, every {@link Callable} executed by {@link HystrixCommand} will call {@link #wrapCallable(Callable)} to give a chance for custom implementations to decorate the {@link Callable} with
 * additional behavior.
 * <p>
 * When you implement a concrete {@link HystrixConcurrencyStrategy}, you should make the strategy idempotent w.r.t ThreadLocals.
 * Since the usage of threads by Hystrix is internal, Hystrix does not attempt to apply the strategy in an idempotent way.
 * Instead, you should write your strategy to work idempotently. See https://github.com/Netflix/Hystrix/issues/351 for a more detailed discussion.
 * <p>
 * See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: <a
 * href="https://github.com/Netflix/Hystrix/wiki/Plugins" rel="external nofollow" >https://github.com/Netflix/Hystrix/wiki/Plugins</a>.
 */
public abstract class HystrixConcurrencyStrategy 

搜索发现有好几个地方继承了HystrixConcurrencyStrategy类

其中就有我们熟悉的Spring Security和刚才提到的Sleuth都是使用了自定义的策略,同时由于Hystrix只允许有一个并发策略,因此为了不影响Spring Security和Sleuth,我们可以参考他们的策略实现自己的策略,大致思路:

将现有的并发策略作为新并发策略的成员变量;

在新并发策略中,返回现有并发策略的线程池、Queue;

代码如下:

public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { 

 private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategy.class);
 private HystrixConcurrencyStrategy delegate; 

 public FeignHystrixConcurrencyStrategy() {
  try {
   this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
   if (this.delegate instanceof FeignHystrixConcurrencyStrategy) {
    // Welcome to singleton hell...
    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 ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
   HystrixThreadPoolProperties threadPoolProperties) {
  return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
 } 

 @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);
 } 

 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();
   }
  }
 }
} 

最后,将这个策略类作为bean配置到feign的配置类FeignClientsConfigurationCustom中

 @Bean
 public FeignHystrixConcurrencyStrategy feignHystrixConcurrencyStrategy() {
  return new FeignHystrixConcurrencyStrategy();
 }

至此,结合FeignClientsConfigurationCustom配置feign调用session丢失的问题完美解决。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解feign调用session丢失解决方案

    最近在做项目的时候发现,微服务使用feign相互之间调用时,存在session丢失的问题.例如,使用Feign调用某个远程API,这个远程API需要传递一个鉴权信息,我们可以把cookie里面的session信息放到Header里面,这个Header是动态的,跟你的HttpRequest相关,我们选择编写一个拦截器来实现Header的传递,也就是需要实现RequestInterceptor接口,具体代码如下: @Configuration @EnableFeignClients(basePack

  • 详解Java分布式Session共享解决方案

    分布式Session一致性? 说白了就是服务器集群Session共享的问题 Session的作用? Session 是客户端与服务器通讯会话跟踪技术,服务器与客户端保持整个通讯的会话基本信息. 客户端在第一次访问服务端的时候,服务端会响应一个sessionId并且将它存入到本地cookie中,在之后的访问会将cookie中的sessionId放入到请求头中去访问服务器,如果通过这个sessionid没有找到对应的数据那么服务器会创建一个新的sessionid并且响应给客户端. 分布式Sessio

  • 详解java调用存储过程并封装成map

    详解java调用存储过程并封装成map 本文代码中注释写的比较清楚不在单独说明,希望能帮助到大家, 实例代码: public List<Map<String , Object>> doCallProcedure(String procedureString,String[] parameters) throws PersistentDataOperationException { if (!isReady ()) { throw new PersistentDataOperatio

  • 详解C# 中Session的用法

    Session模型简介 在学习之前我们会疑惑,Session是什么呢?简单来说就是服务器给客户端的一个编号.当一台WWW服务器运行时,可能有若干个用户浏览正在运正在这台服务器上的网站.当每 个用户首次与这台WWW服务器建立连接时,他就与这个服务器建立了一个Session,同时服务器会自动为其分配一个SessionID,用以标识这个用 户的唯一身份.这个SessionID是由WWW服务器随机产生的一个由24个字符组成的字符串,我们会在下面的实验中见到它的实际样子. 这个唯一的SessionID是有

  • 详解Python调用系统命令的六种方法

    作为胶水语言,Python可以很方便的执行系统命令,Python3中常用的执行操作系统命令有os.system().os.popen().subprocess.popen().subprocess.call().subprocess.run().subprocess.getstatusoutput()六种方法. os.system() system函数可以将字符串转化成命令在服务器上运行:其原理是每一条system函数执行时,其会创建一个子进程在系统上执行命令行,子进程的执行结果无法影响主进程.

  • 详解PHP调用Go服务的正确方式

    问题 服务耦合 我们在开发过程中可能会遇到这样的情况: 进程依赖于某服务,所以把服务耦合在进程代码中: 服务初始化耗时长,拖慢了进程启动时间: 服务运行要占用大量内存,多进程时内存损耗严重. 文本匹配服务,它是消息处理流程中的一环,被多个消息处理进程依赖,每次初始化进程要 6秒 左右时间构造 Trie 树,而且服务读取关键词大文件.使用树组构造 Trie 树,会占用大量(目前设置为 256M )内存. 我已经把进程写成了守护进程的形式,让它们长时间执行,虽然不用更多地考虑初始化时间了,但占用内存

  • 详解Feign的实现原理

    目录 一.什么是Feign 二.为什么用Feign 三.实例 3.1.原生使用方式 3.2.结合 Spring Cloud 使用方式 四.探索Feign 五.总结 一.什么是Feign Feign 是⼀个 HTTP 请求的轻量级客户端框架.通过 接口 + 注解的方式发起 HTTP 请求调用,面向接口编程,而不是像 Java 中通过封装 HTTP 请求报文的方式直接调用.服务消费方拿到服务提供方的接⼝,然后像调⽤本地接⼝⽅法⼀样去调⽤,实际发出的是远程的请求.让我们更加便捷和优雅的去调⽤基于 HT

  • 详解java调用ffmpeg转换视频格式为flv

    详解java调用ffmpeg转换视频格式为flv 注意:下面的程序是在Linux下运行的,如果在windows下rmvb转换成avi会出现问题,想成功需要下载下个drv43260.dll东西放到C:WindowsSystem32下面 这几天在写一个视频管理系统,遇到一个很大的问题就是如果把不同格式转换为flv,格式!经过网上的一番搜索,自己在总结,整理,整理,终于整出来了!实现了视频进行转换的同时还能够进行视频截图和删除原文件的功能! 格式转换主要原理就是先用java调用ffmpeg的exe文件

  • 详解C++调用Python脚本中的函数的实例代码

    1.环境配置 安装完python后,把python的include和lib拷贝到自己的工程目录下 然后在工程中包括进去 2.例子 先写一个python的测试脚本,如下 这个脚本里面定义了两个函数Hello()和_add().我的脚本的文件名叫mytest.py C++代码: #include "stdafx.h" #include <stdlib.h> #include <iostream> #include "include\Python.h&quo

  • 详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐) 调用python脚本中的函数 准备工作: 创建maven工程,结构如下: 到官网https://www.jython.org/download.html下载Jython的jar包或者在maven的pom.xml文件中加入如下代码: <dependency> <groupId>org

随机推荐