詳解feign調(diào)用session丟失解決方案
最近在做項(xiàng)目的時(shí)候發(fā)現(xiàn),微服務(wù)使用feign相互之間調(diào)用時(shí),存在session丟失的問(wèn)題。例如,使用Feign調(diào)用某個(gè)遠(yuǎn)程API,這個(gè)遠(yuǎn)程API需要傳遞一個(gè)鑒權(quán)信息,我們可以把cookie里面的session信息放到Header里面,這個(gè)Header是動(dòng)態(tài)的,跟你的HttpRequest相關(guān),我們選擇編寫(xiě)一個(gè)攔截器來(lái)實(shí)現(xiàn)Header的傳遞,也就是需要實(shí)現(xiàn)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); } } } } }
經(jīng)過(guò)測(cè)試,上面的解決方案可以正常的使用;
但是,當(dāng)引入Hystrix熔斷策略時(shí),出現(xiàn)了一個(gè)新的問(wèn)題:
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
此時(shí)requestAttributes會(huì)返回null,從而無(wú)法傳遞session信息,最終發(fā)現(xiàn)RequestContextHolder.getRequestAttributes(),該方法是從ThreadLocal變量里面取得對(duì)應(yīng)信息的,這就找到問(wèn)題原因了,是由于Hystrix熔斷機(jī)制導(dǎo)致的。
Hystrix有2個(gè)隔離策略:THREAD以及SEMAPHORE,當(dāng)隔離策略為 THREAD 時(shí),是沒(méi)辦法拿到 ThreadLocal 中的值的。
因此有兩種解決方案:
方案一:調(diào)整格隔離策略:
hystrix.command.default.execution.isolation.strategy: SEMAPHORE
這樣配置后,F(xiàn)eign可以正常工作。
但該方案不是特別好。原因是Hystrix官方強(qiáng)烈建議使用THREAD作為隔離策略! 可以參考官方文檔說(shuō)明。
方案二:自定義策略
記得之前在研究zipkin日志追蹤的時(shí)候,看到過(guò)Sleuth有自己的熔斷機(jī)制,用來(lái)在thread之間傳遞Trace信息,Sleuth是可以拿到自己上下文信息的,查看源碼找到了
org.springframework.cloud.sleuth.instrument.hystrix.SleuthHystrixConcurrencyStrategy
這個(gè)類,查看SleuthHystrixConcurrencyStrategy的源碼,繼承了HystrixConcurrencyStrategy,用來(lái)實(shí)現(xiàn)了自己的并發(fā)策略。
/** * 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 * rel="external nofollow" >https://github.com/Netflix/Hystrix/wiki/Plugins</a>. */ public abstract class HystrixConcurrencyStrategy
搜索發(fā)現(xiàn)有好幾個(gè)地方繼承了HystrixConcurrencyStrategy類
其中就有我們熟悉的Spring Security和剛才提到的Sleuth都是使用了自定義的策略,同時(shí)由于Hystrix只允許有一個(gè)并發(fā)策略,因此為了不影響Spring Security和Sleuth,我們可以參考他們的策略實(shí)現(xiàn)自己的策略,大致思路:
將現(xiàn)有的并發(fā)策略作為新并發(fā)策略的成員變量;
在新并發(fā)策略中,返回現(xiàn)有并發(fā)策略的線程池、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(); } } } }
最后,將這個(gè)策略類作為bean配置到feign的配置類FeignClientsConfigurationCustom中
@Bean public FeignHystrixConcurrencyStrategy feignHystrixConcurrencyStrategy() { return new FeignHystrixConcurrencyStrategy(); }
至此,結(jié)合FeignClientsConfigurationCustom配置feign調(diào)用session丟失的問(wèn)題完美解決。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)現(xiàn)畫(huà)圖 給圖片底部添加文字標(biāo)題
這篇文章主要介紹了Java實(shí)現(xiàn)畫(huà)圖 給圖片底部添加文字標(biāo)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11詳解Java的Hibernate框架中的緩存與原生SQL語(yǔ)句的使用
這篇文章主要介紹了Java的Hibernate框架中的緩存與原生SQL語(yǔ)句的使用,Hibernate是Java的SSH三大web開(kāi)發(fā)框架之一,需要的朋友可以參考下2015-12-12Java異常中toString()和getMessage()區(qū)別
在java異常體系中,要打印異常信息,可以通過(guò):e.getMessage() 、 e.toString() e.printStackTrace() 等方法打印,本文主要介紹了Java異常中toString()和getMessage()區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01maven setting多倉(cāng)庫(kù)配置方式
這篇文章主要介紹了maven setting多倉(cāng)庫(kù)配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Java并發(fā)(Runnable+Thread)實(shí)現(xiàn)硬盤文件搜索功能
這篇文章主要介紹了Java并發(fā)(Runnable+Thread)實(shí)現(xiàn)硬盤文件搜索,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01java對(duì)象轉(zhuǎn)成byte數(shù)組的3種方法
這篇文章主要為大家詳細(xì)介紹了java對(duì)象轉(zhuǎn)成byte數(shù)組的3種方法,具有一定的參考價(jià)值,感興趣的朋友可以參考一下2018-06-06springboot整合minio實(shí)現(xiàn)文件上傳與下載且支持鏈接永久訪問(wèn)
本文主要介紹了springboot整合minio實(shí)現(xiàn)文件上傳與下載且支持鏈接永久訪問(wèn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01