Spring Cloud 远程接口调用OpenFeign负载均衡实现原理详解

环境:Spring Cloud 2021.0.7 + Spring Boot 2.7.12


配置依赖

maven依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

开启注解功能

@SpringBootApplication
// 开启Feign功能,在该注解中你还可以配置,如下3个重要的信息:
// 1. 为所有的FeignClient提供统一默认的配置
// 2. 指定扫描那些包写的类
// 3. 指定有哪些@FeignClient类
@EnableFeignClients
public class AppApplication {public static void main(String[] args) {SpringApplication.run(AppApplication.class, args);}}

FeignClient生成Bean原理

容器在启动过程中会找到所有@FeignClient的接口类,然后将这些类注册为容器Bean,而每一个Feign客户端对应的是FactoryBean对象FeignClientFactoryBean。

具体如何找这些带有@FeignClient注解的接口类可以查看FeignClientsRegistrar该类就在@EnableFeignClients中被导入。

FeignClientFactoryBean

public class FeignClientFactoryBean implements FactoryBean {public Object getObject() {return getTarget();}<T> T getTarget() {FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class) : applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) {if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();// 负载均衡处理return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));}// ...}protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {// 在OpenFeign中核心实现负载均衡的类就是具体的Client类// Feign负载均衡能力实现通过具体Client实现,每一个FeignClient客户端都会对应一个子容器AnnotationConfigApplicationContext// 根据@FeignClient配置的服务名name或value为key,从一个LoadBalancerClientFactory(父类)中的Map中查找该name对应的容器// 如果不存在则创建一个AnnotationConfigApplicationContext。每个子容器都设置了父容器,如果通过子容器查找不到Client的实现,那么会从父容器中查找Client client = getOptional(context, Client.class);}
}

Client实现

Client的具体实现可以有如下:

  1. apache httpclient

  2. okhttp

  3. default(jdk)

具体使用哪个是根据你环境引入了哪个依赖(httpclient,okhttp)

<!-- httpclient -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId><version>${version}</version>
</dependency>
<!-- okhttp -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId><version>${version}</version>
</dependency>

具体选择通过如下配置

@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {
}

如果你的环境有多个实现,那么这里会根据这里的导入顺序加载。这里以最后一个
DefaultFeignLoadBalancerConfiguration为例。

class DefaultFeignLoadBalancerConfiguration {@Bean@ConditionalOnMissingBean// 没有启用spring-retry重试功能@Conditional(OnRetryNotEnabledCondition.class)public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {// 这里构造函数第一个参数将会成为最终执行远程接口调用的实现return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, loadBalancerClientFactory);}
}

在没有导入httpclient或者okhttp情况下,使用的Client实现是
FeignBlockingLoadBalancerClient。

负载均衡实现

构造
FeignBlockingLoadBalancerClient传入了负载均衡客户端LoadBalancerClient及负载均衡客户端工厂LoadBalancerClientFactory该工厂是用来创建每一个Feign客户端对应的子容器
AnnotationConfigApplicationContext及从对应子容器获取相应的Bean实例对象,如:Client,Request.Options,Logger.Level等。

public class FeignBlockingLoadBalancerClient implements Client {// 此Client代理对象是上面的new Client.Default(null, null)private final Client delegate;private final LoadBalancerClient loadBalancerClient;private final LoadBalancerClientFactory loadBalancerClientFactory;public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory) {this.delegate = delegate;this.loadBalancerClient = loadBalancerClient;this.loadBalancerClientFactory = loadBalancerClientFactory;}@Overridepublic Response execute(Request request, Request.Options options) throws IOException {final URI originalUri = URI.create(request.url());// 获取服务名serviceIdString serviceId = originalUri.getHost();String hint = getHint(serviceId);DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(buildRequestData(request), hint));// ...// 通过负载均衡客户端获取指定serviceId的服务实例ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);// ...// 通过获取到的ServiceInstance实例,重新构造请求地址String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();// 重新构建一个新的请求Request newRequest = buildRequest(request, reconstructedUrl);LoadBalancerProperties loadBalancerProperties = loadBalancerClientFactory.getProperties(serviceId);return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse, supportedLifecycleProcessors, loadBalancerProperties.isUseRawStatusCodeInResponseData());}protected Request buildRequest(Request request, String reconstructedUrl) {return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),request.charset(), request.requestTemplate());}
}

LoadBalancerClient具体实现:​​​​​​​BlockingLoadBalancerClient

