Spring Cloud Alibaba Sentinel 对 Feign 的支持

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud Alibaba Sentinel 对 Feign 的支持相关的知识,希望对你有一定的参考价值。

Spring Cloud Alibaba Sentinel 除了对 RestTemplate 做了支持,同样对于 Feign 也做了支持,如果我们要从 Hystrix 切换到 Sentinel 是非常方便的,下面来介绍下如何对 Feign 的支持以及实现原理。
集成 Feign 使用
spring-cloud-starter-alibaba-sentinel 的依赖还是要加的,如下:

1.  <dependency>
2.      <groupId>org.springframework.cloud</groupId>
3.      <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
4.      <version>0.2.1.RELEASE</version>
5.  </dependency>

需要在配置文件中开启 sentinel 对 feign 的支持:

1.  feign.sentinel.enabled=true
然后我们定义自己需要调用的 Feign Client:
1.  @FeignClient(name = "user-service", fallback = UserFeignClientFallback.class)
2.  public interface UserFeignClient {
3.  
4.      @GetMapping("/user/get")
5.      public String getUser(@RequestParam("id") Long id);
6.  
7.  }

定义 fallback 类 UserFeignClientFallback:

1.  @Component
2.  public class UserFeignClientFallback implements UserFeignClient {
3.  
4.      @Override
5.      public String getUser(Long id) {
6.          return "fallback";
7.      }
8.  
9.  }

测试代码:

1.  @Autowired
2.  private UserFeignClient userFeignClient;
3.  
4.  @GetMapping("/testFeign")
5.  public String testFeign() {
6.      return userFeignClient.getUser(1L);
7.  }

你可以将这个 Client 对应的 user-service 停掉,然后就可以看到输出的内容是 "fallback"
如果要对 Feign 调用做限流,资源名称的规则是精确到接口的,以我们上面定义的接口来分析,资源名称就是GET:http://user-service/user/get,至于资源名称怎么定义的,接下面的源码分析你就知道了
原理分析
技术图片
首先看SentinelFeignAutoConfiguration中如何自动配置:

1.  @Bean
2.  @Scope("prototype")
3.  @ConditionalOnMissingBean
4.  @ConditionalOnProperty(name = "feign.sentinel.enabled")
5.  public Feign.Builder feignSentinelBuilder() {
6.      return SentinelFeign.builder();
7.  }

@ConditionalOnProperty 中 feign.sentinel.enabled 起了决定性作用,这也就是为什么我们需要在配置文件中指定 feign.sentinel.enabled=true。
接下来看 SentinelFeign.builder 里面的实现:
build方法中重新实现了super.invocationHandlerFactory方法,也就是动态代理工厂,构建的是InvocationHandler对象。
build中会获取Feign Client中的信息,比如fallback,fallbackFactory等,然后创建一个SentinelInvocationHandler,SentinelInvocationHandler继承了InvocationHandler。

1.  @Override
2.  public Feign build() {
3.      super.invocationHandlerFactory(new InvocationHandlerFactory() {
4.          @Override
5.          public InvocationHandler create(Target target,
6.                  Map<Method, MethodHandler> dispatch) {
7.              // 得到Feign Client Bean
8.              Object feignClientFactoryBean = Builder.this.applicationContext
9.                      .getBean("&" + target.type().getName());
10.             // 得到fallback类
11.             Class fallback = (Class) getFieldValue(feignClientFactoryBean,
12.                     "fallback");
13.             // 得到fallbackFactory类
14.             Class fallbackFactory = (Class) getFieldValue(feignClientFactoryBean,
15.                     "fallbackFactory");
16.             // 得到调用的服务名称
17.             String name = (String) getFieldValue(feignClientFactoryBean, "name");
18. 
19.             Object fallbackInstance;
20.             FallbackFactory fallbackFactoryInstance;
21.             // 检查 fallback 和 fallbackFactory 属性
22.             if (void.class != fallback) {
23.                 fallbackInstance = getFromContext(name, "fallback", fallback,
24.                                 target.type());
25.                 return new SentinelInvocationHandler(target, dispatch,
26.                                 new FallbackFactory.Default(fallbackInstance));
27.             }
28.             if (void.class != fallbackFactory) {
29.                 fallbackFactoryInstance = (FallbackFactory) getFromContext(name,
30.                         "fallbackFactory", fallbackFactory,
31.                                 FallbackFactory.class);
32.                 return new SentinelInvocationHandler(target, dispatch,
33.                                 fallbackFactoryInstance);
34.             }
35.             return new SentinelInvocationHandler(target, dispatch);
36.         }
37. 
38.         // 省略部分代码                
39.     });
40. 
41.     super.contract(new SentinelContractHolder(contract));
42.     return super.build();
43. }

