在使用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:

HystrixPlugins.getInstance().registerEventNotifier(ACustomHystrixEventNotifierDefaultStrategy.getInstance());

 

这样,在自定义的并发策略的基础上,再在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);
        }
    }
}

(完)

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