public class BlockingLoadBalancerClient implements LoadBalancerClient {private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;public BlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {this.loadBalancerClientFactory = loadBalancerClientFactory;}public <T> ServiceInstance choose(String serviceId, Request<T> request) {// 获取一个负载均衡器,默认是轮询策略ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;}Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();if (loadBalancerResponse == null) {return null;}return loadBalancerResponse.getServer();}// 重新构造请求的uripublic URI reconstructURI(ServiceInstance serviceInstance, URI original) {return LoadBalancerUriTools.reconstructURI(serviceInstance, original);}
}
public final class LoadBalancerUriTools {public static URI reconstructURI(ServiceInstance serviceInstance, URI original) {// ...return doReconstructURI(serviceInstance, original);}private static URI doReconstructURI(ServiceInstance serviceInstance, URI original) {String host = serviceInstance.getHost();String scheme = Optional.ofNullable(serviceInstance.getScheme()).orElse(computeScheme(original, serviceInstance));int port = computePort(serviceInstance.getPort(), scheme);if (Objects.equals(host, original.getHost()) && port == original.getPort() && Objects.equals(scheme, original.getScheme())) {return original;}boolean encoded = containsEncodedParts(original);return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host).port(port).build(encoded).toUri();}
}

轮询算法​​​​​​​

public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {public Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {// ...// 如果只有一个实例if (instances.size() == 1) {return new DefaultResponse(instances.get(0));}// Ignore the sign bit, this allows pos to loop sequentially from 0 to// Integer.MAX_VALUEint pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());return new DefaultResponse(instance);}
}

执行远程调用

接着上面
FeignBlockingLoadBalancerClient#execute方法最终的返回方法执行​​​​​​​

final class LoadBalancerUtils {static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean useRawStatusCodes) throws IOException {return executeWithLoadBalancerLifecycleProcessing(feignClient, options, feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors, true, useRawStatusCodes);}static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced, boolean useRawStatusCodes) throws IOException {// 这里执行生命周期实际调用前动作try {// 执行时间的调用,而这里的feignClient就是在FeignBlockingLoadBalancerClient传递过来的,new Client.Default(null, null)Response response = feignClient.execute(feignRequest, options);// 这里执行生命周期回调,省略return response;}// ...}
}

Client.Default

public interface Client {public Response execute(Request request, Options options) throws IOException {// 通过JDK自带的网络连接进行处理HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);}
}

以上就是一个Feign客户端请求的负载均衡实现原理

完毕~!~!

求助三连~~~

