您好,登錄后才能下訂單哦!
SpringCloud Feign轉發請求頭并防止session失效的解決方法?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
微服務開發中經常有這樣的需求,公司自定義了通用的請求頭,需要在微服務的調用鏈中轉發,比如在請求頭中加入了token,或者某個自定義的信息uniqueId,總之就是自定義的一個鍵值對的東東,A服務調用B服務,B服務調用C服務,這樣通用的東西如何讓他在一個調用鏈中不斷地傳遞下去呢?以A服務為例:
方案1
最傻的辦法,在程序中獲取,調用B的時候再轉發,怎么獲取在Controller中國通過注解獲取,或者通過request對象獲取,這個不難,在請求B服務的時候,通過注解將值放進去即可;簡代碼如下:
獲取: @RequestMapping(value = "/api/test", method = RequestMethod.GET) public String testFun(@RequestParam String name, @RequestHeader("uniqueId") String uniqueId) { if(uniqueId == null ){ return "Must defined the uniqueId , it can not be null"; } log.info(uniqueId, "begin testFun... "); return uniqueId; }
然后A使用Feign調用B服務的時候,傳過去:
@FeignClient(value = "DEMO-SERVICE") public interface CallClient { /** * 訪問DEMO-SERVICE服務的/api/test接口,通過注解將logId傳遞給下游服務 */ @RequestMapping(value = "/api/test", method = RequestMethod.GET) String callApiTest(@RequestParam(value = "name") String name, @RequestHeader(value = "uniqueId") String uniqueId); }
方案弊端:毫無疑問,這方案不好,因為對代碼有侵入,需要開發人員沒次手動的獲取和添加,因此舍棄
方案2
服務通過請求攔截器,在請求從A發送到B之后,在攔截器內將自己需要的東東加到請求頭:
import com.intellif.log.LoggerUtilI; import feign.RequestInterceptor; 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.HttpServletRequest; import java.util.Enumeration; /** * 自定義的請求頭處理類,處理服務發送時的請求頭; * 將服務接收到的請求頭中的uniqueId和token字段取出來,并設置到新的請求頭里面去轉發給下游服務 * 比如A服務收到一個請求,請求頭里面包含uniqueId和token字段,A處理時會使用Feign客戶端調用B服務 * 那么uniqueId和token這兩個字段就會添加到請求頭中一并發給B服務; * * @author mozping * @version 1.0 * @date 2018/6/27 14:13 * @see FeignHeadConfiguration * @since JDK1.8 */ @Configuration public class FeignHeadConfiguration { private final LoggerUtilI logger = LoggerUtilI.getLogger(this.getClass().getName()); @Bean public RequestInterceptor requestInterceptor() { return requestTemplate -> { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs != null) { HttpServletRequest request = attrs.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); /** * 遍歷請求頭里面的屬性字段,將logId和token添加到新的請求頭中轉發到下游服務 * */ if ("uniqueId".equalsIgnoreCase(name) || "token".equalsIgnoreCase(name)) { logger.debug("添加自定義請求頭key:" + name + ",value:" + value); requestTemplate.header(name, value); } else { logger.debug("FeignHeadConfiguration", "非自定義請求頭key:" + name + ",value:" + value + "不需要添加!"); } } } else { logger.warn("FeignHeadConfiguration", "獲取請求頭失敗!"); } } }; } }
網上很多關于這種方法的博文或者資料,大同小異,但是有一個問題,在開啟熔斷器之后,這里的attrs就是null,因為熔斷器默認的隔離策略是thread,也就是線程隔離,實際上接收到的對象和這個在發送給B不是一個線程,怎么辦?有一個辦法,修改隔離策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改為信號量的隔離模式,但是不推薦,因為thread是默認的,而且要命的是信號量模式,熔斷器不生效,比如設置了熔斷時間hystrix.command.default.execution.isolation.semaphore.timeoutInMilliseconds=5000,五秒,如果B服務里面sleep了10秒,非得等到B執行完畢再返回,因此這個方案也不可取;但是有什么辦法可以在默認的Thread模式下讓攔截器拿到上游服務的請求頭?自定義策略:代碼如下:
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容器即可; * * @author mozping * @version 1.0 * @date 2018/7/5 9:08 * @see FeignHystrixConcurrencyStrategyIntellif * @since JDK1.8 */ @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(); } } } }
然后使用默認的熔斷器隔離策略,也可以在攔截器內獲取到上游服務的請求頭信息了
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。