【SpringCloud负载均衡】【源码+图解】【三】LoadBalancer的工作原理

news2024/12/24 20:17:22

【SpringCloud负载均衡】【源码+图解】【二】LoadBalancer配置

目录

  • 3. LoadBalancer的工作原理
    • 3.1 创建LoadBalancerRequest
    • 3.2 创建上下文
      • 3.2.1 properties
      • 3.2.2 configurations
      • 3.2.3 contexts
    • 3.3 获取ReactiveLoadBalancer
    • 3.4 获取ServiceInstance
    • 3.5 向serviceInstance请求结果
      • 3.5.1 LoadBalancerServiceInstanceCookieTransformer
      • 3.5.2 XForwardedHeadersTransformer
      • 3.5.3实现自定义的HeadersTransformer
  • 未完待续

3. LoadBalancer的工作原理

当客户端发起请求后被LoadBalancerInterceptor拦截,看下拦截的方法

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    // 即BlockingLoadBalancerClient
	private LoadBalancerClient loadBalancer;
    // 即LoadBalancerRequestFactory
	private LoadBalancerRequestFactory requestFactory;

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
        // 获取url的host,如上文中UserController中的"http://product/prod",则serviceName(后文也指serviceId)为product
		String serviceName = originalUri.getHost();
		return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
	}

}

当请求被拦截后LoadBalancer的整个工作流程

在这里插入图片描述

具体过程如下:

  1. 将普通httpRequest封装成BlockingLoadBalancerRequest
  2. 如果当前serviceId对应的上下文不存在则创建上下文
  3. 从上下文中获取负载均衡器
  4. 利用负载均衡器选取合适的serviceInstance
  5. 向具体的serviceInstance发送请求

3.1 创建LoadBalancerRequest

public class LoadBalancerRequestFactory {
    public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) {
        // 将普通的HttpRequest包装成LoadBalancerRequest
		return new BlockingLoadBalancerRequest(loadBalancer, transformers,
				new BlockingLoadBalancerRequest.ClientHttpRequestData(request, body, execution));
	}
}

BlockingLoadBalancerRequest的关键在于apply函数,后续再详细分析

3.2 创建上下文

在LoadBalancer中LoadBalancerClientFactory负责管理yml文件中的配置、微服务的应用上下文ApplicationContext,每一个serviceId对应一个ApplicationContext,而这个ApplicationContext中最关键的是提供服务列表的Supplier和选择服务的ReactiveLoadBalancer。先看LoadBalancerClientFactory类图

在这里插入图片描述

然后我们将LoadBalancerClientFactory的属性具象化,方便理解,看下图

在这里插入图片描述

接下来详细介绍下这三个参数

3.2.1 properties

它的载体为LoadBalancerClientsProperties类,我们看下它的类图

在这里插入图片描述

结合类图,我们就可以列出关于loadBalance的相关配置

spring:
  cloud:
	loadbalancer:
      health-check:
        initial-delay: 0s
        interval: 25s
        refetch-instances: false
        refetch-instances-interval: 25s
        path:
          default: /actuator/health
        repeat-health-check: true
      hint:
        my-hint: hint-example
      hint-header-name: "X-SC-LB-Hint"
      retry:
        enabled: true
        retry-on-all-operations: false
        max-retries-on-same-service-instance: 0
        max-retries-on-next-service-instance: 1
        retryable-status-codes:
        - 404
        backoff:
          enabled: false
          min-backoff: 5
          max-backoff: 10 
          jitter: 0.5
      sticky-session:
        instance-id-cookie-name: sc-lb-instance-id
        add-service-instance-cookie: false
      use-raw-status-code-in-response-data: false
      x-forwarded:
        enabled: false
      clients:
        product: 
          # client的配置与前面相同,参考上面的类图

3.2.2 configurations

默认情况下会有下面四个配置类,也就是对于每一个ApplicationContext都会加载的配置,这些配置主要是注入负载均衡所需要的的Balancer和实例提供者Supplier

default.org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration
org.springframework.cloud.netflix.eureka.loadbalancer.EurekaLoadBalancerClientConfiguration
default.org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration
default.org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration

当然也可以自己配置,如下

@LoadBalancerClient(name = "my-loadbalance-config")
public class MyLoadbalanceConfiguration {
    
}

然后在NamedContextFactory.setConfigurations方法打断点,启动后发起http请求既可以看到自定义的配置已经被添加到了configurations

在这里插入图片描述

那么,这些configurations用来干嘛呢?接下来的contexts揭晓

3.2.3 contexts

前面说过,对于每一个不同的serviceId都会创建一个不同的上下文,那么我们分析下创建的过程,即NamedContextFactory.createContext

