1. 程式人生 > >feign呼叫session丟失解決方案

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

獲取不到request資訊,從而無法傳遞session資訊,最終發現RequestContextHolder.getRequestAttributes()該方法是從ThreadLocal變數裡面取得對應資訊的,這就找到問題原因了,由於Hystrix熔斷機制導致的。
Hystrix有隔離策略: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">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();  
      }  
    }  
  }  
}  

至此,結合FeignClientsConfigurationCustom配置feign呼叫session丟失的問題完美解決