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
英文地址,請注明出處:http://en.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 …