createContext位于它的父类NamedContextFactory中,接下来看下源码

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
    // name即为serviceId
    protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context;
        // 默认下parent为AnnotationConfigServletWebServerApplicationContext
		if (this.parent != null) {
			......
			context = new AnnotationConfigApplicationContext(beanFactory);
			context.setClassLoader(this.parent.getClassLoader());
		}
		else {
			context = new AnnotationConfigApplicationContext();
		}
        // 也就是说可以通过设置@LoadBalancerClient的name属性给不同的serviceId配置不同的configuration
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
				context.register(configuration);
			}
		}
        // 注册默认的配置,即前面说到的那四个以及@LoadBalancerClient的name不赋值的configuration
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
        // defaultConfigType: LoadBalancerClientConfiguration
		context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
        // 设置loadbalancer.client.name = serviceId
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			context.setParent(this.parent);
		}
		context.setDisplayName(generateDisplayName(name));
        // 刷新上下文,注入configurations中的beans
		context.refresh();
		return context;
	}
}

默认情况下我们看下**context.refresh()**都注入了哪些依赖

在这里插入图片描述

3.3 获取ReactiveLoadBalancer

上一小节结束后对于**LoadBalancerClientFactory.getInstance(String serviceId)**就能明白其中的工作原理了,看下源码

public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
		implements ReactiveLoadBalancer.Factory<ServiceInstance> {
    @Override
	public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
        // 1、首先从contexts中获取serviceId的ApplicationContext
        // 2、从ApplicationContext中获取ReactorServiceInstanceLoadBalancer的bean,默认下是RoundRobinLoadBalancer,即轮询选择器
		return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
	}
}

3.4 获取ServiceInstance

接下来就到**RoundRobinLoadBalancer.choose(Request request)**挑选实例,也就是负载均衡最关键的一步了。

首先看下RoundRobinLoadBalancer的类图,了解下它的功能与属性

在这里插入图片描述

position:上一次选取的实例的下标

serviceId:应用名字

serviceInstanceListSupplierProvider:获取serviceId的所有实例

其次看下RoundRobinLoadBalancer的配置

public class LoadBalancerClientConfiguration {
	@Bean
	@ConditionalOnMissingBean
	public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
			LoadBalancerClientFactory loadBalancerClientFactory) {
        // 注意,这里的name就是前面创建context时在配置的loadbalancer.client.name,也即当前的serviceId
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RoundRobinLoadBalancer(
            // 这里返回的是ClientFactoryObjectProvider,它的作用其实就是LoadBalancerClientFactory的一个中间代理,
            // 这个代理只提供ServiceInstanceListSupplier类
				loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}
}

最后choose的过程

public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    public Mono<Response<ServiceInstance>> choose(Request request) {
        // 默认下为CachingServiceInstanceListSupplier
		ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
				.getIfAvailable(NoopServiceInstanceListSupplier::new);
		return supplier.get(request) // 对于Supplier的工作原理见后面的4.2节
            // 1、先获取符合条件的instances列表
            .next() 
            // 2、从列表中轮询获取单个serviceInstance
			.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances)); 
	}
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
		if (instances.isEmpty()) {
			return new EmptyResponse();
		}
		// 当前pos = lastPos + 1
		int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        // 求余实现轮询
		ServiceInstance instance = instances.get(pos % instances.size());
		return new DefaultResponse(instance);
	}
}

3.5 向serviceInstance请求结果

上一步挑选到serviceinstance后就该到向它发送请求获取结果了,该请求由BlockingLoadBalancerRequest发出.

先看下BlockingLoadBalancerRequest的类图

在这里插入图片描述

BlockingLoadBalancerRequest发出请求是在apply方法,看下源码

class BlockingLoadBalancerRequest implements HttpRequestLoadBalancerRequest<ClientHttpResponse> {
	@Override
	public ClientHttpResponse apply(ServiceInstance instance) throws Exception {
		HttpRequest serviceRequest = new ServiceRequestWrapper(clientHttpRequestData.request, instance, loadBalancer);
		if (this.transformers != null) {
            // LoadBalancerServiceInstanceCookieTransformer和XForwardedHeadersTransformer
			for (LoadBalancerRequestTransformer transformer : this.transformers) {
                // 进一步封装 request
				serviceRequest = transformer.transformRequest(serviceRequest, instance);
			}
		}
        // 底层的InterceptingClientHttpRequest发送http请求获取结果,不再详细分析
		return clientHttpRequestData.execution.execute(serviceRequest, clientHttpRequestData.body);
	}
}

整体过程如下图

在这里插入图片描述

这里着重分析下LoadBalancerServiceInstanceCookieTransformerXForwardedHeadersTransformer,看看两者对request做了什么

3.5.1 LoadBalancerServiceInstanceCookieTransformer

