Spring Cloud系列(三十二)Feign丟失Cookie和Header資訊的問題(Finchley.RC2版本)
阿新 • • 發佈:2019-01-08
當通過Feign呼叫其他的服務時,Feign是不會帶上當前請求的Cookie資訊和頭資訊的,而我們一般都會在Cookie或者請求頭裡帶著一些重要的資訊,如cookieid,token等。那麼我們怎麼將這些資訊傳遞到其他的服務裡呢?
方式一
最傻的辦法,在程式中獲取,呼叫其他服務的時候通過引數的方式注入。
@RequestMapping(value = "/api/test", method = RequestMethod.GET) public String test(@RequestParam String name, @RequestHeader("token") String token) { if(token== null ){ return "Must defined the token, it can not be null"; } return token; }
呼叫其他服務時,傳過去
@FeignClient(value = "DEMO-SERVICE") public interface CallClient { /** * 訪問DEMO-SERVICE服務的/api/test介面,通過註解將token傳遞給下游服務 */ @RequestMapping(value = "/api/test", method = RequestMethod.GET) String callApiTest(@RequestParam(value = "name") String name, @RequestHeader(value = "token") String token); }
毫無疑問,這方案不好,因為對程式碼有侵入,需要開發人員沒次手動的獲取和新增,因此捨棄。
方式二
服務通過請求攔截器,在請求從A服務傳送到B服務之後,在攔截器內將自己需要的東西加到請求頭
package com.jnlc.analyzer.website.config.feign; import feign.RequestInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; /** * 自定義的請求頭處理類,處理服務傳送時的請求頭; * 將服務接收到的請求頭中的uniqueId和token欄位取出來,並設定到新的請求頭裡面去轉發給下游服務 * 比如A服務收到一個請求,請求頭裡麵包含uniqueId和token欄位,A處理時會使用Feign客戶端呼叫B服務 * 那麼uniqueId和token這兩個欄位就會新增到請求頭中一併發給B服務; * * @author Curise * @create 2018/11/20 * @since 1.0.0 */ @Configuration public class FeignHeadConfiguration { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs != null) { HttpServletRequest request = attrs.getRequest(); // 如果在Cookie內通過如下方式取 Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { requestTemplate.header(cookie.getName(), cookie.getValue()); } } else { logger.warn("FeignHeadConfiguration", "獲取Cookie失敗!"); } // 如果放在header內通過如下方式取 Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); /** * 遍歷請求頭裡面的屬性欄位,將jsessionid新增到新的請求頭中轉發到下游服務 * */ if ("jsessionid".equalsIgnoreCase(name)) { logger.debug("新增自定義請求頭key:" + name + ",value:" + value); requestTemplate.header(name, value); } else { logger.debug("FeignHeadConfiguration", "非自定義請求頭key:" + name + ",value:" + value + "不需要新增!"); } } } else { logger.warn("FeignHeadConfiguration", "獲取請求頭失敗!"); } } }; } }
注意這樣新增後要在Header裡取
// 其他服務取Feign新增到Header裡的值
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
if ("jsessionid".equalsIgnoreCase(name)) {
return value;
}
}
}
return null;
這樣仍然有個問題:在開啟熔斷器之後,方法裡的attrs是null,因為熔斷器預設的隔離策略是thread,也就是執行緒隔離,實際上接收到的物件和這個在傳送給B不是一個執行緒,怎麼辦?有一個辦法,修改隔離策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改為訊號量的隔離模式,但是不推薦,因為thread是預設的,而且要命的是訊號量模式,熔斷器不生效,比如設定了熔斷時間。
另一個辦法:重寫Feign的隔離策略
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 自定義Feign的隔離策略;
* 在轉發Feign的請求頭的時候,如果開啟了Hystrix,Hystrix的預設隔離策略是Thread(執行緒隔離策略),因此轉發攔截器內是無法獲取到請求的請求頭資訊的,可以修改預設隔離策略為訊號量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,這樣的話轉發執行緒和請求執行緒實際上是一個執行緒,這並不是最好的解決方法,訊號量模式也不是官方最為推薦的隔離策略;另一個解決方法就是自定義Hystrix的隔離策略,思路是將現有的併發策略作為新併發策略的成員變數,在新併發策略中,返回現有併發策略的執行緒池、Queue;將策略加到Spring容器即可;
*
*/
@Component
public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy {
private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif.class);
private HystrixConcurrencyStrategy delegate;
public FeignHystrixConcurrencyStrategyIntellif() {
try {
this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) {
// 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();
}
}
}
}
然後使用預設的熔斷器隔離策略,也可以在攔截器內獲取到上游服務的請求頭資訊了;