标签:meta ret 精确 href access 不同的 特权 build 调度框架
Spring Cloud Alibaba Sentinel 除了对 RestTemplate 做了支持,同样对于 Feign 也做了支持,如果我们要从 Hystrix 切换到 Sentinel 是非常方便的,下面来介绍下如何对 Feign 的支持以及实现原理。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. }
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 的支持
标签:meta ret 精确 href access 不同的 特权 build 调度框架
原文地址:https://blog.51cto.com/14888386/2515783