SentinelInvocationHandler中的invoke方法里面进行熔断限流的处理。

1.  // 得到资源名称(GET:http://user-service/user/get)
2.  String resourceName = methodMetadata.template().method().toUpperCase() + ":"
3.                      + hardCodedTarget.url() + methodMetadata.template().url();
4.  Entry entry = null;
5.  try {
6.      ContextUtil.enter(resourceName);
7.      entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
8.      result = methodHandler.invoke(args);
9.  }
10. catch (Throwable ex) {
11.     // fallback handle
12.     if (!BlockException.isBlockException(ex)) {
13.         Tracer.trace(ex);
14.     }
15.     if (fallbackFactory != null) {
16.         try {
17.             // 回退处理
18.             Object fallbackResult = fallbackMethodMap.get(method)
19.                     .invoke(fallbackFactory.create(ex), args);
20.             return fallbackResult;
21.         }
22.         catch (IllegalAccessException e) {
23.             // shouldn‘t happen as method is public due to being an interface
24.             throw new AssertionError(e);
25.         }
26.         catch (InvocationTargetException e) {
27.             throw new AssertionError(e.getCause());
28.         }
29.     }
30.     // 省略.....
31. }

总结br/>总的来说,这些框架的整合都有相似之处,前面讲RestTemplate的整合其实和Ribbon中的@LoadBalanced原理差不多。
这次的Feign的整合其实我们从其他框架的整合也是可以参考出来的,最典型的就是Hystrix了。
我们想下Hystrix要对Feign的调用进行熔断处理,那么肯定是将Feign的请求包装了HystrixCommand。
同样的道理,我们只要找到Hystrix是如何包装的,无非就是将Hystrix的代码换成Sentinel的代码而已。
InvocationHandlerFactory是用于创建动态代理的工厂,有默认的实现,也有Hystrix的实现feign.hystrix.HystrixFeign。

1.  Feign build(final FallbackFactory<?> nullableFallbackFactory) {
2.        super.invocationHandlerFactory(new InvocationHandlerFactory() {
3.          @Override public InvocationHandler create(Target target,
4.              Map<Method, MethodHandler> dispatch) {
5.            return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
6.          }
7.        });
8.        super.contract(new HystrixDelegatingContract(contract));
9.        return super.build();
10. }

上面这段代码是不是跟Sentinel包装的类似,不同的是Sentinel构造的是SentinelInvocationHandler ,Hystrix构造的是HystrixInvocationHandle。在HystrixInvocationHandler的invoke方法中进行HystrixCommand的包装。
加入星球特权

1、从前端到后端玩转Spring Cloud
2、实战分库分表中间件Sharding-JDBC
3、实战分布式任务调度框架Elastic Job
4、配置中心Apollo实战
5、高并发解决方案之缓存
6、更多课程等你来解锁,20+课程

技术图片

尹吉欢
我不差钱啊
喜欢作者


以上是关于Spring Cloud Alibaba Sentinel 对 Feign 的支持的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Alibaba环境搭建

Spring Cloud Alibaba全家桶——Spring Cloud Alibaba介绍

Spring Cloud Alibaba系列教程——Spring Cloud Alibaba开篇

spring boot 整合spring cloud alibaba

spring boot 整合spring cloud alibaba

深度剖析Spring Cloud Alibaba系列——如何兼容Spring Cloud