Feign 进行rpc 调用时使用ribbon负载均衡源码解析

简介: Feign 进行rpc 调用时使用ribbon负载均衡源码解析

转载请注明出处:  

  Feign客户端接口的动态代理生成是基于JDK的动态代理来实现的,那么在所有的方法调用的时候最终都会走InvocationHandler接口的实现,默认就是ReflectiveFeign.FeignInvocationHandler,那我们接下来就来看看,FeignInvocationHandler是如何实现rpc调用的。

  FeignInvocationHandler对于invoke方法的实现。

public class ReflectiveFeign extends Feign {
static class FeignInvocationHandler implements InvocationHandler {
       
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (!"equals".equals(method.getName())) {
                if ("hashCode".equals(method.getName())) {
                    return this.hashCode();
                } else {
                    return "toString".equals(method.getName()) ? this.toString() : ((MethodHandler)this.dispatch.get(method)).invoke(args);
                }
            } else {
                try {
                    Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return this.equals(otherHandler);
                } catch (IllegalArgumentException var5) {
                    return false;
                }
            }
        }
 }

  实现rpc 调用的方法是 this.dispatch.get(method)).invoke(args)

  从dispatch获取要调用的方法对应的MethodHandler,然后调用MethodHandler的invoke方法。那MethodHandler是什么时候生成的呢?MethodHandler是在构建动态代理的时候生成的, 那MethodHandler作用是什么呢?你可以理解为最终rpc的调用都是基于这个MethodHandler来实现的,每个方法都有对应MethodHandler来实现rpc调用,接下来我们就来看一下MethodHandler的invoke方法的实现。

  MethodHandler是个接口,有两个实现类,一个是DefaultMethodHandler,这个是处理接口中的默认方法的,另一个是SynchronousMethodHandler,这个是实现rpc调用的方法。接下来我们就看看SynchronousMethodHandler关于invoke方法的实现。

public Object invoke(Object[] argv) throws Throwable {
         // 构建了一个RequestTemplate,RequestTemplate可以看成是组装http请求所需各种参数的封装,比如什么情头,body之类的都放在这里面。
        RequestTemplate template = this.buildTemplateFromArgs.create(argv);
         //Options主要是封装了发送请求是连接超时时间和读超时时间的配置
        Options options = this.findOptions(argv);
         // 重试组件,实现重试
        Retryer retryer = this.retryer.clone();
        while(true) {
            try {
                // 进行restTemplate 请求
                return this.executeAndDecode(template, options);
            } catch (RetryableException var9) {
                RetryableException e = var9;
                try {
                    retryer.continueOrPropagate(e);
                } catch (RetryableException var8) {
                    Throwable cause = var8.getCause();
                    if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                        throw cause;
                    }
                    throw var8;
                }
                if (this.logLevel != Level.NONE) {
                    this.logger.logRetry(this.metadata.configKey(), this.logLevel);
                }
            }
        }

  查看 executeAndDecode 方法实现

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
         // 该方法会遍历所有的拦截器RequestInterceptor,通过这个可以扩展实现一些自定义功能,如请求头等;
        Request request = this.targetRequest(template);
        long start = System.nanoTime();
        Response response;
        try {
            // 进行restTemplate 请求,并封装响应信息
            // client 为 LoadBalancerFeignClient,通过这个可以实现与ribbon 的结合
            response = this.client.execute(request, options);
            response = response.toBuilder().request(request).requestTemplate(template).build();
        } catch (IOException var12) {
            if (this.logLevel != Level.NONE) {
                this.logger.logIOException(this.metadata.configKey(), this.logLevel, var12, this.elapsedTime(start));
            } 
            throw FeignException.errorExecuting(request, var12);
        }
        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
        if (this.decoder != null) {
            return this.decoder.decode(response, this.metadata.returnType());
        } else {
            CompletableFuture<Object> resultFuture = new CompletableFuture();
            this.asyncResponseHandler.handleResponse(resultFuture, this.metadata.configKey(), response, this.metadata.returnType(), elapsedTime);
        }
    }

  Client是发送http请求的关键类 ; 当Feign客户端在构建动态代理的时候,填充很多组件到Feign.Builder中,其中有个组件就是Client的实现 ;这个组件的实现是要依赖负载均衡的,也就是这个组件是Feign用来整合Ribbon的入口。

 

Feign是如何通过ribbon实现负载均衡的

  查看 FeignRibbonClientAutoConfiguration 配置类:

@ConditionalOnClass({ILoadBalancer.class, Feign.class})
@ConditionalOnProperty(
    value = {"spring.cloud.loadbalancer.ribbon.enabled"},
    matchIfMissing = true
)
@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureBefore({FeignAutoConfiguration.class})
@EnableConfigurationProperties({FeignHttpClientProperties.class})
@Import({HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class})
public class FeignRibbonClientAutoConfiguration {
    public FeignRibbonClientAutoConfiguration() {
    }
}


