spring cloud负载均衡分析之FeignBlockingLoadBalancerClient、BlockingLoadBalancerClient

本文主要分析被 @FeignClient 注解的接口类请求过程中负载均衡逻辑,流程分析使用的依赖版本信息如下:

        <spring-boot.version>3.2.1</spring-boot.version><spring-cloud.version>2023.0.0</spring-cloud.version><com.alibaba.cloud.version>2023.0.0.0-RC1</com.alibaba.cloud.version><!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!-- spring-cloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency><!--注册中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>${com.alibaba.cloud.version}</version></dependency>

背景

平常我们代码里用@FeignClien注解一个接口类,实现一个远程接口(如下)

@FeignClient(name = ServiceNameConstants.XXX, fallbackFactory = XXXFactory.class)
public interface RemoteXXXService {@GetMapping("/XXX/getById")Result<XXX> getById(@RequestParam("Id") String Id);
}

被@FeignClien注解的类,在运行的时候容器会生成一个动态类,从调用堆栈能看出;

在这里插入图片描述
最终调用到 FeignBlockingLoadBalancerClient 的 execute 方法,下面我们详细分析这个方法调用的由来;

分析

直接答案:openfeign负载均衡使用需要注解和自动装配配合才能生效,即@EnableFeignClients与 spring-cloud-openfeign-core下的org.springframework.boot.autoconfigure.AutoConfiguration.imports 配合;

  • @EnableFeignClients主要是决定是否要使用负载均衡(引用了负载均衡依赖,但是可以不使用该特性)
  • AutoConfiguration 主要是初始化一些负载均衡所需的基础设施
负载均衡代理类生成流程

使用open-feign我们一般会在启动类上添加注解 @EnableFeignClients,这个注解内容如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {  ...  }

可以看出该注解类是一个 @Import 的复合注解,也就是说在启动过程中该注解具备@Import的功能,会引入FeignClientsRegistrar类;

我们看看FeignClientsRegistrar这个类是ImportBeanDefinitionRegistrar的子类,应用启动后会执行registerBeanDefinitions方法;

注册FeignClient BeanDefinition

我们直接来到核心方法
org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient

	private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,Map<String, Object> attributes) {String className = annotationMetadata.getClassName();if (String.valueOf(false).equals(environment.getProperty("spring.cloud.openfeign.lazy-attributes-resolution", String.valueOf(false)))) {eagerlyRegisterFeignClientBeanDefinition(className, attributes, registry);}else {lazilyRegisterFeignClientBeanDefinition(className, attributes, registry);}}

这个方法主要根据配置注册懒加载bean或者是立即实例化的类,配置key是:spring.cloud.openfeign.lazy-attributes-resolution ,如果是true的话应该会加长应用启动时间,只有配置了true才会懒初始化;

BeanDefinition的目标类是org.springframework.cloud.openfeign.FeignClientFactoryBean,那么后续创建FeignClient 的代理类就由该类承担;
1、核心入口方法是:org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget,
2、最终调用的方法是:feign.ReflectiveFeign#newInstance(feign.Target, C)

    public <T> T newInstance(Target<T> target, C requestContext) {ReflectiveFeign.TargetSpecificationVerifier.verify(target);Map<Method, InvocationHandlerFactory.MethodHandler> methodToHandler = this.targetToHandlersByName.apply(target, requestContext);InvocationHandler handler = this.factory.create(target, methodToHandler);T proxy = (T)Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);for(InvocationHandlerFactory.MethodHandler methodHandler : methodToHandler.values()) {if (methodHandler instanceof DefaultMethodHandler) {((DefaultMethodHandler)methodHandler).bindTo(proxy);}}return proxy;}

到这里 Proxy 代理类就创建好了;

open-feign自动装配

我们打开spring-cloud-openfeign-core的spring配置
在这里插入图片描述
内容如下:

org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration
org.springframework.cloud.openfeign.FeignAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
  • FeignAutoConfiguration主要功能:

提供生成动态代理所需的默认组件(如 Encoder、Decoder、Contract),确保 @EnableFeignClients 扫描到的接口能正确实例化。

  • FeignAutoConfiguration主要功能:

