Spring Cloud Alibaba Sentinel 除了对 RestTemplate 做了支持,同样对于 Feign 也做了支持,如果我们要从 Hystrix 切换到 Sentinel 是非常方便的,下面来介绍下如何对 Feign 的支持以及实现原理。
1. 集成 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:
@Componentpublic class UserFeignClientFallback implements UserFeignClient {@Overridepublic String getUser(Long id) {return "fallback";}}
测试代码:
@Autowiredprivate UserFeignClient userFeignClient;@GetMapping("/testFeign")public String testFeign() {return userFeignClient.getUser(1L);}
你可以将这个 Client 对应的 user-service 停掉,然后就可以看到输出的内容是 “fallback”
如果要对 Feign 调用做限流,资源名称的规则是精确到接口的,以我们上面定义的接口来分析,资源名称就是GET:http://user-service/user/get,至于资源名称怎么定义的,接下面的源码分析你就知道了。
2. 原理分析
首先看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。
@Overridepublic Feign build() {super.invocationHandlerFactory(new InvocationHandlerFactory() {@Overridepublic InvocationHandler create(Target target,Map<Method, MethodHandler> dispatch) {// 得到Feign Client BeanObject 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 handleif (!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 interfacethrow new AssertionError(e);}catch (InvocationTargetException e) {throw new AssertionError(e.getCause());}}// 省略.....}
3. 总结
总的来说,这些框架的整合都有相似之处,前面讲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的包装。
