转载请注明出处:  

  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

Feign 进行rpc 调用时使用ribbon负载均衡源码解析的更多相关文章

  1. 小D课堂 - 新版本微服务springcloud+Docker教程_4-03 高级篇幅之Ribbon负载均衡源码分析实战

    笔记 3.高级篇幅之Ribbon负载均衡源码分析实战     简介: 讲解ribbon服务间调用负载均衡源码分析         1.完善下单接口         2.分析@LoadBalanced ...

  2. Ribbon负载均衡 (源码分析)

    Ribbon负载均衡 SpringCloud已经删除了ribbon组件,所以需要手动导入依赖.(要学是因为很多项目业务已经使用了ribbon) 服务拉取的时候添加了@LoadBalanced注解,实现 ...

  3. CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装

    CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装 http://www.cnblogs.com/ppoo24/p/4918288.ht ...

  4. Feign整合Ribbon和Hystrix源码解析

    在上篇文章Feign自动装配中,我们提到了Feign的自动装配的原理,以及Feign整合Ribbon和Hystrix的核心在类FeignClientFactoryBean中,那么本篇文章就来揭开这个类 ...

  5. Tars | 第2篇 TarsJava SpingBoot启动与负载均衡源码初探

    目录 前言 1. Tars客户端启动 @EnableTarsServer 2. Communicator通信器 3. 客户端的负载均衡调用器LoadBalance 最后 前言 通过源码分析可以得出这样 ...

  6. 【RocketMQ】负载均衡源码分析

    RocketMQ在集群模式下,同一个消费组内,一个消息队列同一时间只能分配给组内的某一个消费者,也就是一条消息只能被组内的一个消费者进行消费,为了合理的对消息队列进行分配,于是就有了负载均衡. 接下来 ...

  7. CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装 之 (三)Nginx负载均衡配置

    Nginx反向代理到单个PHP-FPM(PHP-FPM可位于不同机器) 0.首先,创建我们的网站根目录[注:须在PHP-FPM所在的那台机器创建](以后网站的代码放到此目录下): mkdir /opt ...

  8. CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装 之 (二)PHP(PHP-FPM)安装篇

    编译安装PHP及内置PHP-FPM nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把结果返回给客户端(浏览器). nginx一般是把请 ...

  9. CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装 之 (一)Nginx安装篇

    CentOS 6.5 minimal安装不再赘述 Nginx源码安装 1.安装wget下载程序 yum -y install wget 2.安装编译环境:gcc gcc-c++ automake au ...

  10. HBase rebalance 负载均衡源码角度解读使用姿势

    关键词:hbase rebalance 负载均衡 参考源码版本:apache-hbase-1.1.2 什么是HBase Rebalance ? 随着数据写入越来越多以及不均衡,即使一开始每个Regio ...

随机推荐

  1. CompletableFuture入门

    CompletableFuture入门 1.Future vs CompletableFuture 1.1 准备工作 先定义一个工具类 import java.nio.file.Files; impo ...

  2. POJ1006、hdu1370

    思路:中国剩余定理.纯粹的用暴力求逆元. 1 #include<iostream> 2 #include<string.h> 3 #include<string> ...

  3. 【算法】Java版

    二分查找算法 二分查找算法(Binary Search Algorithm)是一种在有序数组中查找特定元素的搜索算法.该算法的基本思想是将数组从中间分成两部分,然后与目标元素进行比较,进而确定目标元素 ...

  4. 整一个工具类【根据URL地址获取file文件对象】

    整一个工具类[根据URL地址获取file文件对象] 直接将网络url文件转换为file对象 import java.io.*; import java.net.URL; public class Im ...

  5. SpringBoot-MybatisPlus-Dynamic(多数据源)-springboot-mybatisplus-dynamic-duo-shu-ju-yuan-

    title: SpringBoot-MybatisPlus-Dynamic(多数据源) date: 2021-05-07 13:58:06.637 updated: 2021-12-26 17:43: ...

  6. 【scikit-learn基础】--『监督学习』之 K-近邻分类

    KNN(K-近邻),全称K-Nearest Neighbors,是一种常用的分类算法.KNN算法的历史可以追溯到1957年,当时Cover和Hart提出了"最近邻分类"的概念.但是 ...

  7. java中根据公网IP获取地址

    package com.dashan.utils.iputils; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3 ...

  8. Nginx unexpected end of file 配置证书遇到问题,如何解决?

    原文链接 https://bysocket.com/nginx-unexpected-end-of-file-expecting-in-key-file/ 一.Nginx unexpected end ...

  9. 火山引擎DataLeap数据调度实例的 DAG 优化方案(三):技术实现

    在原始数据中,是以一个数组的形式返回节点信息及依赖关系.所以,需要对数据进行处理形成图所需要的数据,同时,利用多个 map 对数据进行存储,方便后续对数据进行检索,减少时间复杂度. 实例节点的样式需要 ...

  10. AI Studio 基本操作

    https://aistudio.baidu.com/aistudio/projectdetail/6182202 项目启停 执行和调试 添加代码或文件 运行代码 %cd /home/aistudio ...