Spring Cloud OpenFeign 原理

缘由

Spring Cloud 作为微服务框架,工作当中必用。原因也很简单,Spring cloud 依托Springboot,背靠Spring Framework,又有Eureka 和 Alibaba 两个大厂的支持,大多数的互联网公司会优先选择以此作为后端开发框架。在服务调用方面,OpenFeign 和 Dubbo 都是十分优秀的组件,OpenFeign 由 Spring cloud 推出,其使用十分流行。

简述

Spring Cloud OpenFeign 是在feign的基础上,与Spring Cloud集成,做出了很多的改变,比如SpringMvcContract,融入服务发现、负载均衡等等。对常用技术进行原理分析,提升技术积累的同时,还能了解OpenFeign的瓶颈,为后续的调优工作做准备。

正文

OpenFeign 使用Http协议调用服务接口,提供简洁的RPC调用,使用少量的注解就可以达到接近本地方法调用的效果。

使用

  1. 引入pom 依赖

         
             org.springframework.cloud
             spring-cloud-starter-openfeign
         
  2. 功能开启

    @EnableFeignClients({"com.lake.feign.uaa"})
    @SpringBootApplication
    public class ProductApplication {
    
     public static void main(String[] args) {
         SpringApplication.run(ProductApplication.class, args);
     }
    }
    
  3. 定义feign接口

    @FeignClient(value = "uaa", fallback = UaaEchoFallback.class)
    public interface UaaEchoFeign {
    
     @GetMapping(value = "/api/feign/uaa/echo", consumes = MediaType.APPLICATION_JSON_VALUE)
     ResultFeignBean echo(@RequestParam(value = "echo") String echo);
    
    }
  4. 其他服务需要对提供对应的Http接口。
  5. 服务当中引入UaaEchoFeign接口,调用本地方法就可以完成RPC调用。

OpenFeign的使用非常的方便,相比于单体应用开发,仅仅是多使用了一些注解。但是背后的技术原理值得分析一番,如此便捷的技术背后的技术也很值得大家学习。

原理

OpenFeign 的大体工作流程如下:

  1. 开启OepnFeign,扫描Feign接口,注册到SpringIoc。
  2. Bean创建时生成代理类。
  3. RPC调用时,执行封装Http请求。
  4. 使用服务发现获取可以访问的服务实例。
  5. 使用负载均衡挑选服务实例地址。
  6. 发起调用。
  7. 对调用结果进行解析处理。

Feign功能开启

@EnableFeignClients 注解开启了Feign功能的序幕。

  1. 注释说明此注解会扫描@FeignClient注解的类。
  2. 使用@EnableFeignClients注解时可以指定需要扫描的package 或者 class。
  3. 指定自定义的配置类。
  4. 指定@FeignClient标注的类,不需要再去执行扫描。
  5. @Import(FeignClientsRegistrar.class),FeignClientsRegistrar 拿到开启的接力棒。

@EnableFeignClients 注解在classpath中,会在Springboot启动时,扫描到此注解并解析其中的@Import注解导入的类。


FeignClientsRegistrar 实现ImportBeanDefinitionRegistrar接口,重写registerBeanDefinitions()。

  1. 加载@EnableFeignClients中指定的配置类
  2. 扫描@FeignClient注解类,或者注册@EnableFeignClients中指定的FeignClient类
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        // 加载@EnableFeignClients中指定的配置类
        registerDefaultConfiguration(metadata, registry);
        // 扫描@FeignClient注解类 或 注册@EnableFeignClients中指定的FeignClient类
        registerFeignClients(metadata, registry);
    }
public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {

        LinkedHashSet candidateComponents = new LinkedHashSet<>();
        Map attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);

        // 获取@EnableFeignClients中指定的FeignClient类
        final Class[] clients = attrs == null ? null
                : (Class[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            // 扫描@FeignClient注解类
            ClassPathScanningCandidateComponentProvider scanner = getScanner();
            scanner.setResourceLoader(this.resourceLoader);
            scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
            Set basePackages = getBasePackages(metadata);
            for (String basePackage : basePackages) {
                candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
            }
        }
        else {
            for (Class clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }

        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 attributes = annotationMetadata
                        .getAnnotationAttributes(FeignClient.class.getCanonicalName());

                String name = getClientName(attributes);
                // 注册FeignClient注解中指定的配置类
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));
                // 注册FeignClient类,beanDefinition中指定使用 FeignClientFactoryBean 类
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }

Feign功能的开启仅通过一个FeignClientsRegistrar,扫描FeignClient类,解析FeignClient注解中指定的属性,封装到 BeanDefinition,作为一个FeignClientFactoryBean,注册到IOC当中。
将接力棒传到 FeignClientFactoryBean 中,Bean创建时执行FactoryBean的getObject获取Feign接口代理类。

生成Feign代理

FeignClientFactoryBean.getObject() 会生成Feign接口代理类。

spring cloud openFeign starter中的 spring.factories中,EnableAutoConfiguration接口的实现类 FeignAutoConfiguration,会注册一个 FeignContext Bean,FeignContext 继承 NamedContextFactory,NamedContextFactory 实现 ApplicationContextAware,应用执行Aware回调时会设置NamedContextFactory的 parent,创建FeignContext 是就会将当前应用设置为父context。