public class LoadBalancerServiceInstanceCookieTransformer implements LoadBalancerRequestTransformer {
    @Override
	public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
        // 如果spring.cloud.loadbalancer.clients.{serviceId}存在,则获取spring.cloud.loadbalancer.clients.{serviceId}.stickySession
        // 否则获取默认的spring.cloud.loadbalancer.stickySession
		LoadBalancerProperties.StickySession stickySession = factory != null
				? factory.getProperties(instance.getServiceId()).getStickySession() : stickySessionProperties;
        // 获取stickySession.instanceIdCookieName,默认下为sc-lb-instance-id
		String instanceIdCookieName = stickySession.getInstanceIdCookieName();
		HttpHeaders headers = request.getHeaders();
		List<String> cookieHeaders = new ArrayList<>(request.getHeaders().getOrEmpty(HttpHeaders.COOKIE));
        // 创建cookie
		String serviceInstanceCookie = new HttpCookie(instanceIdCookieName, instance.getInstanceId()).toString();
		cookieHeaders.add(serviceInstanceCookie);
        // 将cookie放到request的headers
		headers.put(HttpHeaders.COOKIE, cookieHeaders);
		return request;
	}
}

3.5.2 XForwardedHeadersTransformer

public class XForwardedHeadersTransformer implements LoadBalancerRequestTransformer {
	@Override
	public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
        // 如果spring.cloud.loadbalancer.clients.{serviceId}存在,则获取spring.cloud.loadbalancer.clients.{serviceId}.xForwarded
        // 否则获取默认的spring.cloud.loadbalancer.xForwarded
		LoadBalancerProperties.XForwarded xForwarded = factory.getProperties(instance.getServiceId()).getXForwarded();
        // 默认为false
		if (xForwarded.isEnabled()) {
			HttpHeaders headers = request.getHeaders();
			String xForwardedHost = request.getURI().getHost();
			String xforwardedProto = request.getURI().getScheme();
			headers.add("X-Forwarded-Host", xForwardedHost);
			headers.add("X-Forwarded-Proto", xforwardedProto);
		}
		return request;
	}

}

3.5.3实现自定义的HeadersTransformer

// 1、@Component注入spring容器
@Component
// 2、实现LoadBalancerRequestTransformer
public class MyHeadersTransformer implements LoadBalancerRequestTransformer{

    // 3、实现transformRequest方法,添加自定义逻辑
    @Override
    public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
        System.out.println("MyHeadersTransformer");
        return request;
    }

}

未完待续

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

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

相关文章

Java要学到什么程度才可以找工作?

Java为不同的集合提供了一个集合框架。集合基于数据结构&#xff0c;比如常见的&#xff1a;列表、数组、集合、哈希图等等。因此&#xff0c;在研究集合时&#xff0c;最好了解一点数据结构的相关知识。 主要副题&#xff1a; List Set Map ArrayList LinkedList Queue…

web3:智能合约-虚拟机(EVM、HVM、WASM、MOVE)

在区块链上&#xff0c;用户通过运行部署在区块链上的合约&#xff0c;完成需要共识的操作。而为智能合约提供运行环境的便是对应的虚拟机。 目录EVM基础概念技术细节EVM的存储模型交易在EVM的执行普通转账交易智能合约的创建或者调用EVM机器语言与现有的虚拟机科技作比较EVM的…

Java中类的复用

类的复用&#xff08;组合与继承&#xff09; 第一种方法&#xff1a;只需在新类中产生现有类的对象&#xff0c;新类由现有类组成&#xff0c;也称为组合&#xff0c;该方法只是复用了现有程序代码的功能&#xff1b; 第二种方法&#xff1a;按现有类来创建新类&#xff0c;…

m基于LPF-VMD和KELM的鸟群优化算法的风速预测算法matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 1).使用 LPF-VMD 对风速时间序列进行分解&#xff0c; 得到一个低频的趋势分量以及 n 个由 VMD 分解得 到的 BIMF。 2).对 LPF-VMD 分解得到的各分量分别建立 KELM 预测模型&#xff0c;采用 B…

【进阶】C语言第二课:升级你的指针(1)

目录 &#x1f929;前言&#x1f929;&#xff1a; 一、字符指针&#x1f92f;&#xff1a; 1.字符指针的使用&#x1f99d;&#xff1a; 2.常量字符串&#x1f98a;&#xff1a; 3.相关面试题分析&#x1f423;&#xff1a; 二、指针数组&#x1f9d0;&#xff1a; 三、数…

vue+nodejs公益图书借阅捐赠管理系统

公益图书捐赠管理系统 用户信息&#xff1a;id、用户名、密码、捐书数量&#xff08;管理员端可以点击跳转查看详情&#xff09;、上传电子书数量&#xff08;管理员端可以点击跳转查看详情&#xff09;、借阅图书数量&#xff08;管理员端可以点击跳转查看详情&#xff09;&am…

