文档章节

spring cloud(第五部)Feign负载均衡原理解析

白中墨
 白中墨
发布于 06/21 13:50
字数 2270
阅读 187
收藏 0

 

注解背后的秘密

  • @EnableFeignClients
    1、作用:
    1.1、用于表示该客户端开启Feign方式调用
    1.2、创建一个关于FeignClient的工厂Bean,这个工厂Bean会通过@FeignClient收集调用信息(服务、接口等),创建动态代理对象
    2、实现:
    2.1、首先温习一下,spring的基础知识,在spring中有两类bean:
    一类就是普通的bean、一类是工厂bean,普通的bean通过xml配置也好,注解配置也好,编写业务逻辑时用到的还是比较多的,对于工厂Bean是起到什么作用呢?
    工厂Bean首先它也是一个Bean,这个Bean我们业务中不会直接用到,它主要是用于生成其他的一些Bean,内部进行了高度封装,非常容易实现配置化的管理,屏蔽了实现细节,下面的代码,这里以orderClient为例,他的代理实现Bean,就是通过工厂Bean生成的

    @RestController
    public class UserRibbonController {
    
        @Autowired
        private OrderClient orderClient;
    
        @RequestMapping("/userorder_feign")
        public Properties testUserOrderByFeign() {        
            return orderClient.testHello();
        }
        
    
    }
    
    
    
    @FeignClient(value = "orderService")
    public interface OrderClient {
        //XmlBeanDefinitionReader tt = null;
        @RequestMapping("/hello")
        public Properties testHello();
    }

    2.2、spring bean的动态注册
    静态注册,无需解释,就是平常大家用到的比较多的场景,写一个Service,进行注解标记或XML配置,而动态注册是解决什么问题,根据客户端的配置动态的,也就是可以按需做bean的注册:
    2.3、实现FeignClientFactoryBean的动态注册
    在这里啰嗦一句,为什么要实现个这玩意?因为需要生成不同的代理类的实现bean,举例如下:
    服务A、服务B、服务C,现在有三个微服务,在A中需要访问B和C的微服务接口,这个时候A中需要引用bClient、cClient、而FeignClientFactoryBean负责生成bClient、cClient的动态代理类,所以FactroryBean如果实现动态注册,根据不同的Client基础数据注册不同的FeignClientFactoryBean,就可以自动生成动态的代理类了
    如何实现的动态注册,请往下看:
    2.3.1、继承ImportBeanDefinitionRegistrar接口,重点实现registerBeanDefinitions方法

    class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
    		ResourceLoaderAware, EnvironmentAware

    2.3.2、import注解导入Registar

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Import(FeignClientsRegistrar.class)
    public @interface EnableFeignClients {

    2.3.3、在注解处采用@Configuration标出
    main函数入口,采用了@SpringBootApplication注解,该注解继承了@SpringBootConfiguration,@SpringBootConfiguration的注解使用了@Configuration做了标注,传递属性如下:
    @Configuration-》@SpringBootConfiguration-》@SpringBootApplication,所以在入口函数处也是有@Configuration标识的,这样就能通过FeignClientRegistrar实现动态注册工厂Bean

  • @FeignClient
    负责标识一个用于业务调用的Client,给FactoryBean提供创建代理对象,提供基础数据(类名、方法、服务名、URI等),作用是提供这些静态配置

动态代理的实现

  • 动态注册FeignClientFactoryBean
    由前文所述,容器初始化时,是由FeignClientsRegistrar完成动态注册bean,它为什么能完成动态注册参考上面的2.3,这里我们具体看下动态注册的过程:FeignClientsRegistrar的核心入口方法是registerBeanDefinitions,这是在它的接口ImportBeanDefinitionRegistrar做的定义:
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata,
    			BeanDefinitionRegistry registry) {
    		registerDefaultConfiguration(metadata, registry);
    		registerFeignClients(metadata, registry);
    	}

    整个过程大概就是,通过配置类,或者package路径做扫描,收集FeignClient的静态信息,每个Client会把他的基本信息,类名、方法、服务名等绑定到FactoryBean上,这样就就具备了生成一个动态代理类的基本条件,代码和分析如下:

    public void registerFeignClients(AnnotationMetadata metadata,
                BeanDefinitionRegistry registry) {
            //创建一个类扫描器
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
    
            Set<String> basePackages;
            //获取EnableFeignClients注解包含的属性
            Map<String, Object> attrs = metadata
                    .getAnnotationAttributes(EnableFeignClients.class.getName());
            //这是一个FeignClient注解过滤器
            AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                    FeignClient.class);
            final Class<?>[] clients = attrs == null ? null
                    : (Class<?>[]) attrs.get("clients");
            //准备出待扫描的路径
            if (clients == null || clients.length == 0) {
                scanner.addIncludeFilter(annotationTypeFilter);
                basePackages = getBasePackages(metadata);
            }
            else {
                final Set<String> clientClasses = new HashSet<>();
                basePackages = new HashSet<>();
                for (Class<?> clazz : clients) {
                    basePackages.add(ClassUtils.getPackageName(clazz));
                    clientClasses.add(clazz.getCanonicalName());
                }
                AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                    @Override
                    protected boolean match(ClassMetadata metadata) {
                        String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                        return clientClasses.contains(cleaned);
                    }
                };
                scanner.addIncludeFilter(
                        new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
            }
    
            for (String basePackage : basePackages) {
            //先扫描出含有@FeignClient注解的类
                Set<BeanDefinition> candidateComponents = scanner
                        .findCandidateComponents(basePackage);
                for (BeanDefinition candidateComponent : candidateComponents) {
                    if (candidateComponent instanceof AnnotatedBeanDefinition) {
                        // verify annotated class is an interface
                        AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                        AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                        //该类必须是接口,做强制校验
                        Assert.isTrue(annotationMetadata.isInterface(),
                                "@FeignClient can only be specified on an interface");
                        //获取该类的所有属性
                        Map<String, Object> attributes = annotationMetadata
                                .getAnnotationAttributes(
                                        FeignClient.class.getCanonicalName());
                        //获取服务名称
                        String name = getClientName(attributes);
                        registerClientConfiguration(registry, name,
                                attributes.get("configuration"));
                        //循环对使用@FeignClient注解的不同的类,做工厂bean注册
                        registerFeignClient(registry, annotationMetadata, attributes);
                    }
                }
            }
        }
    
        private void registerFeignClient(BeanDefinitionRegistry registry,
                AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
            //含有@FeignClient该类的名称
            String className = annotationMetadata.getClassName();
            //创建一个BeanDefinitionBuilder,内含AbstractBeanDefinition,指定待创建的Bean的名字是FeignClientFactoryBean
            BeanDefinitionBuilder definition = BeanDefinitionBuilder
                    .genericBeanDefinition(FeignClientFactoryBean.class);
            validate(attributes);
            //给AbstractBeanDefinition增加属性url,并赋值
            definition.addPropertyValue("url", getUrl(attributes));
            //给AbstractBeanDefinition增加属性path,并赋值,后面的不一一赘述了
            definition.addPropertyValue("path", getPath(attributes));
            String name = getName(attributes);
            definition.addPropertyValue("name", name);
            definition.addPropertyValue("type", className);
            definition.addPropertyValue("decode404", attributes.get("decode404"));
            definition.addPropertyValue("fallback", attributes.get("fallback"));
            definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    
            String alias = name + "FeignClient";
            AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
            boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
    
            beanDefinition.setPrimary(primary);
    
            String qualifier = getQualifier(attributes);
            if (StringUtils.hasText(qualifier)) {
                alias = qualifier;
            }
    
            BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                    new String[] { alias });
            //动态注册
            BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
        }
        //BeanDefinitionReaderUtils.registerBeanDefinition
        public static void registerBeanDefinition(
                BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
                throws BeanDefinitionStoreException {
    
            // Register bean definition under primary name.
            String beanName = definitionHolder.getBeanName();
            //BeanDefinitionRegistry.registerBeanDefinition实现具体的注册,会告知他需要注册的类名、以及AbstractBeanDefinition(已经包含动态BEAN的属性,在上面的代码做的属性添加和赋值)
            registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
            // Register aliases for bean name, if any.
            String[] aliases = definitionHolder.getAliases();
            if (aliases != null) {
                for (String alias : aliases) {
                    registry.registerAlias(beanName, alias);
                }
            }
        }
  • 生成动态代理对象
    FeignClientFactoryBean继承自FactoryBean,通过调用实现类的getObject完成代理类的创建:
      
      @Override
      public Object getObject() throws Exception {
        return getTarget();
      }
      <T> T getTarget() {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);
        //先校验基础属性,基础属性是在FeignClientsRegistrar中给动态Bean,添加属性addPropertyValue时候赋值的
        //URL为空,则使用http://serviceName的方式拼接
        if (!StringUtils.hasText(this.url)) {
          String url;
          if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
          }
          else {
            url = this.name;
          }
          url += cleanPath();
          //创建动态的代理对象,采用JDK动态代理
          return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
              this.name, url));
        }
        //含有url,也就是这个地址不为空,也就是FeignClient的注解接口里是一个绝对地址
        if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
          this.url = "http://" + this.url;
        }
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
          if (client instanceof LoadBalancerFeignClient) {
            // not load balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient)client).getDelegate();
          }
          builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        //返回默认代理对象,该代理方式会直接请求,不会走负载均衡走的是:feign.Client的默认实现Default
        return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
            this.type, this.name, url));
      }
    

    省略掉中间步骤的代码,直接看动态代理生成的逻辑:ReflectiveFeign-》newInstance

      public <T> T newInstance(Target<T> target) {
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
        for (Method method : target.type().getMethods()) {
          if (method.getDeclaringClass() == Object.class) {
            continue;
          } else if(Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
          } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
          }
        }
        //target收到的请求都代理给methodHandler做实现,具体是SynchronousMethodHandler可以直接观看下它的invoke方法
        InvocationHandler handler = factory.create(target, methodToHandler);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
    
        for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
      }
      public Object invoke(Object[] argv) throws Throwable {
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        Retryer retryer = this.retryer.clone();
        while (true) {
          try {
            return executeAndDecode(template);
          } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
              logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
          }
        }
      }
    
      Object executeAndDecode(RequestTemplate template) throws Throwable {
        Request request = targetRequest(template);
    
        if (logLevel != Logger.Level.NONE) {
          logger.logRequest(metadata.configKey(), logLevel, request);
        }
    
        Response response;
        long start = System.nanoTime();
        try {
         //这里Client有两个,一个是默认的,不走负载均衡Feign.Client$Default,一个是走负载均衡通过LoadBalancerFeignClient
          response = client.execute(request, options);
          //省略代码

    如果用户在FeignClient的注解中直接使用了URL,这种方式一般用于调试环境,直接指定一个服务的绝对地址,这种情况下不会走负载均衡,走默认的Client,代码如下:

    
        @Override
        public Response execute(Request request, Options options) throws IOException {
          HttpURLConnection connection = convertAndSend(request, options);
          return convertResponse(connection).toBuilder().request(request).build();
        }

    如果用户在FeignClient中使用了seriveName,那么请求地址将会是http://serviceName,这种情况下是需要走负载均衡的,通过如下代码发现Feign的负载均衡也是基于Ribbon实现:
     

    	public Response execute(Request request, Request.Options options) throws IOException {
    		try {
    			URI asUri = URI.create(request.url());
    			String clientName = asUri.getHost();
    			URI uriWithoutHost = cleanUrl(request.url(), clientName);
    			FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
    					this.delegate, request, uriWithoutHost);
    
    			IClientConfig requestConfig = getClientConfig(options, clientName);
    			return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
    					requestConfig).toResponse();
    		}
    		catch (ClientException e) {
    			IOException io = findIOException(e);
    			if (io != null) {
    				throw io;
    			}
    			throw new RuntimeException(e);
    		}
    	}

    详细的负载策略、列表维持、与服务发现的结合,在这里就不展开叙述了,因为我们在前篇文章已经做了详细介绍,感兴趣的同学可以继续钻研

  • 总结:
    本章的目的是探讨FeignClient是如何加载和运行起来的
    1、FeignClient底层的负载策略是基于Ribbon,最终也是通过http去调用
    2、FeignClient采用了动态注册的方式生成动态的代理实现
    3、从设计上比较灵活,注解加载、动态注册,又考虑了负载和非负载的情况

