SpringCloud Feign
在 Spring Cloud 中 微服务之间的调用会用到Feign,但是在默认情况下,Feign 调用远程服务存在Header请求头丢失问题。

解决方案

首先需要写一个 Feign请求拦截器,通过实现RequestInterceptor接口,完成对所有的Feign请求,传递请求头和请求参数。

Feign 请求拦截器

  1. public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
  2. private static final Logger logger = LoggerFactory.getLogger(FeignBasicAuthRequestInterceptor.class);
  3. @Override
  4. public void apply(RequestTemplate requestTemplate) {
  5. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
  6. .getRequestAttributes();
  7. HttpServletRequest request = attributes.getRequest();
  8. Enumeration<String> headerNames = request.getHeaderNames();
  9. if (headerNames != null) {
  10. while (headerNames.hasMoreElements()) {
  11. String name = headerNames.nextElement();
  12. String values = request.getHeader(name);
  13. requestTemplate.header(name, values);
  14. }
  15. }
  16. Enumeration<String> bodyNames = request.getParameterNames();
  17. StringBuffer body =new StringBuffer();
  18. if (bodyNames != null) {
  19. while (bodyNames.hasMoreElements()) {
  20. String name = bodyNames.nextElement();
  21. String values = request.getParameter(name);
  22. body.append(name).append("=").append(values).append("&");
  23. }
  24. }
  25. if(body.length()!=0) {
  26. body.deleteCharAt(body.length()-1);
  27. requestTemplate.body(body.toString());
  28. logger.info("feign interceptor body:{}",body.toString());
  29. }
  30. }
  31. }

配置 让所有 FeignClient,使用 FeignBasicAuthRequestInterceptor

  1. feign:
  2. client:
  3. config:
  4. default:
  5. connectTimeout: 5000
  6. readTimeout: 5000
  7. loggerLevel: basic
  8. requestInterceptors: com.leparts.config.FeignBasicAuthRequestInterceptor

也可以配置让 某个 FeignClient 使用这个 FeignBasicAuthRequestInterceptor

  1. feign:
  2. client:
  3. config:
  4. xxxx: # 远程服务名
  5. connectTimeout: 5000
  6. readTimeout: 5000
  7. loggerLevel: basic
  8. requestInterceptors: com.leparts.config.FeignBasicAuthRequestInterceptor

经过测试,上面的解决方案可以正常的使用;但是出现了新的问题。
在转发Feign的请求头的时候,如果开启了Hystrix,Hystrix的默认隔离策略是Thread(线程隔离策略),因此转发拦截器内是无法获取到请求的请求头信息的。
可以修改默认隔离策略为信号量模式:

  1. hystrix.command.default.execution.isolation.strategy=SEMAPHORE

但信号量模式不是官方推荐的隔离策略;另一个解决方法就是自定义Hystrix的隔离策略。

自定义策略

HystrixConcurrencyStrategy 是提供给开发者去自定义hystrix内部线程池及其队列,还提供了包装callable的方法,以及传递上下文变量的方法。所以可以继承了HystrixConcurrencyStrategy,用来实现了自己的并发策略。

  1. @Component
  2. public class FeignHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
  3. private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategy.class);
  4. private HystrixConcurrencyStrategy delegate;
  5. public FeignHystrixConcurrencyStrategy() {
  6. try {
  7. this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
  8. if (this.delegate instanceof FeignHystrixConcurrencyStrategy) {
  9. // Welcome to singleton hell...
  10. return;
  11. }
  12. HystrixCommandExecutionHook commandExecutionHook =
  13. HystrixPlugins.getInstance().getCommandExecutionHook();
  14. HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier();
  15. HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher();
  16. HystrixPropertiesStrategy propertiesStrategy =
  17. HystrixPlugins.getInstance().getPropertiesStrategy();
  18. this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy);
  19. HystrixPlugins.reset();
  20. HystrixPlugins instance = HystrixPlugins.getInstance();
  21. instance.registerConcurrencyStrategy(this);
  22. instance.registerCommandExecutionHook(commandExecutionHook);
  23. instance.registerEventNotifier(eventNotifier);
  24. instance.registerMetricsPublisher(metricsPublisher);
  25. instance.registerPropertiesStrategy(propertiesStrategy);
  26. } catch (Exception e) {
  27. log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
  28. }
  29. }
  30. private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
  31. HystrixMetricsPublisher metricsPublisher,
  32. HystrixPropertiesStrategy propertiesStrategy) {
  33. if (log.isDebugEnabled()) {
  34. log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy ["
  35. + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher ["
  36. + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]");
  37. log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
  38. }
  39. }
  40. @Override
  41. public <T> Callable<T> wrapCallable(Callable<T> callable) {
  42. RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  43. return new WrappedCallable<>(callable, requestAttributes);
  44. }
  45. @Override
  46. public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
  47. HystrixProperty<Integer> corePoolSize,
  48. HystrixProperty<Integer> maximumPoolSize,
  49. HystrixProperty<Integer> keepAliveTime,
  50. TimeUnit unit, BlockingQueue<Runnable> workQueue) {
  51. return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime,
  52. unit, workQueue);
  53. }
  54. @Override
  55. public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
  56. HystrixThreadPoolProperties threadPoolProperties) {
  57. return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
  58. }
  59. @Override
  60. public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
  61. return this.delegate.getBlockingQueue(maxQueueSize);
  62. }
  63. @Override
  64. public <T> HystrixRequestVariable<T> getRequestVariable(HystrixRequestVariableLifecycle<T> rv) {
  65. return this.delegate.getRequestVariable(rv);
  66. }
  67. static class WrappedCallable<T> implements Callable<T> {
  68. private final Callable<T> target;
  69. private final RequestAttributes requestAttributes;
  70. WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
  71. this.target = target;
  72. this.requestAttributes = requestAttributes;
  73. }
  74. @Override
  75. public T call() throws Exception {
  76. try {
  77. RequestContextHolder.setRequestAttributes(requestAttributes);
  78. return target.call();
  79. } finally {
  80. RequestContextHolder.resetRequestAttributes();
  81. }
  82. }
  83. }
  84. }

至此,Feign调用丢失请求头的问题就解决了 。