Hystrix传播ThreadLocal对象,Feign调用返回null问题
微服务与微服务之间相互调用,你是否有过使用Hystrix时,该传播ThreadLocal对象的困惑?
我们知道Hystrix有隔离策略:
THREAD(线程池隔离):即:每个实例都增加个线程池进行隔离
SEMAPHORE(信号量隔离):适应非网络请求,因为是同步的请求,无法支持超时,只能依靠协议本身
现在有如下两种隔离机制的场景,先来看下默认隔离机制Thread
用户已登录的情况
通过Feign调用的auth用户登录认证服务 (被调用方)
调用方
结果,userinfo服务无法获取到用户登录的信息
切换组策略SEMAPHORE,并重启
重新进行请求(可通过ThreadLocal获取)
使用Hystrix时,获取ThreadLocal的两种方式
1、将隔离策略设为SEMAPHORE即可:
这样配置后,Feign可以正常工作。
但该方案不是特别好。原因是Hystrix官方强烈建议使用THREAD作为隔离策略!
2、自定义并发策略(博主采用方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | @Slf4j @Component public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { private HystrixConcurrencyStrategy delegate; public RequestAttributeHystrixConcurrencyStrategy() { try { log.info( "加载RequestAttributeHystrixConcurrencyStrategy" ); this .delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); if ( this .delegate instanceof RequestAttributeHystrixConcurrencyStrategy) { // 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(); } } } } |
/** * * 需要一个或者多个RequestInterceptor去配置诸如请求头信息,还给出了授权的头部配置。 * 到这里我们就明白了,我们需要实现一个RequestInterceptor,在里面将原来的请求头信息付给下游请求,实际上就是Cookie信息, * 这样sessionId就传到下游了,也就实现了共享。 */ @Configuration public class FeignRequestIntercepter implements RequestInterceptor { @Autowired RequestAttributeHystrixConcurrencyStrategy requestAttributeHystrixConcurrencyStrategy; @Override public void apply(RequestTemplate requestTemplate) { /** * 使用 RequestContextHolder.getRequestAttributes() 静态方法获得Request。 (但仅限于Feign不开启Hystrix支持时。) * 当Feign开启Hystrix支持时,获取值为null * 原因在于,Hystrix的默认隔离策略是THREAD 。而 RequestContextHolder 源码中,使用了两个ThreadLocal 。 * 解决方案一:调整隔离策略 将隔离策略设为SEMAPHORE即可 * hystrix.command.default.execution.isolation.strategy: SEMAPHORE * 这样配置后,Feign可以正常工作。但该方案不是特别好。原因是Hystrix官方强烈建议使用THREAD作为隔离策略! * * 解决方案二:自定义并发策略 * 既然Hystrix不太建议使用SEMAPHORE作为隔离策略,那么是否有其他方案呢? * 答案是自定义并发策略,目前,Spring Cloud Sleuth以及Spring Security都通过该方式传递 ThreadLocal 对象。 * 编写自定义并发策略比较简单,只需编写一个类,让其继承HystrixConcurrencyStrategy ,并重写wrapCallable 方法即可。 * */ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes == null){ System.out.println("requestAttributes为null"); return; } //获取本地线程绑定的请求对象 HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest(); System.out.println("获取本地线程绑定的请求对象:"+request); //给请求模板附加本地线程头部信息,主要是cookie信息 Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()){ String name =(String) headerNames.nextElement(); System.out.println("name:"+name); requestTemplate.header(name,request.getHeader(name)); } } }
补充:
亲自测试过:(以下均为Feign远程调用)
@PostMapping("/test")
public Boolean testUpdate(@RequestBody User user) {
//正常接收user
//执行相应更新逻辑
return true;
}
调用testUpdate端接收到的返回信息为null。
以上情况如何解决?
--> 可以将user实体转换成json格式字符串形式
@RequestParam: 写不写都可以,只是加上了该注解,相关的参数一定要传入
public Boolean testUpdate(@RequestParam String param) {
//正常接收param
//json转换成实体类
User user = ......;
//执行相应更新逻辑
return true;
}
以上修改完毕后,调用testUpdate端接收到的返回信息为false或true,正常
总结:远程调用feign,get请求,正常书写传参;post请求,尽量传递字符串
本人接单:有关java的任何单子,全栈--有意加扣:2115146575
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步