什么是Feign

Feign是Netflix公司开发的声明式、模板化的的HTTP客户端工具,可以帮助我们更加优雅的调用HTTP API,同时也使得微服务之间的调用变得简单。
SpringCloud feign进行了增强,使得Feign更够支持SpringMvc注解

Feign的作用

  1. 作为HTTP的客户端替代RestTemplate,支持注解的方式
  2. Feign组件中引入了Ribbon和Hystrix组件,这使得Feign也能够为我们提供负载均衡、熔断、降级的功能;
    我下面的贴图来自GitHub:openFeign

Feign的原理

先来看一下feign流程

在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

下面跟着代码我们来一起看一下:

1.这里重点讲一下LoadBalancerFeignClient

public Response execute(Request request, Options options) throws IOException {
        try {
            //获取本次请求的URI
            URI asUri = URI.create(request.url());
            //获取服务的名称,FeignClient中定义的服务的名称
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            //存储URI
            RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
            //存储connectTimeout、readTimeout的时间等
            IClientConfig requestConfig = this.getClientConfig(options, clientName);
            /*接下来的处理,会分成两步
            *1、this.lbClient(clientName)   //这里主要是通过CachingSpringLoadBalancerFactory获取FeignLoadBalancer
            *   下面2步骤会分析如何获取的
            *2、executeWithLoadBalancer(ribbonRequest, requestConfig)//这里就是通过LoadBalancer先选出需要调用的server,然后发送请求;
            *   具体是先通过LoadBalancerCommand获取到要调用的server信息
            *   然后再通过httpclient发送请求;
            *   从LazyTracingFeignClient
            *  下面3步骤会分析如何获取的
            *   
            */
            return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
        } catch (ClientException var8) {
            IOException io = this.findIOException(var8);
            if (io != null) {
                throw io;
            } else {
                throw new RuntimeException(var8);
            }
        }
    }
}

2.CachingSpringLoadBalancerFactory#create(String clientName)

public FeignLoadBalancer create(String clientName) {
   //private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap();
   //cache是一个volatile修饰的Map,
   FeignLoadBalancer client = (FeignLoadBalancer)this.cache.get(clientName);
       if (client != null) {
           return client;
       } else {
           //缓存中没有找到,这里先进行config初始化;
           IClientConfig config = this.factory.getClientConfig(clientName);
           //从SpringClientFactory中获取ZoneAwareLoadBalancer
           //ZoneAwareLoadBalancer是ribbon中一个非常重要的类,它用来获取eureka中的server列表,并且进行服务列表的更新;
           ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
           ServerIntrospector serverIntrospector = (ServerIntrospector)this.factory.getInstance(clientName, ServerIntrospector.class);
           //创建FeignLoadBalancer实例
           FeignLoadBalancer client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
           //将该FeignLoadBalancer放入容器中
           this.cache.put(clientName, client);
           return (FeignLoadBalancer)client;
       }

3.选取server、并进行调用

  • AbstractLoadBalancerAwareClient#executeWithLoadBalancer()
    • LoadBalancerCommand.submit()先通过ZoneAwareLoadBalancer获取到server信息,再执行调用
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
        LoadBalancerCommand command = this.buildLoadBalancerCommand(request, requestConfig);
        try {
            return (IResponse)command.submit(new ServerOperation<T>() {
                //submit函数中获取到了server信息,并在回调函数中进行真正的服务请求
                public Observable<T> call(Server server) {
                    URI finalUri = AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());
                    ClientRequest requestForServer = request.replaceUri(finalUri);
                    try {
                        //这里会调用FeignLoadBalancer#FeignLoadBalancer.RibbonResponse execute(...)方法
                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
                    } catch (Exception var5) {
                        return Observable.error(var5);
                    }
                }
            }).toBlocking().single();
        } catch (Exception var6) {
            ......
        }
    }

4.FeignLoadBalancer#FeignLoadBalancer.RibbonResponse execute(…)

public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
        Options options;
        if (configOverride != null) {
            //这里会覆盖RibbonProperties中的信息
            RibbonProperties override = RibbonProperties.from(configOverride);
            options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
        } else {
            options = new Options(this.connectTimeout, this.readTimeout);
        }
        /*下面会通过client进行查询,由于我的项目中引入了zipkin,所以这里会调用org.springframework.cloud.sleuth.instrument.web.client.feign.LazyTracingFeignClient
        *   ==>LazyTracingFeignClient.execute(Request request, Options options)
        *       ==>TracingFeignClient.execute(Request request, Options options)
        *           ==>Client.Default.execute(Request request, Options options)
        */
        Response response = request.client().execute(request.toRequest(), options);
        return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
    }

推荐阅读:

springCloud面试之feign+ribbon+hystirx交互概览

更多推荐

springCloud面试之Feign