FeignAutoConfiguration 自动检测项目中是否包含负载均衡依赖(如 spring-cloud-starter-loadbalancer),若存在则配置 LoadBalancerFeignClient

@ConditionalOnClass(Feign.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties(FeignHttpClientProperties.class)
@Configuration(proxyBeanMethods = false)
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class,Http2ClientFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
public class FeignLoadBalancerAutoConfiguration {@Bean@ConditionalOnBean(LoadBalancerClientFactory.class)@ConditionalOnMissingBean(XForwardedHeadersTransformer.class)public XForwardedHeadersTransformer xForwarderHeadersFeignTransformer(LoadBalancerClientFactory factory) {return new XForwardedHeadersTransformer(factory);}
}

这个类通过@Import引入了DefaultFeignLoadBalancerConfiguration,其注入FeignBlockingLoadBalancerClient

	@Bean@ConditionalOnMissingBean@Conditional(OnRetryNotEnabledCondition.class)public Client feignClient(LoadBalancerClient loadBalancerClient,LoadBalancerClientFactory loadBalancerClientFactory,List<LoadBalancerFeignRequestTransformer> transformers) {return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,loadBalancerClientFactory, transformers);}
FeignBlockingLoadBalancerClient

这个类是归属spring-cloud-starter-openfeign依赖

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>

我们看看execute方法

public Response execute(Request request, Request.Options options) throws IOException {final URI originalUri = URI.create(request.url());String serviceId = originalUri.getHost();Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);String hint = getHint(serviceId);DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(buildRequestData(request), hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),RequestDataContext.class, ResponseData.class, ServiceInstance.class);supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// @AServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(instance);if (instance == null) {String message = "Load balancer does not contain an instance for the service " + serviceId;if (LOG.isWarnEnabled()) {LOG.warn(message);}supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(CompletionContext.Status.DISCARD, lbRequest, lbResponse)));return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value()).body(message, StandardCharsets.UTF_8).build();}//		@BString reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();Request newRequest = buildRequest(request, reconstructedUrl, instance);return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,supportedLifecycleProcessors);}

@A:通过 loadBalancer 获得服务实例
@B:获得真是的请求地址,即nacos上注册的地址

BlockingLoadBalancerClient

上面FeignBlockingLoadBalancerClient @A 和 @B的地方都调用了loadBalancerClient的方法,loadBalancerClient是一个BlockingLoadBalancerClient类对象。

pom里依赖如下,需要单独引入:

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
choose方法
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {// @CReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;} else {
// @DResponse<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();}}

@C:这个地方很奇特,底层代码是根据serviceId找到缓存的GenericApplicationContext对象,然后通过getBean的方式获得对象,没有想通这样做的理由;

@D:如果从缓存中有找到ReactiveLoadBalancer,则将结果封装成ServiceInstance对象。(这个过程代码比较复杂)

reconstructURI 方法
    public URI reconstructURI(ServiceInstance serviceInstance, URI original) {return LoadBalancerUriTools.reconstructURI(serviceInstance, original);}

最终调用到如下方法:

    private static URI doReconstructURI(ServiceInstance serviceInstance, URI original) {String host = serviceInstance.getHost();String scheme = (String)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;} else {boolean encoded = containsEncodedParts(original);return UriComponentsBuilder.fromUri(original).scheme(scheme).host(host).port(port).build(encoded).toUri();}}

最终从ServiceInstance 解析出目标服务信息,返回URI对象;

over~~

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

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

相关文章

ref 和 reactive

文章目录ref 和 reactive一、差异二、能否替代的场景分析&#xff08;1&#xff09;基本类型数据&#xff08;2&#xff09;对象类型数据&#xff08;3&#xff09;数组类型数据&#xff08;4&#xff09; 需要整体替换的场景三、替代方案与兼容写法1. 用 reactive 模拟 ref2. …

BatchNorm 与 LayerNorm:原理、实现与应用对比

