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 的包装。
欢迎加入我的知识星球,一起交流技术,免费学习猿天地的课程(http://cxytiandi.com/course)
PS:目前星球中正在星主的带领下组队学习 Sentinel,等你哦!