 图片

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一百三十、海豚调度器——用DolphinScheduler定时调度HiveSQL任务

一、目标 用海豚调度器对Hive数仓各层数据库的SQL任务进行定时调度。比如&#xff0c;DWD层脱敏清洗表的动态插入数据、DWS层指标表的动态插入数据 二、工具版本 1、海豚调度器&#xff1a;apache-dolphinscheduler-2.0.5-bin.tar.gz 2、Hive&#xff1a;apache-hive-3.1.2…

长短期记忆网络(LSTM)原理解析

长短期记忆网络&#xff08;Long Short-Term Memory&#xff0c;简称LSTM&#xff09;是一种常用于处理序列数据的深度学习模型。它在循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;的基础上进行了改进&#xff0c;旨在解决传统RNN中的梯度消失…

PyTorch训练RNN, GRU, LSTM:手写数字识别

文章目录 pytorch 神经网络训练demoResult参考来源 pytorch 神经网络训练demo 数据集&#xff1a;MNIST 该数据集的内容是手写数字识别&#xff0c;其分为两部分&#xff0c;分别含有60000张训练图片和10000张测试图片 图片来源&#xff1a;https://tensornews.cn/mnist_intr…

Kafka消息监控管理工具Offset Explorer的使用教程

1、kafka监控管理工具 Offset Explorer是一款用于监控和管理Apache Kafka集群中消费者组偏移量的开源工具。它提供了一个简单直观的用户界面&#xff0c;用于查看和管理Kafka消费者组偏移量的详细信息。 Offset Explorer具有以下主要功能和特点&#xff1a; 实时监控&#x…

架构训练营学习笔记3-5:消息队列备选架构设计实战

本文属于架构训练营学习笔记系列&#xff1a;模块3的案例讲解 总的来说&#xff0c;这篇从更高的维度去讲&#xff0c;而不是关注消息队列的常见问题&#xff1a;比如消息如何发送&#xff0c;消息如何不丢失 &#xff0c;消息如何不重复。总体上分为2部分&#xff1a;利益干系…

【Docker】什么是Docker,它用来干什么

作者简介&#xff1a; 辭七七&#xff0c;目前大一&#xff0c;正在学习C/C&#xff0c;Java&#xff0c;Python等 作者主页&#xff1a; 七七的个人主页 文章收录专栏&#xff1a; 七七的闲谈 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f…

ADB初识

ADB是Android Debug Bridge&#xff0c;是一个命令行程序。abd可以从计算机上通过USB控制Android手机设备。可以使用ADB复制文件、安装和卸载应用程序&#xff0c;运行shell命令等。 ADB的下载配置 Windows版本&#xff1a;https://dl.google.com/android/repository/platform…

2.SpringBoot运维实用篇

SpringBoot运维实用篇 ​ ​ 下面就从运维实用篇开始讲&#xff0c;在运维实用篇中&#xff0c;我给学习者的定位是玩转配置&#xff0c;为开发实用篇中做各种技术的整合做好准备工作。 主要分为以下内容&#xff1a; SpringBoot程序的打包与运行配置高级多环境开发日志 ​…

C国演义 [第十二章]

第十二章 打家劫舍题目理解步骤dp数组递推公式初始化遍历顺序 代码 打家劫舍II题目理解步骤递推公式初始化遍历顺序 代码 打家劫舍 力扣链接 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋…

《深度学习推荐系统》笔记

目录 一、推荐系统是什么1.作用和意义2.推荐系统的架构2.1 逻辑架构2.2 技术架构 二、传统的推荐系统方法1. 协同过滤算法1.1 userCF&&ItemCF1.3 矩阵分解算法 2. 逻辑回归算法3. 因子分解机3.1 POLY2模型3.2 FM模型3.3 FFM模型3.4 小结 4. 组合模型4.1 GBDTLR组合模型…

通过监控平台提高运维效率、降低运营成本、实现绿色低碳、节能降耗、提升PUE值-安科瑞黄安南

01引言 近年来&#xff0c;随着母线槽在建筑及工厂的配电中越来越广泛&#xff0c;母线槽场景运用得越多&#xff0c;随着数据中心建设的快速发展和更高需求&#xff0c;智能母线系统逐渐被应用于机房的末端配电中&#xff0c;具有电流小、插接方便、智能化程度高等特点&#…

中间件上云部署 zookeeper

中间件上云部署 zookeeper 企业级中间件上云部署 zookeeper一、环境说明二、zookeeper部署YAML资源清单准备三、zookeeper部署及部署验证四、zookeeper应用验证 企业级中间件上云部署 zookeeper 一、环境说明 storageclassingress 二、zookeeper部署YAML资源清单准备 # vim…

【OC总结- Block】

文章目录 前言2. Block2.1 Block的使用规范2.2 __block修饰符2.3 Block的类型2.4 Block的循环引用及解决循环引用的场景引入解决循环引用Block循环引用场景 2.5 Block的实现及其本质2.5.1 初始化部分2.5.2 调用部分2.5.3 捕获变量 Block本质2.6 Block捕获变量 和 对象2.7 Block…

数据结构-双向带头循环链表

链表的分类实现带有哨兵位的双向的循环链表**定义节点的结构**初始化单个节点初始化带有哨兵位的双向循环链表打印链表销毁链表尾插尾删头插头删find函数在任意位置之前插入任意位置的删除全部代码list.hlist.ctest.c 链表和顺序表的区别 链表的分类 如下 根据上述的三种组合…

部署langchain+chatglm

先参考&#xff1a;window零基础部署langchain-ChatGLM_飞奔的屎壳郎的博客-CSDN博客 安装一部分&#xff0c; 1.GCC安装 gcc64位下载 一定要装64位的gcc&#xff0c;因为我的电脑是w10 64位的&#xff0c;装32位运行langchain报错并配置环境变量 可直接用压缩包中的文件&am…

Verilog 学习之路

循环 7-10 代码段 generategenvar i;for (i0; i<8; i i1) begin: my_block_nameassign out[i] in[8-i-1];end endgenerate解释 该代码使用了 S y s t e m V e r i l o g SystemVerilog SystemVerilog 中的 g e n e r a t e generate generate 构造&#xff0c;它允许在…

【软考】系统架构设计风格分类的个人理解

个人适当学习了软考系统架构设计师中关于系统架构设计相关的内容&#xff0c;梳理了一下相关信息。 常见架构类型和常见分类 常见的软考中出现的系统架构列举如下&#xff1a; 分层架构管道-过滤器架构客户端-服务器架构模型-视图-控制器架构&#xff0c;即MVC架构事件驱动架…

行为树(BEHAVIOR TREES)及其工业应用

顾名思义&#xff0c;行为树是描述事物&#xff08;人&#xff0c;动物&#xff0c;机器人&#xff0c;虚拟角色等等&#xff09;行为的树形结构。游戏行业使用行为树为角色行为建模。现在行为树建模技术正在向其它领域渗透&#xff0c;比如工业产线编排&#xff0c;机器人控制…

【从零开始学习C++ | 第二十一篇】C++新增特性 (上)

目录 前言&#xff1a; 委托构造函数&#xff1a; 类内初始化&#xff1a; 空指针&#xff1a; 枚举类&#xff1a; 总结&#xff1a; 前言&#xff1a; C的学习难度大&#xff0c;内容繁多。因此我们要及时掌握C的各种特性&#xff0c;因此我们更新本篇文章&#xff0c;向…

【案例实战】高并发业务的多级缓存架构一致性解决方案

我们在高并发的项目中基本上都离不开缓存&#xff0c;那么既然引入缓存&#xff0c;那就会有一个缓存与数据库数据一致性的问题。 首先&#xff0c;我们先来看看高并发项目里面Redis常见的三种缓存读写模式。 Cache Aside 读写分离模式&#xff0c;是最常见的Redis缓存模式&a…