BatchNorm 与 LayerNorm&#xff1a;原理、实现与应用对比 Batch Normalization (批归一化) 和 Layer Normalization (层归一化) 是深度学习中两种核心的归一化技术&#xff0c;它们解决了神经网络训练中的内部协变量偏移问题&#xff0c;大幅提升了模型训练的稳定性和收敛速度…

OcsNG基于debian一键部署脚本

&#x1f914; 为什么有了GLPI还要部署OCS-NG&#xff1f; 核心问题&#xff1a;数据收集的风险 GLPI直接收集的问题&#xff1a; Agent直接向GLPI报告数据时&#xff0c;任何收集异常都会直接影响资产数据库网络问题、Agent故障可能导致重复资产、错误数据、资产丢失无法对收集…

001_Claude开发者指南介绍

Claude开发者指南介绍 目录 Claude简介Claude 4 模型开始使用核心功能支持资源 Claude简介 Claude 是由 Anthropic 构建的高性能、可信赖和智能的 AI 平台。Claude 具备出色的语言、推理、分析和编程能力&#xff0c;可以帮助您解决各种复杂任务。 想要与 Claude 聊天吗&a…

004_Claude功能特性与API使用

Claude功能特性与API使用 目录 API 基础使用核心功能特性高级功能开发工具平台支持 API 基础使用 快速开始 通过 Anthropic Console 获取 API 访问权限&#xff1a; 在 console.anthropic.com/account/keys 生成 API 密钥使用 Workbench 在浏览器中测试 API 认证方式 H…

ReAct论文解读(1)—什么是ReAct?

什么是ReAct&#xff1f; 在大语言模型&#xff08;LLM&#xff09;领域中&#xff0c;ReAct 指的是一种结合了推理&#xff08;Reasoning&#xff09; 和行动&#xff08;Acting&#xff09; 的提示方法&#xff0c;全称是 “ReAct: Synergizing Reasoning and Acting in Lan…

【云服务器安全相关】服务器防火墙常见系统日志信息说明

目录✅ 一、防火墙日志是做什么的&#xff1f;&#x1f6e0;️ 二、常见防火墙日志信息及说明&#x1f9ea; 三、典型日志示例解析1. 被阻断的访问&#xff08;DROP&#xff09;2. 被允许的访问&#xff08;ACCEPT&#xff09;3. 被拒绝的端口访问4. 可疑端口扫描行为&#x1f…

011_视觉能力与图像处理

视觉能力与图像处理 目录 视觉能力概述支持的图像格式图像上传方式使用限制最佳实践应用场景API使用示例视觉能力概述 多模态交互 Claude 3 系列模型具备强大的视觉理解能力,可以分析和理解图像内容,实现真正的多模态AI交互。这种能力使Claude能够: 图像内容分析:理解图…

ansible自动化部署考试系统前后端分离项目

1. ✅ansible编写剧本步骤1️⃣创建roles目录结构2️⃣在group_vars/all/main.yml中定义变量列表3️⃣在tasks目录下编写tasks任务4️⃣在files目录下准备部署文件5️⃣在templates目录下创建j2模板文件6️⃣在handlers目录下编写handlers7️⃣在roles目录下编写主playbook8️⃣…

【AI论文】GLM-4.1V-Thinking:迈向具备可扩展强化学习的通用多模态推理

摘要&#xff1a;我们推出GLM-4.1V-Thinking&#xff0c;这是一款旨在推动通用多模态推理发展的视觉语言模型&#xff08;VLM&#xff09;。在本报告中&#xff0c;我们分享了在以推理为核心的训练框架开发过程中的关键发现。我们首先通过大规模预训练开发了一个具备显著潜力的…

Linux进程通信——匿名管道

目录 1、进程间通信基础概念 2、管道的工作原理 2.1 什么是管道文件 3、匿名管道的创建与使用 3.1、pipe 系统调用 3.2 父进程调用 fork() 创建子进程 3.3. 父子进程的文件描述符共享 3.4. 关闭不必要的文件描述符 3.5 父子进程通过管道进行通信 父子进程通信的具体例…

sql:sql在office中的应用有哪些?