© 著作权归作者所有

白中墨
粉丝 1
博文 27
码字总数 41497
作品 0
昌平
私信 提问
白话SpringCloud | 第四章:服务消费者(Ribbon+Feign)

前言 上两章节,介绍了下关于注册中心-Eureka的使用及高可用的配置示例,本章节开始,来介绍下服务和服务之间如何进行服务调用的,同时会讲解下几种不同方式的服务调用。 一点知识 在体系中,...

oKong
2018/09/21
868
1
Spring Cloud 实战之服务提供与调用

eureka注册续约流程 启动注册中心 服务提供者生产服务并注册到服务中心中 消费者从服务中心中获取服务并执行 服务提供 1.在spring-cloud-manage下创建一个子项目producer-service pom.xml文件...

技术小能手
2018/10/26
0
0
疯狂Spring Cloud连载(14)Spring Cloud整合Feign

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/29
789
2
杨大仙的程序空间 - 疯狂Spring Cloud连载

疯狂Spring Cloud连载(1)Spring Cloud概述 疯狂Spring Cloud连载(2)搭建开发环境 疯狂Spring Cloud连载(3)Spring Boot简介与配置 疯狂Spring Cloud连载(4)第一个Eureka程序 疯狂Spr...

晨猫
2018/11/26
2
0
拜托!面试请不要再问我Spring Cloud底层原理