@FeignClient注解中的value值是服务名,应用会为每个服务创建一个FeignContext,Feign 配置时支持服务级别的配置,比如为用户服务设置一个超时时间之类的。
Feign代理类创建时,从FeignContext中获取Feign.Builder Encoder Decoder Contract Client Targetar... 单例Bean,然后根据全局配置或者服务级别的Feign配置对Feign.Builder进行定制。

client 和 targetar 有多个实现,LoadBalancerFeignClient 具备负载均衡功能,DefaultTargeter feign的默认实现。Hystrix 和 seata 引入的同时也会引入新的实现。

Feign.Builder.build() 完成对Feign对象ReflectiveFeign的创建。

 public Feign build() {
      // ... 省略代码 client 参数是 LoadBalancerFeignClient
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
 }

ReflectiveFeign.newInstance() 创建Feign接口的代理类。

  public  T newInstance(Target target) {
    Map nameToHandler = targetToHandlersByName.apply(target);
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();

    // feign 接口类中的方法转换为 DefaultMethodHandler 或者 SynchronousMethodHandler
    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)));
      }
    }
    // 创建代理对象 ReflectiveFeign.FeignInvocationHandler 
    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;
  }

Feign代理对象 ReflectiveFeign.FeignInvocationHandler 生成到此已完成,其中使用到 父子Context 、 builder 构造、jdk反射、 FactoryBean 技术。过程还是比较精彩的。

RPC调用过程

ReflectiveFeign.FeignInvocationHandler中的invoke() 就是RPC的工作过程。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // ... 省略代码
      // 方法名和 DefaultMethodHandler 的映射。
      return dispatch.get(method).invoke(args);
    }

将接力棒交给 SynchronousMethodHandler.invoke(),feign接口方法到Http请求的转化。

  public Object invoke(Object[] argv) throws Throwable {
    // template 构建
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        continue;
      }
    }
  }
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 准备请求
    Request request = targetRequest(template);
    Response response;
    long start = System.nanoTime();
    try {
      // 执行请求
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      // 请求响应
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    if (decoder != null)
      // Decoder 可自定义
      return decoder.decode(response, metadata.returnType());

    CompletableFuture resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      return resultFuture.join();
    } catch (CompletionException e) {
    }
  } 
 

集成Ribbon

准备

Open Feign 的负载均衡能力需要借助于Ribbon实现,通过将 httpClient 委托为 LoadBalancer 实现Rpc调用的负载均衡。

Open Feign 为每个服务设置一个FeignContext 来隔离服务之间的配置和功能实例,Ribbon也会为每个服务创建一个 SpringClientFactory 作为服务的Ribbon上下文。

RibbonAutoConfiguration 配置类会创建Bean: SpringClientFactory,LoadBalancerClient的实现类 RibbonLoadBalancerClient

RibbonClientConfiguration 配置类会创建Bean: IRule的实现类 ZoneAvoidanceRule,IPing的实现类DummyPing,ILoadBalancer的实现类ZoneAwareLoadBalancer,

OkHttpFeignLoadBalancedConfiguration 配置会创建Bean:Client 的实现类LoadBalancerFeignClient

通过配置类对Ribbon中的主要功能类进行实例化,在ribbon context 中获取功能类时,会借助父 context,获取通过配置类注入的Bean。


LoadBalancerFeignClient作用

Feign接口转换代理对象过程中,服务的上下文中会根据Client的配置注入到当前下文中,LoadBalancerFeignClient 就是Client的实现类。

LoadBalancerFeignClient 在Feign.Builder.build()中参与 SynchronousMethodHandler.Factory 对象的构造,在feign.ReflectiveFeign#newInstance 中 调用 feign.ReflectiveFeign.ParseHandlersByName#apply,apply()时会将 client实例注入到
synchronousMethodHandler 中。Feign接口方法调用的时候,就会使用到 LoadBalancerFeignClient。

Feign接口调用最终会通过 org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute 进入 Ribbon的处理流程。

工作流程

ribbon 的入口是 LoadBalancerFeignClient#execute

获取上下文中的 IClientConfig 实现类 DefaultClientConfigImpl,
组装FeignLoadBalancer.RibbonRequest对象,使用委派模式将OkHttpClient的调用委派到RibbonRequest中。
获取上下文中的 FeignLoadBalancer 对象


执行 com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)。

  1. 将 IClientConfig 和 RibbonRequest 组装为 LoadBalancerCommand。
  2. 进入 com.netflix.loadbalancer.reactive.LoadBalancerCommand#submit, 执行com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer查找服务实例
  3. 获取服务实例的地址后,执行真正的Http调用 org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute,完成RPC请求。

选择服务实例

  1. 进入 com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer
  2. 进入 com.netflix.loadbalancer.ILoadBalancer#chooseServer, ILoadBalancer的实现类 DynamicServerListLoadBalancer 在FeignLoadBalancer创建时注入。最终方法会进入 BaseLoadBalancer 执行 chooseServer()。
  3. 进入 com.netflix.loadbalancer.BaseLoadBalancer#chooseServer,基础的负载均衡实现
  4. 进入 com.netflix.loadbalancer.IRule#choose,根据负载均衡算法挑选服务器

OpenFeign 是一个提供开发效率的框架,也对SpringCloud提供的功能进行融合,也是对微服务框架分析的第一站,后续会基于此文进行扩展,比如 Hystrix ribbon sluth...

你可能感兴趣的:(Spring Cloud OpenFeign 原理)