当前位置: 首页 > news >正文

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.dtcms.com/a/276944.html

相关文章:

  • 【Qt开发】Qt的背景介绍(一)
  • 时序预测 | Matlab代码实现VMD-TCN-GRU-MATT变分模态分解时间卷积门控循环单元多头注意力多变量时序预测
  • [特殊字符] Python自动化办公 | 3步实现Excel数据清洗与可视化,效率提升300%
  • 开源链动2+1模式、AI智能名片与S2B2C商城小程序在私域运营中的协同创新研究
  • 从零开始跑通3DGS教程:(五)3DGS训练
  • 《区间dp》
  • 一文读懂现代卷积神经网络—深度卷积神经网络(AlexNet)
  • 深入理解观察者模式:构建松耦合的交互系统
  • Redis技术笔记-从三大缓存问题到高可用集群落地实战
  • ESP-Timer入门(基于ESP-IDF-5.4)
  • JVM:内存、类加载与垃圾回收
  • 每天一个前端小知识 Day 30 - 前端文件处理与浏览器存储机制实践
  • [Rust 基础课程]选一个合适的 Rust 编辑器
  • 通用定时器GPT
  • 输入npm install后发生了什么
  • # 通过wifi共享打印机只有手动翻页正反打印没有自动翻页正反打印,而通过网线连接的主机电脑可以自动翻页正反打印
  • OneCode3.0 VFS分布式文件管理API速查手册
  • Codeforces Round 855 (Div. 3)
  • 【iOS】方法与消息底层分析
  • 动物世界一语乾坤韵芳华 人工智能应用大学毕业论文 -仙界AI——仙盟创梦IDE
  • Docker Compose文件内容解释
  • 鸿蒙选择本地视频文件,并获取首帧预览图
  • 14.ResourceMangaer启动解析
  • 【java】AI内容用SSE流式输出
  • 【读书笔记】《C++ Software Design》第七章:Bridge、Prototype 与 External Polymorphism
  • 数据库3.0
  • IPC框架
  • DAY01:【ML 第一弹】机器学习概述
  • php生成二维码
  • 15.手动实现BatchNorm(BN)