微服务与微服务之间相互调用,你是否有过使用Hystrix时,该传播ThreadLocal对象的困惑?
我们知道Hystrix有隔离策略:
THREAD(线程池隔离):即:每个实例都增加个线程池进行隔离
SEMAPHORE(信号量隔离):适应非网络请求,因为是同步的请求,无法支持超时,只能依靠协议本身

现在有如下两种隔离机制的场景,先来看下默认隔离机制Thread
用户已登录的情况
通过Feign调用的auth用户登录认证服务 (被调用方)

调用方


结果,userinfo服务无法获取到用户登录的信息
切换组策略SEMAPHORE,并重启
重新进行请求(可通过ThreadLocal获取)

使用Hystrix时,获取ThreadLocal的两种方式
1、将隔离策略设为SEMAPHORE即可:
hystrix.command.default.execution.isolation.strategy: SEMAPHORE
这样配置后,Feign可以正常工作。
但该方案不是特别好。原因是Hystrix官方强烈建议使用THREAD作为隔离策略!
2、自定义并发策略(博主采用方式)
@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远程调用返回null问题解决
1、首先确保下游服务接收的参数,从上游正常传入
2、添加有关Feign的相关注解
@EnableFeignClients
@FeignClient
3、下游若需要返回对象,不要用Object进行返回,将Object改为对象(如UserModel)


至此,以上问题均得到了解决。(可以请博主喝杯咖啡喔,有需要博主解决的问题,欢迎评论区留言),对了,博主是全能型的喔