在Office软件套件中&#xff0c;主要是Access和Excel会用到SQL&#xff08;结构化查询语言&#xff09;&#xff0c;以下是它们在这两款软件中的具体应用&#xff1a; 在Access中的应用 创建和管理数据库对象&#xff1a; 创建表&#xff1a;使用CREATE TABLE语句可以创建新的数…

零基础完全理解视觉语言模型(VLM):从理论到代码实践

本文是《从LLM到VLM&#xff1a;视觉语言模型的核心技术与Python实现》的姊妹篇&#xff0c;主要面向零基础的读者&#xff0c;希望用更通俗易懂的语言带领大家入门VLM。本教程的完整代码可以在GitHub上找到&#xff0c;如果你有任何问题或建议&#xff0c;欢迎交流讨论。 写在…

数据结构 Map和Set

文章目录&#x1f4d5;1. 二叉搜索树✏️1.1 查找操作✏️1.2 插入操作✏️1.3 删除操作&#x1f4d5;2. Map的使用✏️2.1 Map的常用方法✏️2.2 TreeMap和HashMap的区别✏️2.3 HashMap的底层实现&#x1f4d5;3. Set的使用✏️3.1 Set的常用方法✏️3.2 TreeSet和HashSet的区…

树莓派5-系统 Debian 12 开启VNC远程访问踩坑记录

简单记录一下踩坑&#xff0c;安装vnc远程访问服务并设置开机自启1.查看系统版本&#xff0c;我这里的系统版本是 12cat /etc/os-release2.安装VNC服务sudo apt install realvnc-vnc-server realvnc-vnc-viewer -y3.创建服务单元文件&#xff1a;sudo nano /etc/systemd/system…

TASK2 夏令营:用AI做带货视频评论分析

TASK2 夏令营&#xff1a;用AI做带货视频评论分析**电商评论洞察赛题&#xff1a;从Baseline到LLM进阶优化学习笔记**一、 赛题核心解读1.1. 任务链条与目标1.2. 关键挑战与评分机制二、 Baseline方案回顾与瓶颈分析2.1. Baseline技术栈2.2. 核心瓶颈三、 进阶优化策略&#xf…

Docker:安装命令笔记

目录 零、安装&#xff1a;略 一、镜像 1.0、获取镜像&#xff1a; 1.1、查看镜像&#xff1a; 1.2、删除镜像&#xff1a; 二、容器 2.0、创建并启动容器 2.1、tomcat和jdk9的“创建并启动容器”的命令 2.2、容器操作 2.3、容器日志操作 零、安装&#xff1a;略 略 …

Python七彩花朵

系列文章 序号直达链接Tkinter1Python李峋同款可写字版跳动的爱心2Python跳动的双爱心3Python蓝色跳动的爱心4Python动漫烟花5Python粒子烟花Turtle1Python满屏飘字2Python蓝色流星雨3Python金色流星雨4Python漂浮爱心5Python爱心光波①6Python爱心光波②7Python满天繁星8Pytho…

【保姆级图文详解】MCP架构(客户端-服务端)、三种方式使用MCP服务、Spring AI MCP客户端和服务端开发、MCP部署方案、MCP安全性

文章目录前言一、MCP(model context protocol)1.1、概念描述1.2、MCP作用与意义1.3、MCP架构二、使用MCP(model context protocol)2.1、云平台使用MCP2.2、软件客户端使用MCP2.3、Spring AI程序中使用MCP三、Spring AI MCP(model context protocol)开发过程3.1、MCP服务端开发3…

Linux的 iproute2 配置:以太网(Ethernet)、绑定(Bond)、虚拟局域网(VLAN)、网桥(Bridge)笔记250713

Linux的 iproute2 配置:以太网(Ethernet)、绑定(Bond)、虚拟局域网(VLAN)、网桥(Bridge&#xff09;笔记250713 在 Linux 中使用 iproute2 工具集配置网络是现代且推荐的方法&#xff0c;它取代了旧的 ifconfig、route、brctl、vconfig 等命令。iproute2 提供了统一的接口 ip …