Feign Hystrix微服务调用Session传播

不念不忘少年蓝@ 2022-05-11 13:58 394阅读 0赞

在使用SpringCloud来构建微服务时,服务和服务之间的调用非常频繁,服务之间调用通常用feign和Hystrix结合来使用,当使用上游微服务调用下游微服务时,怎么将上游服务的请求信息传递到下游服务中去呢?Feign提供了Interceptor来设置请求下游服务的header等信息,如下:

  1. @Component
  2. public class FeignRequestInterceptor implements RequestInterceptor {
  3. @Value("${spring.application.name}")
  4. private String serviceName;
  5. @Override
  6. public void apply(RequestTemplate requestTemplate) {
  7. requestTemplate.header("from-service", serviceName);
  8. }
  9. }

这样可以设置系统配置的数据到http请求header中,同样可以设置数据库的数据或者是其他服务提供的数据。但是要设置请求上游服务的请求header的话,就比较麻烦了,有人提供了以下解决方案

  1. @Component
  2. public class FeignRequestInterceptor implements RequestInterceptor {
  3. @Value("${spring.application.name}")
  4. private String serviceName;
  5. @Override
  6. public void apply(RequestTemplate requestTemplate) {
  7. requestTemplate.header("from-service", serviceName);
  8. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  9. if(null != attributes){
  10. HttpServletRequest request = attributes.getRequest();
  11. String sessionId = request.getHeader("SESSIONID");
  12. if(!StringUtils.isEmpty(sessionId)){
  13. requestTemplate.header("SESSIONID", sessionId);
  14. }
  15. }
  16. }
  17. }

这个方案在只使用feign的情况下没有问题,但是在feign和hystrix结合使用时,却有了问题,调试发现RequestContextHolder.getRequestAttributes()每次返回都是空,这是为什么呢?

查看RequestContextHolder的源码发现,RequestContextHolder将ServletRequestAttribute存放在了线程ThreadLocal中,而Hystrix默认的是多线程的,不是同一个线程,ThreadLocal中的数据自然拿不到,这个时候怎么解决这个问题呢?

1,调整hystrix执行的隔离策略。

给hystrix增加配置

  1. hystrix:
  2. command:
  3. default:
  4. execution:
  5. isolation:
  6. 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方法提供了一个在请求执行前覆写的机会,如下

  1. /**
  2. * Provides an opportunity to wrap/decorate a {@code Callable<T>} before execution.
  3. * <p>
  4. * This can be used to inject additional behavior such as copying of thread state (such as {@link ThreadLocal}).
  5. * <p>
  6. * <b>Default Implementation</b>
  7. * <p>
  8. * Pass-thru that does no wrapping.
  9. *
  10. * @param callable
  11. * {@code Callable<T>} to be executed via a {@link ThreadPoolExecutor}
  12. * @return {@code Callable<T>} either as a pass-thru or wrapping the one given
  13. */
  14. public <T> Callable<T> wrapCallable(Callable<T> callable) {
  15. return callable;
  16. }

重写wrapCallable方法,添加自定义逻辑

  1. @Component
  2. public class HystrixConcurrencyStrategyCustomize extends HystrixConcurrencyStrategy {
  3. public <T> Callable<T> wrapCallable(Callable<T> callable) {
  4. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  5. HystrixCustomizeCallable hystrixCustomizeCallable = new HystrixCustomizeCallable(attributes, callable);
  6. return hystrixCustomizeCallable;
  7. }
  8. }
  9. class HystrixCustomizeCallable<T> implements Callable<T>{
  10. private ServletRequestAttributes attributes;
  11. private Callable<T> callable;
  12. public HystrixCustomizeCallable(ServletRequestAttributes attributes, Callable<T> callable){
  13. this.attributes = attributes;
  14. this.callable = callable;
  15. }
  16. @Override
  17. public T call() throws Exception {
  18. try{
  19. if(null != this.attributes){
  20. RequestContextHolder.setRequestAttributes(this.attributes);
  21. }
  22. return this.callable.call();
  23. }finally {
  24. RequestContextHolder.resetRequestAttributes();
  25. }
  26. }
  27. }

使策略生效

自定义的策略如何在编写后生效呢?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:

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

 

这样,在自定义的并发策略的基础上,再在Feign的RequestInterceptor中为请求添加header,将上游请求的session信息传递到下游微服务。

但是这样的解决方案在应用加入actuator是依然后问题。

  1. Caused by: java.lang.IllegalStateException: Another strategy was already registered.

提示已经注册策略,不能重复注册,参考下Sleuth以及Spring Security的实现,先删除已经注册的策略,重新注册新的策略,如下:

  1. @Configuration
  2. public class HystrixConfig {
  3. public static final Logger log = LoggerFactory.getLogger(HystrixConfig.class);
  4. public HystrixConfig(){
  5. try {
  6. HystrixConcurrencyStrategy target = new HystrixConcurrencyStrategyCustomize();
  7. HystrixConcurrencyStrategy strategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
  8. if (strategy instanceof HystrixConcurrencyStrategyCustomize) {
  9. // Welcome to singleton hell...
  10. return;
  11. }
  12. HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins
  13. .getInstance().getCommandExecutionHook();
  14. HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
  15. .getEventNotifier();
  16. HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
  17. .getMetricsPublisher();
  18. HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
  19. .getPropertiesStrategy();
  20. if (log.isDebugEnabled()) {
  21. log.debug("Current Hystrix plugins configuration is ["
  22. + "concurrencyStrategy [" + target + "]," + "eventNotifier ["
  23. + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "],"
  24. + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
  25. log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
  26. }
  27. HystrixPlugins.reset();
  28. HystrixPlugins.getInstance().registerConcurrencyStrategy(target);
  29. HystrixPlugins.getInstance()
  30. .registerCommandExecutionHook(commandExecutionHook);
  31. HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
  32. HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
  33. HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
  34. }
  35. catch (Exception e) {
  36. log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
  37. }
  38. }
  39. }

(完)

发表评论

表情:
评论列表 (有 0 条评论,394人围观)

还没有评论,来说两句吧...

相关阅读