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

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

创新互联是少有的网站建设、做网站、营销型企业网站、小程序定制开发、手机APP,开发、制作、设计、卖友情链接、推广优化一站式服务网络公司,2013年至今,坚持透明化,价格低,无套路经营理念。让网页惊喜每一位访客多年来深受用户好评

@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 headerNames = request.getHeaderNames(); 
  if (headerNames != null) { 
   while (headerNames.hasMoreElements()) { 
    String name = headerNames.nextElement(); 
    Enumeration 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.
 * 

* 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. *

* 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. *

* See {@link HystrixPlugins} or the Hystrix GitHub Wiki for information on configuring plugins: https://github.com/Netflix/Hystrix/wiki/Plugins. */ public abstract class HystrixConcurrencyStrategy

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

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

其中就有我们熟悉的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  Callable wrapCallable(Callable callable) { 
  RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); 
  return new WrappedCallable<>(callable, requestAttributes); 
 } 
 
 @Override 
 public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, 
   HystrixProperty corePoolSize, HystrixProperty maximumPoolSize, 
   HystrixProperty keepAliveTime, TimeUnit unit, BlockingQueue 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 getBlockingQueue(int maxQueueSize) { 
  return this.delegate.getBlockingQueue(maxQueueSize); 
 } 
 
 @Override 
 public  HystrixRequestVariable getRequestVariable(HystrixRequestVariableLifecycle rv) { 
  return this.delegate.getRequestVariable(rv); 
 } 
 
 static class WrappedCallable implements Callable { 
  private final Callable target; 
  private final RequestAttributes requestAttributes; 
 
  public WrappedCallable(Callable 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丢失的问题完美解决。

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


本文题目:详解feign调用session丢失解决方案
本文来源:http://pwwzsj.com/article/ghcccp.html