Feign Hystrix微服务调用Session传播
在使用SpringCloud来构建微服务时,服务和服务之间的调用非常频繁,服务之间调用通常用feign和Hystrix结合来使用,当使用上游微服务调用下游微服务时,怎么将上游服务的请求信息传递到下游服务中去呢?Feign提供了Interceptor来设置请求下游服务的header等信息,如下:@Componentpublic class FeignRequestInterceptor ...
在使用SpringCloud来构建微服务时,服务和服务之间的调用非常频繁,服务之间调用通常用feign和Hystrix结合来使用,当使用上游微服务调用下游微服务时,怎么将上游服务的请求信息传递到下游服务中去呢?Feign提供了Interceptor来设置请求下游服务的header等信息,如下:
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Value("${spring.application.name}")
private String serviceName;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("from-service", serviceName);
}
}
这样可以设置系统配置的数据到http请求header中,同样可以设置数据库的数据或者是其他服务提供的数据。但是要设置请求上游服务的请求header的话,就比较麻烦了,有人提供了以下解决方案
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Value("${spring.application.name}")
private String serviceName;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("from-service", serviceName);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(null != attributes){
HttpServletRequest request = attributes.getRequest();
String sessionId = request.getHeader("SESSIONID");
if(!StringUtils.isEmpty(sessionId)){
requestTemplate.header("SESSIONID", sessionId);
}
}
}
}
这个方案在只使用feign的情况下没有问题,但是在feign和hystrix结合使用时,却有了问题,调试发现RequestContextHolder.getRequestAttributes()每次返回都是空,这是为什么呢?
查看RequestContextHolder的源码发现,RequestContextHolder将ServletRequestAttribute存放在了线程ThreadLocal中,而Hystrix默认的是多线程的,不是同一个线程,ThreadLocal中的数据自然拿不到,这个时候怎么解决这个问题呢?
1,调整hystrix执行的隔离策略。
给hystrix增加配置
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
是什么意思呢?
strategy可以取THREAD和SEMAPHORE两种策略,两种策略分别是:
- THREAD,HystrixCommand.run()在一个单独的线程中执行,即下游依赖的请求在一个单独的线程中隔离,并发请求数收到线程中线程数的限制。
- SEMAPHORE,HystrixCommand.run()在调用线程中执行,即下游依赖请求在当前调用线程中执行,并发请求受信号量计数的限制
这样,上述配置就是告诉Hystrix使用SEMAPHORE隔离策略,调用和请求下游依赖在一个线程中执行,就可以访问同一个Threadlocal中的数据了。
但是,官方建议,执行HystrixCommand使用THREAD策略,执行HystrixObservableCommand使用SEMAPHORE策略。因为在线程中执行的命令对除网络超时以外的延迟有额外的保护层。通常,HystrixCommands使用信号量模式唯一的场景是,调用的并发量非常高(每个实例每秒数百个)是,这样线程的开销就非常高,通常用于线下的调用。
这样配置以后,RequestContextHolder.getRequestAttributes()就可以拿到数据了,但是hystrix官方却不推荐这么做,所以暂不考虑。
官方文档见:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy
所以这种方式可以实现,但是不推荐。
2,重写THREAD策略,将ServletRequestAttribute传入到hystrix熔断执行下游请求的线程中。
首先,查看查看Hystrix的THREAD策略的实现,见(HystrixConcurrencyStrategy),发现wrapCallable方法提供了一个在请求执行前覆写的机会,如下
/**
* Provides an opportunity to wrap/decorate a {@code Callable<T>} before execution.
* <p>
* This can be used to inject additional behavior such as copying of thread state (such as {@link ThreadLocal}).
* <p>
* <b>Default Implementation</b>
* <p>
* Pass-thru that does no wrapping.
*
* @param callable
* {@code Callable<T>} to be executed via a {@link ThreadPoolExecutor}
* @return {@code Callable<T>} either as a pass-thru or wrapping the one given
*/
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return callable;
}
重写wrapCallable方法,添加自定义逻辑
@Component
public class HystrixConcurrencyStrategyCustomize extends HystrixConcurrencyStrategy {
public <T> Callable<T> wrapCallable(Callable<T> callable) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HystrixCustomizeCallable hystrixCustomizeCallable = new HystrixCustomizeCallable(attributes, callable);
return hystrixCustomizeCallable;
}
}
class HystrixCustomizeCallable<T> implements Callable<T>{
private ServletRequestAttributes attributes;
private Callable<T> callable;
public HystrixCustomizeCallable(ServletRequestAttributes attributes, Callable<T> callable){
this.attributes = attributes;
this.callable = callable;
}
@Override
public T call() throws Exception {
try{
if(null != this.attributes){
RequestContextHolder.setRequestAttributes(this.attributes);
}
return this.callable.call();
}finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
使策略生效
自定义的策略如何在编写后生效呢?Hystrix官网中提供了解决方案
If you wish to register a plugin before you invoke the HystrixCommand for the first time, you may do so with code like the following:
|
这样,在自定义的并发策略的基础上,再在Feign的RequestInterceptor中为请求添加header,将上游请求的session信息传递到下游微服务。
但是这样的解决方案在应用加入actuator是依然后问题。
Caused by: java.lang.IllegalStateException: Another strategy was already registered.
提示已经注册策略,不能重复注册,参考下Sleuth以及Spring Security的实现,先删除已经注册的策略,重新注册新的策略,如下:
@Configuration
public class HystrixConfig {
public static final Logger log = LoggerFactory.getLogger(HystrixConfig.class);
public HystrixConfig(){
try {
HystrixConcurrencyStrategy target = new HystrixConcurrencyStrategyCustomize();
HystrixConcurrencyStrategy strategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (strategy instanceof HystrixConcurrencyStrategyCustomize) {
// 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();
if (log.isDebugEnabled()) {
log.debug("Current Hystrix plugins configuration is ["
+ "concurrencyStrategy [" + target + "]," + "eventNotifier ["
+ eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "],"
+ "propertiesStrategy [" + propertiesStrategy + "]," + "]");
log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
}
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(target);
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);
}
}
}
(完)
更多推荐
所有评论(0)