利用全长转录组多重阵列测序检测同源异构体

哈佛大学和麻省理工学院近期发表了“High-throughput RNA isoform sequencing using programmable cDNA concatenation.”研究论文中&#xff0c;将 cDNA 串联成可用于长读长测序最佳的单分子的技术应用于肿瘤浸润 T 细胞的单细胞 RNA 测序,提高了寻找可变剪接基因的准确度&…

秒懂:JCTool 的 Mpsc 高性能无锁队列 (史上最全+10W字长文)

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

C# 修饰符

一 访问控制符 二 static 1 static 的字段、方法、属性是属于整个类的 ① static方法中&#xff0c;不能访问实例变量&#xff1b; ② 调用static方法时&#xff0c;直接用类名访问 Console.Write();Math.Sqrt(); Convert.ToDateTime();DateTime.Parse String.Copy(a);Strin…

【JavaSE】javaSE练习项目——>《简易图书管理系统》

目录 前言&#xff1a; 1、项目实现要求 2、设计思路流程 设计思路&#xff1a; 登录后菜单的实现效果&#xff1a; 3、代码实现&#xff08;大体框架&#xff09; Main类 book包 Book类 BookList类 user包 User类 AdminUser(管理员)类 NormalUser&#xff08;普通…

Hibernate Validator 使用详解

目录 Hibernate Validator的依赖 Hibernate Validator 支持注解 空与非空检查 Boolean值检查 日期检查 数值检查 其他 Hibernate-validator扩展约束 Hibernate Validator 校验 简单对象校验 嵌套对象校验 Hibernate Validator 分组校验 静态分组 动态分组 动态分…

【软件测试】刚入职后,快速适应新的工作需要做啥?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 入职一家新公司后&a…

基于AD8226的环境光传感器电路

人们越来越多地认为环境光是一种能源,可用于驱动心率监控器、浴室灯具、远程天气传感器和其他低功耗器件。对于能量采集系统,最关键的是精确测量环境光的能力。本设计思路将描述一种简单的低成本电路,可以根据环境光的强度按一定比例提供电压。 所用传感器是一款光敏电阻(L…

spring framework 容器

org.springframework.beans 和 org.springframework.context 包是 Spring Framework 的 IoC 容器的基础。 这里需掌握两个体系结构&#xff0c;BeanFactory 和 ApplicationContext。 BeanFactory 主要接口&#xff0c;可分为三级&#xff1a; BeanFactory 是顶层容器&#xf…

Nacos 配置中心之主动拉取

客户端 客户端的配置有两种方式来维持,一是客户端主动拉取,而是客户端长轮询更新 配置文件的种类 1、本地配置文件: 本地就已经存在的配置文件 2、 本地缓存文件: 从服务端获取的保存在了本地 (本地生成了文件) 3、 cacheData 缓存数据: 内存中缓存的配置文件数据 客户端主动获…

【分享】学浪PC端登录分析及实现

本文所有教程及源码、软件仅为技术研究。不涉及计算机信息系统功能的删除、修改、增加、干扰,更不会影响计算机信息系统的正常运行。不得将代码用于非法用途,如侵立删!学浪PC端登录分析及实现 环境 win10Fiddlerchrome学浪PC端登录:aHR0cHM6Ly9zdHVkZW50LWFwaS5peWluY2Fpc2…

AE 动效制作和交付方案

在界面设计中&#xff0c;设计师利用动效让整个界面更加活泼&#xff0c;给界面元素带来生命力&#xff0c;解决功能上的问题&#xff0c;在更好地展示产品功能的基础上&#xff0c;凸显品牌的特色。而作为用户&#xff0c;动效增强了体验者的审美感受、情感需要&#xff0c;让…

德才论

目录 1015:德才论 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 思路: 1.技巧&#xff1a; 1.2结构体代码: 2.分类: 1.德才分类 1.2德才分类代码: 2.cmp函数 2.1 cmp函数代码: 3.超时问题(易错&#xff0c;算法要优化) 总代码…

神经网络优化

提升深度神经网络&#xff1a;超参数调节&#xff0c;正则化&#xff0c;优化 之前已经学习了如何构建神经网络&#xff0c;本章将继续学习如何有效运行神经网络&#xff0c;内容涉及超参数调优&#xff0c;如何构建数据以及如何确保优化算法快速运行&#xff0c;从而使学习算…

LVM卷在线扩容报错:resize would cause inodes_count overflow

一、问题描述 某次在线环境&#xff0c;存储使用率告警在线扩容时&#xff0c;文件系统扩容失败&#xff0c;报错如下&#xff1a; Size of logical volume sihua/video changed from <162.00 TiB (42467321 extents) to <258.00 TiB (67633142 extents).Logical volu…