最近在做公司微服務項目的時候發現,微服務使用feign互相之間調用時,存在session丢失的問題。
例如,使用feign調用某個遠端API,這個遠端API需要傳遞一個鑒權資訊,我們可以把cookie裡面的session資訊放到http request 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是可以拿到自己上下文資訊的,于是檢視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" target="_blank" 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丢失的問題完美解決。