Sentinel對Feign的支持


原文鏈接:https://segmentfault.com/a/1190000019183725

Spring Cloud Alibaba Sentinel 除了對 RestTemplate 做了支持,同樣對於 Feign 也做了支持,如果我們要從 Hystrix 切換到 Sentinel 是非常方便的,下面來介紹下如何對 Feign 的支持以及實現原理。

集成 Feign 使用

spring-cloud-starter-alibaba-sentinel 的依賴還是要加的,如下:

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>0.2.1.RELEASE</version> </dependency>

需要在配置文件中開啟 sentinel 對 feign 的支持:

feign.sentinel.enabled=true

然后我們定義自己需要調用的 Feign Client:

@FeignClient(name = "user-service", fallback = UserFeignClientFallback.class) public interface UserFeignClient { @GetMapping("/user/get") public String getUser(@RequestParam("id") Long id); }

定義 fallback 類 UserFeignClientFallback:

@Component public class UserFeignClientFallback implements UserFeignClient { @Override public String getUser(Long id) { return "fallback"; } }

測試代碼:

@Autowired private UserFeignClient userFeignClient; @GetMapping("/testFeign") public String testFeign() { return userFeignClient.getUser(1L); }

你可以將這個 Client 對應的 user-service 停掉,然后就可以看到輸出的內容是 "fallback"

如果要對 Feign 調用做限流,資源名稱的規則是精確到接口的,以我們上面定義的接口來分析,資源名稱就是GET:http://user-service/user/get,至於資源名稱怎么定義的,接下面的源碼分析你就知道了。

原理分析

feign包里的代碼就是對feign支持的代碼

首先看SentinelFeignAutoConfiguration中如何自動配置:

@Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.sentinel.enabled") public Feign.Builder feignSentinelBuilder() { return SentinelFeign.builder(); }

@ConditionalOnProperty 中 feign.sentinel.enabled 起了決定性作用,這也就是為什么我們需要在配置文件中指定 feign.sentinel.enabled=true

接下來看 SentinelFeign.builder 里面的實現:

build方法中重新實現了super.invocationHandlerFactory方法,也就是動態代理工廠,構建的是InvocationHandler對象。

build中會獲取Feign Client中的信息,比如fallback,fallbackFactory等,然后創建一個SentinelInvocationHandler,SentinelInvocationHandler繼承了InvocationHandler。

@Override public Feign build() { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { // 得到Feign Client Bean Object feignClientFactoryBean = Builder.this.applicationContext .getBean("&" + target.type().getName()); // 得到fallback類 Class fallback = (Class) getFieldValue(feignClientFactoryBean, "fallback"); // 得到fallbackFactory類 Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean, "fallbackFactory"); // 得到調用的服務名稱 String name = (String) getFieldValue(feignClientFactoryBean, "name"); Object fallbackInstance; FallbackFactory fallbackFactoryInstance; // 檢查 fallback 和 fallbackFactory 屬性 if (void.class != fallback) { fallbackInstance = getFromContext(name, "fallback", fallback, target.type()); return new SentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance)); } if (void.class != fallbackFactory) { fallbackFactoryInstance = (FallbackFactory) getFromContext(name, "fallbackFactory", fallbackFactory, FallbackFactory.class); return new SentinelInvocationHandler(target, dispatch, fallbackFactoryInstance); } return new SentinelInvocationHandler(target, dispatch); } // 省略部分代碼 }); super.contract(new SentinelContractHolder(contract)); return super.build(); }

SentinelInvocationHandler中的invoke方法里面進行熔斷限流的處理。

// 得到資源名稱(GET:http://user-service/user/get) String resourceName = methodMetadata.template().method().toUpperCase() + ":" + hardCodedTarget.url() + methodMetadata.template().url(); Entry entry = null; try { ContextUtil.enter(resourceName); entry = SphU.entry(resourceName, EntryType.OUT, 1, args); result = methodHandler.invoke(args); } catch (Throwable ex) { // fallback handle if (!BlockException.isBlockException(ex)) { Tracer.trace(ex); } if (fallbackFactory != null) { try { // 回退處理 Object fallbackResult = fallbackMethodMap.get(method) .invoke(fallbackFactory.create(ex), args); return fallbackResult; } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an interface throw new AssertionError(e); } catch (InvocationTargetException e) { throw new AssertionError(e.getCause()); } } // 省略..... }

總結

總的來說,這些框架的整合都有相似之處,前面講RestTemplate的整合其實和Ribbon中的@LoadBalanced原理差不多,這次的Feign的整合其實我們從其他框架的整合也是可以參考出來的,最典型的就是Hystrix了。

我們想下Hystrix要對Feign的調用進行熔斷處理,那么肯定是將Feign的請求包裝了HystrixCommand。同樣的道理,我們只要找到Hystrix是如何包裝的,無非就是將Hystrix的代碼換成Sentinel的代碼而已。

InvocationHandlerFactory是用於創建動態代理的工廠,有默認的實現,也有Hystrix的實現feign.hystrix.HystrixFeign。

Feign build(final FallbackFactory<?> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super.contract(new HystrixDelegatingContract(contract)); return super.build(); }

上面這段代碼是不是跟Sentinel包裝的類似,不同的是Sentinel構造的是SentinelInvocationHandler ,Hystrix構造的是HystrixInvocationHandle。在HystrixInvocationHandler的invoke方法中進行HystrixCommand的包裝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM