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,至於資源名稱怎么定義的,接下面的源碼分析你就知道了。
原理分析
首先看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的包裝。