SpringCloud OpenFeign原来是这么基于Ribbon来实现负载均衡的三

pivoteic
发布于 2022-6-17 16:50
浏览
0收藏

 

这里就说完了Feign整合ribbon的配置类FeignRibbonClientAutoConfiguration,我们也找到了构造Feign.Builder的实现LoadBalancerFeignClient,接下来就来剖析LoadBalancerFeignClient的实现。

public class LoadBalancerFeignClient implements Client {

  static final Request.Options DEFAULT_OPTIONS = new Request.Options();

  private final Client delegate;

  private CachingSpringLoadBalancerFactory lbClientFactory;

  private SpringClientFactory clientFactory;

  public LoadBalancerFeignClient(Client delegate,
      CachingSpringLoadBalancerFactory lbClientFactory,
      SpringClientFactory clientFactory) {
    this.delegate = delegate;
    this.lbClientFactory = lbClientFactory;
    this.clientFactory = clientFactory;
  }

  static URI cleanUrl(String originalUrl, String host) {
    String newUrl = originalUrl;
    if (originalUrl.startsWith("https://")) {
      newUrl = originalUrl.substring(0, 8)
          + originalUrl.substring(8 + host.length());
    }
    else if (originalUrl.startsWith("http")) {
      newUrl = originalUrl.substring(0, 7)
          + originalUrl.substring(7 + host.length());
    }
    StringBuffer buffer = new StringBuffer(newUrl);
    if ((newUrl.startsWith("https://") && newUrl.length() == 8)
        || (newUrl.startsWith("http://") && newUrl.length() == 7)) {
      buffer.append("/");
    }
    return URI.create(buffer.toString());
  }

  @Override
  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);
    }
  }

  IClientConfig getClientConfig(Request.Options options, String clientName) {
    IClientConfig requestConfig;
    if (options == DEFAULT_OPTIONS) {
      requestConfig = this.clientFactory.getClientConfig(clientName);
    }
    else {
      requestConfig = new FeignOptionsClientConfig(options);
    }
    return requestConfig;
  }

  protected IOException findIOException(Throwable t) {
    if (t == null) {
      return null;
    }
    if (t instanceof IOException) {
      return (IOException) t;
    }
    return findIOException(t.getCause());
  }

  public Client getDelegate() {
    return this.delegate;
  }

  private FeignLoadBalancer lbClient(String clientName) {
    return this.lbClientFactory.create(clientName);
  }

  static class FeignOptionsClientConfig extends DefaultClientConfigImpl {

    FeignOptionsClientConfig(Request.Options options) {
      setProperty(CommonClientConfigKey.ConnectTimeout,
          options.connectTimeoutMillis());
      setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
    }

    @Override
    public void loadProperties(String clientName) {

    }

    @Override
    public void loadDefaultValues() {

    }

  }

}

 

在动态代理调用的那里我们得出一个结论,那就是最后会调用Client接口的execute方法的实现,所以我们就看一下execute方法的实现,这里就是一堆操作,从请求的URL中拿到了clientName,也就是服务名。

 

为什么可以拿到服务名?

其实很简单,OpenFeign构建动态代理的时候,传入了一个HardCodedTarget,当时说在构建HardCodedTarget的时候传入了一个url,那个url当时说了其实就是http://服务名,所以到这里,虽然有具体的请求接口的路径,但是还是类似 http://服务名/api/sayHello这种,所以可以通过路径拿到你锁请求的服务名。

 

拿到服务名之后,再拿到了一个配置类IClientConfig,最后调用lbClient,我们看一下lbClient的方法实现。

private FeignLoadBalancer lbClient(String clientName) {
    return this.lbClientFactory.create(clientName);
}

 

就是调用CachingSpringLoadBalancerFactory的create方法

public FeignLoadBalancer create(String clientName) {
    FeignLoadBalancer client = this.cache.get(clientName);
    if (client != null) {
      return client;
    }
    IClientConfig config = this.factory.getClientConfig(clientName);
    ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
    ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
        ServerIntrospector.class);
    client = this.loadBalancedRetryFactory != null
        ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
            this.loadBalancedRetryFactory)
        : new FeignLoadBalancer(lb, config, serverIntrospector);
    this.cache.put(clientName, client);
    return client;
  }

 

这个方法先根据服务名从缓存中获取一个FeignLoadBalancer,获取不到就创建一个。

 

创建的过程就是从每个服务对应的容器中获取到IClientConfig和ILoadBalancer。Ribbon那篇文章都讲过这些核心类,这里不再赘述。

 

默认就是创建不带spring重试功能的FeignLoadBalancer,放入缓存,最后返回这个FeignLoadBalancer。所以第一次来肯定没有,需要构建,也就是最终一定会返回FeignLoadBalancer,所以我们通过lbClient方法拿到的是FeignLoadBalancer。从这里可以看出CachingSpringLoadBalancerFactory是构建FeignLoadBalancer的工厂类,只不过先从缓存中查找,找不到再创建FeignLoadBalancer。

 

拿到FeignLoadBalancer之后就会调用executeWithLoadBalancer,接收到Response之后直接返回。

 

文章转自公众号:三友的java日记

已于2022-6-17 16:50:11修改
收藏
回复
举报
回复
    相关推荐