目录 一、业务场景介绍 二、Spring Cloud核心组件:Eureka 三、Spring Cloud核心组件:Feign 四、Spring Cloud核心组件:Ribbon 五、Spring Cloud核心组件:Hystrix 六、Spring Cloud核心组件...

Java干货分享
2018/11/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Giraph源码分析(八)—— 统计每个SuperStep中参与计算的顶点数目

作者|白松 目的:科研中,需要分析在每次迭代过程中参与计算的顶点数目,来进一步优化系统。比如,在SSSP的compute()方法最后一行,都会把当前顶点voteToHalt,即变为InActive状态。所以每次...

数澜科技
49分钟前
1
0
Xss过滤器(Java)

问题 最近旧的系统,遇到Xss安全问题。这个系统采用用的是spring mvc的maven工程。 解决 maven依赖配置 <properties><easapi.version>2.2.0.0</easapi.version></properties><dependenci......

亚林瓜子
今天
7
0
Navicat 快捷键

操作 结果 ctrl+q 打开查询窗口 ctrl+/ 注释sql语句 ctrl+shift +/ 解除注释 ctrl+r 运行查询窗口的sql语句 ctrl+shift+r 只运行选中的sql语句 F6 打开一个mysql命令行窗口 ctrl+l 删除一行 ...

低至一折起
今天
7
0
Set 和 Map

Set 1:基本概念 类数组对象, 内部元素唯一 let set = new Set([1, 2, 3, 2, 1]); console.log(set); // Set(3){ 1, 2, 3 } [...set]; // [1, 2, 3] 接收数组或迭代器对象 ...

凌兮洛
今天
1
0
PyTorch入门笔记一

张量 引入pytorch,生成一个随机的5x3张量 >>> from __future__ import print_function>>> import torch>>> x = torch.rand(5, 3)>>> print(x)tensor([[0.5555, 0.7301, 0.5655],......

仪山湖
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部