• Spring Cloud Alibaba Sentinel对Feign的支持


    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,至于资源名称怎么定义的,接下面的源码分析你就知道了。

    原理分析

    feign包里的代码就是对feign支持的代码

    首先看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,等你哦!

    微信扫码加入猿天地知识星球

    猿天地

  • 相关阅读:
    .NET:如何应对边界异常?
    由极点五笔到QQ五笔的转换想到的
    【转】七个受用一生的心理寓言
    proverb: better late than never
    背单词软件supermemo从2006版升级到2008版
    今天解决一个异步刷新TreeView的问题
    开始尝试换成Windows 7了
    southpaw
    用supermemo背单词达到6000词条
    Averse, Adverse
  • 原文地址:https://www.cnblogs.com/yinjihuan/p/10867622.html
Copyright © 2020-2023  润新知