@Impot注解导入了三个配置类。

  • HttpClientFeignLoadBalancedConfiguration:基于HttpClient实现http调用的。
  • OkHttpFeignLoadBalancedConfiguration:基于OkHttp实现http调用的。
  • DefaultFeignLoadBalancedConfiguration:默认的,也就是Feign原生的发送http的实现。

  看一下DefaultFeignLoadBalancedConfiguration配置类,因为默认就是这,HttpClientFeignLoadBalancedConfiguration和OkHttpFeignLoadBalancedConfiguration都需要有引入HttpClient和OkHttp依赖才会有用

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
 
  @Bean
  @ConditionalOnMissingBean
  public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
      return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory);
  }
 
}

  这个配置类很简单,声明了LoadBalancerFeignClient到spring容器,传入了三个参数,一个Client的实现,一个CachingSpringLoadBalancerFactory和一个SpringClientFactory。LoadBalancerFeignClient这个类实现了Client接口,也就数说我们在构建Feign.Builder填充的就是这个对象,也就是上面说feign的执行流程最后用来执行请求的Client的实现。

  Client.Default:就是Feign自己实现的Client,里面封装了真正发送http发送请求的功能,LoadBalancerFeignClient虽然也实现了Client接口,但是这个实现其实是为了整合Ribbon用的,并没有发送http的功能,所以需要有个可以发送http功能的实现。

  这里就说完了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) {
    // 调用CachingSpringLoadBalancerFactory的create方法,从缓存中获取一个FeignLoadBalancer,获取不到就创建一个
    return this.lbClientFactory.create(clientName);
}


3.查看 FeignLoadBalancer 源码

  核心源码:

public class FeignLoadBalancer extends
    AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
 
  private final RibbonProperties ribbon;
 
  protected int connectTimeout;
 
  protected int readTimeout;
 
  protected IClientConfig clientConfig;
 
  protected ServerIntrospector serverIntrospector;
 
  public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
      ServerIntrospector serverIntrospector) {
    super(lb, clientConfig);
    this.setRetryHandler(RetryHandler.DEFAULT);
    this.clientConfig = clientConfig;
    this.ribbon = RibbonProperties.from(clientConfig);
    RibbonProperties ribbon = this.ribbon;
    this.connectTimeout = ribbon.getConnectTimeout();
    this.readTimeout = ribbon.getReadTimeout();
    this.serverIntrospector = serverIntrospector;
  }
 
  @Override
  public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
      throws IOException {
    Request.Options options;
    if (configOverride != null) {
      RibbonProperties override = RibbonProperties.from(configOverride);
      options = new Request.Options(override.connectTimeout(this.connectTimeout),
          override.readTimeout(this.readTimeout));
    }
    else {
      options = new Request.Options(this.connectTimeout, this.readTimeout);
    }
    Response response = request.client().execute(request.toRequest(), options);
    return new RibbonResponse(request.getUri(), response);
  }
 }

  FeignLoadBalancer继承自AbstractLoadBalancerAwareClient,AbstractLoadBalancerAwareClient类主要作用是通过ILoadBalancer组件获取一个Server,然后基于这个Server重构了URI,也就是将你的请求路径http://服务名/api/sayHello转换成类似http://192.168.1.101:8088/api/sayHello这种路径,也就是将原服务名替换成服务所在的某一台机器ip和端口,替换之后就交由子类实现的exceut方法来发送http请求。

  所以我们知道调用executeWithLoadBalancer之后,就会重构请求路径,将服务名替换成某个具体的服务器所在的ip和端口,之后交给子类execute来处理,对于这里来说,也就是FeignLoadBalancer的execute方法,因为FeignLoadBalancer继承AbstractLoadBalancerAwareClient。

  直接定位到execute方法最核心的一行代码

Response response = request.client().execute(request.toRequest(), options);

  request.client()就会拿到构建LoadBalancerFeignClient传入的那个Client的实现,这个Client的实现是具体发送请求的实现,默认的就是Client.Default类(不是默认就有可能是基于HttpClient或者是OkHttp的实现)。所以这行代码就是基于这个Client就成功的发送了Http请求,拿到响应,然后将这个Response 封装成一个RibbonResponse返回,最后就返回给MethodHandler,然后解析响应,封装成方法的返回值返回给调用者。

  其实到这里就完全知道Feign是如何整合Ribbon的,LoadBalancerFeignClient其实是OpenFeign适配Ribbon的入口,FeignLoadBalancer才是真正实现选择负载均衡,发送http请求的组件,因为他继承了AbstractLoadBalancerAwareClient。

 

参考文章:

  https://blog.csdn.net/u010342147/article/details/123669493

  https://blog.csdn.net/weixin_45630885/article/details/124391934

  https://blog.csdn.net/weixin_45630885/article/details/124533413

 

 

标签: spring cloud

目录
相关文章
|
6月前
|
JSON 自然语言处理 API
gRPC凭什么成为微服务通信首选?深度解析RPC进化史
本文深入解析了分布式系统中服务通信的核心机制,重点介绍了 RPC 与 gRPC 的原理、优势及使用场景,并详解 gRPC 所依赖的序列化协议 Protocol Buffers(Protobuf)。内容涵盖 RPC 概念、gRPC 特性、Protobuf 语法及服务定义,适合微服务架构设计与维护人员阅读,助你构建高性能、低耦合的服务通信体系。
788 73
gRPC凭什么成为微服务通信首选?深度解析RPC进化史
|
10月前
|
弹性计算 负载均衡 网络协议
阿里云SLB深度解析:从流量分发到架构优化的技术实践
本文深入探讨了阿里云负载均衡服务(SLB)的核心技术与应用场景,从流量分配到架构创新全面解析其价值。SLB不仅是简单的流量分发工具,更是支撑高并发、保障系统稳定性的智能中枢。文章涵盖四层与七层负载均衡原理、弹性伸缩引擎、智能DNS解析等核心技术,并结合电商大促、微服务灰度发布等实战场景提供实施指南。同时,针对性能调优与安全防护,分享连接复用优化、DDoS防御及零信任架构集成的实践经验,助力企业构建面向未来的弹性架构。
790 76
|
11月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1073 29
|
11月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
455 4
|
11月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
11月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
11月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
12月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
3274 1
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
1193 1
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
11月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

相关实验场景

更多

推荐镜像

更多
  • DNS