LoadBalancer源码解析

news2024/11/16 9:21:30

文章目录

  • 一、背景
  • 二、总体流程
  • 三、源码解析
    • 1. lb拦截器配置
    • 2. LB拦截器实现
    • 3. LB执行前置处理
    • 4. 负载均衡
    • 5. LB执行http请求


一、背景

Spring Cloud 2020版本以后,默认移除了对Netflix的依赖,其中就包括Ribbon,官方默认推荐使用Spring Cloud Loadbalancer正式替换Ribbon,并成为了Spring Cloud负载均衡器的唯一实现。LoadBalancer也可以看做是一种进程级的LB,后面用LB代指LoadBalancer。

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

二、总体流程

主要是通过LoadBalancerInterceptor添加到restTemplate中,加入了负载均衡的策略,完成负载均衡的功能。
在这里插入图片描述

三、源码解析

1. lb拦截器配置

首先通过spring.facotries配置,自动条件加载LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig配置,完成拦截器的配置。

	@Configuration(proxyBeanMethods = false)
	@Conditional(RetryMissingOrDisabledCondition.class)
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			// 创建默认的LB拦截器
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				// 将LB拦截器配置到RestTemplate中
				restTemplate.setInterceptors(list);
			};
		}

	}

2. LB拦截器实现

LB拦截器里面实现了intercept方法,将LB负载均衡逻辑的需要的参数装填好,调用LoadBancerClient的execute方法

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		// 服务名
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
		// 将HttpRequest、body、execution 保证成LoadBalancerRequest,具体下面源码
		return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
	}
public class LoadBalancerRequestFactory {

	private final LoadBalancerClient loadBalancer;

	private final List<LoadBalancerRequestTransformer> transformers;

	public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer,
			List<LoadBalancerRequestTransformer> transformers) {
		this.loadBalancer = loadBalancer;
		this.transformers = transformers;
	}

	public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer) {
		this.loadBalancer = loadBalancer;
		transformers = new ArrayList<>();
	}

	public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) {
		// 将LoadBalancerClient, 请求转换器,HttpRequest,body,execution设置到BlockingLoadBalancerRequest
		return new BlockingLoadBalancerRequest(loadBalancer, transformers,
				new BlockingLoadBalancerRequest.ClientHttpRequestData(request, body, execution));
	}

}

3. LB执行前置处理

LB执行前前置处理,主要处理灰度配置,LB监控、根据配置的负载均衡策略选择调用实例

	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
	    // 获取灰度配置,默认headerName=X-SC-LB-Hint
		String hint = getHint(serviceId);
		// 将灰度配置放单LBRequest中
		LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
				buildRequestContext(request, hint));
		// 获取支持当前服务的LB生命周期管理实例
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
		// 执行onStart方法
		supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
		// 根据服务名、请求,选择服务实例
		ServiceInstance serviceInstance = choose(serviceId, lbRequest);
		if (serviceInstance == null) {
			supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
					new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		// 实际http请求
		return execute(serviceId, serviceInstance, lbRequest);
	}
	// 负载均衡策略选择实例
	@Override
	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();
	}
	// 获取灰度配置
	private String getHint(String serviceId) {
		LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);
		String defaultHint = properties.getHint().getOrDefault("default", "default");
		String hintPropertyValue = properties.getHint().get(serviceId);
		return hintPropertyValue != null ? hintPropertyValue : defaultHint;
	}

4. 负载均衡

目前负载均衡算法主要由两个实现RandomLoadBalancer,RoundRobinLoadBalancer,目前代码以RoundRobinLoadBalancer为例进行分析。

	@Override
	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;
	}

此外,负载均衡还和ServiceInstanceListSupplier#get方法有关,灰度策略就是在HintBasedServiceInstanceListSupplier重新实现了get方法,实现了灰度调度功能,源码分析以HintBasedServiceInstanceListSupplier为例进行分析。

	@Override
	public Flux<List<ServiceInstance>> get(Request request) {
		return delegate.get(request).map(instances -> filteredByHint(instances, getHint(request.getContext())));
	}
	
	private List<ServiceInstance> filteredByHint(List<ServiceInstance> instances, String hint) {
		if (!StringUtils.hasText(hint)) {
			return instances;
		}
		List<ServiceInstance> filteredInstances = new ArrayList<>();
		for (ServiceInstance serviceInstance : instances) {
			// 根据灰度策略过滤服务实例
			if (serviceInstance.getMetadata().getOrDefault("hint", "").equals(hint)) {
				filteredInstances.add(serviceInstance);
			}
		}
		if (filteredInstances.size() > 0) {
			return filteredInstances;
		}

		// If instances cannot be found based on hint,
		// we return all instances retrieved for given service id.
		return instances;
	}

5. LB执行http请求

最后就是进行http请求。

@Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)
			throws IOException {
		DefaultResponse defaultResponse = new DefaultResponse(serviceInstance);
		Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
		Request lbRequest = request instanceof Request ? (Request) request : new DefaultRequest<>();
		supportedLifecycleProcessors
				.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance)));
		try {
			// 实际执行http请求
			T response = request.apply(serviceInstance);
			LoadBalancerProperties properties = loadBalancerClientFactory.getProperties(serviceId);
			Object clientResponse = getClientResponse(response, properties.isUseRawStatusCodeInResponseData());
			supportedLifecycleProcessors
					.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
							lbRequest, defaultResponse, clientResponse)));
			return response;
		}
		catch (IOException iOException) {
			supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
					new CompletionContext<>(CompletionContext.Status.FAILED, iOException, lbRequest, defaultResponse)));
			throw iOException;
		}
		catch (Exception exception) {
			supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
					new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, defaultResponse)));
			ReflectionUtils.rethrowRuntimeException(exception);
		}
		return null;
	}
	@Override
	public ClientHttpResponse apply(ServiceInstance instance) throws Exception {
	    // 修改httpRequest的uri中host为选择服务的地址,具体见ServiceRequestWrapper 
		HttpRequest serviceRequest = new ServiceRequestWrapper(clientHttpRequestData.request, instance, loadBalancer);
		if (this.transformers != null) {
			for (LoadBalancerRequestTransformer transformer : this.transformers) {
			    // 请求转换器,处理httprequest
				serviceRequest = transformer.transformRequest(serviceRequest, instance);
			}
		}
		// 跳转回http处理链,进行真正http请求
		return clientHttpRequestData.execution.execute(serviceRequest, clientHttpRequestData.body);
	}
public class ServiceRequestWrapper extends HttpRequestWrapper {

	private final ServiceInstance instance;

	private final LoadBalancerClient loadBalancer;

	public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
		super(request);
		this.instance = instance;
		this.loadBalancer = loadBalancer;
	}

	@Override
	public URI getURI() {
		// 将原uri中host替换为实例的地址,这个是LB真正的目的
		URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		return uri;
	}

}

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

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

相关文章

生物化学 电阻抗成像OpenEIT 番外篇 EIT公式

EIT简介 摘要电阻抗断层扫描&#xff08;EIT&#xff09;是一种成像方式&#xff0c;使用无害的电流探测患者或物体。电流通过放置在靶表面上的电极馈送&#xff0c;数据由在电极处测量的电压组成&#xff0c;这些电压由一组线性独立的电流注入模式产生。EIT旨在恢复目标内部电…

【MySQL】第八部分 加密和解密函数

【MySQL】第八部分 加密和解密函数 文章目录【MySQL】第八部分 加密和解密函数8. 加密和解密函数总结8. 加密和解密函数 函数用法PASSWORD(str)返回字符串str的加密版本&#xff0c;41位长的字符串。加密结果不可逆&#xff0c;常用于用户的密码加密.( 8.0 版本以上不能用)MD5…

海湾化学冲刺上交所上市:计划募资30亿元,华融曾是其股东

近日&#xff0c;青岛海湾化学股份有限公司&#xff08;下称“海湾化学”&#xff09;预披露招股书&#xff0c;准备在上海证券交易所主板上市。本次冲刺上市&#xff0c;海湾化学计划募资30亿元&#xff0c;将于37.5万吨/年环氧氯丙烷绿色循环经济项目&#xff08;一期&#x…

23种设计模式(十七)——状态模式【状态变化】

状态模式 文章目录 状态模式意图什么时候使用状态真实世界类比状态模式的实现状态模式的优缺点亦称:State 意图 对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。 状态模式的解决思想是:当控制一个对象状态转…

【沐风老师】教你用3dMax柱子插件Pillars打造欧式罗马柱建模(附柱子插件下载)

Pillars是一个很棒的3dMax插件。Pillars可以创建柱子、柱状物、栏杆、喷泉&#xff08;喷泉建筑的造型&#xff09;和各种类似形状的三维模型。它也会破坏&#xff08;打断&#xff09;这些模型。有了它&#xff0c;您只需在几秒钟内就能制作出外观精美的柱子和效果逼真的断面效…

4、JDK相关设置

文章目录4、JDK相关设置4.1 设置项目的JDK4.2 设置编译版本4.3 设置out目录【尚硅谷】idea实战教程-讲师&#xff1a;宋红康 生活是属于每个人自己的感受&#xff0c;不属于任何别人的看法 4、JDK相关设置 4.1 设置项目的JDK SDK&#xff1a;软件开发工具包 JDK&#xff1a;Ja…

灭火器摆放识别检测算法 yolo

灭火器摆放识别检测算法通过pythonyolo网络深度学习技术&#xff0c;自动对指定区域灭火器是否缺失进行识别&#xff0c;如果 没有检测到指定区域有灭火器&#xff0c;立即抓拍存档进行告警。YOLO系列算法是一类典型的one-stage目标检测算法&#xff0c;其利用anchor box将分类…

Elasticsearch基本使用初体验02

1.Java API操作ES 1.1 创建项目 创建spring Boot工程&#xff0c;添加相关的依赖。 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><…

基于ssm校园学生协会管理系统jsp校园社团管理系统源码和论文

1、开发环境 &#xff08;1&#xff09;操作系统: Windows10 &#xff08;2&#xff09;数据库与数据库管理工具: MySQL 5.7.19、Navicat for MySQL &#xff08;3&#xff09;Web服务器: Tomcat8.5.38 &#xff08;4&#xff09;开发工具与技术: Eclipse IDEA、SSM框架、Ajax …

2023寒假算法集训营3

&#xff08;数学场真折磨人&#xff09; A. 不断减损的时间&#xff08;贪心&#xff09; 题意&#xff1a; 给定一个数组&#xff0c;任意次操作&#xff0c;每次操作可以 选择一个偶数除以 222 。 求最终数组所有元素之和的最小值。 思路&#xff1a; 要使得所有元素之…

三十四、Kubernetes1.25中Ingress介绍、安装

1、介绍 在前面文章中已经提到&#xff0c;Service对集群之外暴露服务的主要方式有两种&#xff1a;NotePort和LoadBalancer&#xff0c;但是这两种方式&#xff0c;都有一定的缺点&#xff1a; NodePort方式的缺点是会占用很多集群机器的端口&#xff0c;那么当集群服务变多的…

【JavaSE专栏2】JDK、JRE和JVM

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

Nacos-统一配置中心

Nacos-统一配置中心统一配置管理1.Nacos编写配置文件2.微服务拉取配置&#xff08;1&#xff09;引入pom依赖&#xff08;2&#xff09;添加bootstrap.yaml&#xff08;3&#xff09;读取nacos配置3.配置热更新&#xff08;1&#xff09;方式一&#xff08;2&#xff09;方式二…

Ubuntu显示优化 动画

之前从win转到了ubuntu。老大哥问我为啥不直接用Mac。我笑笑没说话。其实就一个字&#xff0c;穷。 使用Ubuntu的过程中有一点小问题&#xff0c;不过平时我主要用来编程&#xff0c;对壁纸&#xff0c;过渡动画这些东西其实并不是很在乎。直到我审美感爆棚的妻子告诉我&#…

设计模式学习(十一):Builder建造者模式

一、什么是Builder模式大都市中林立着许多高楼大厦&#xff0c;这些高楼大厦都是具有建筑结构的大型建筑。通常&#xff0c;建造和构建这种具有建筑结构的大型物体在英文中称为Build。在建造大楼时&#xff0c;需要先打牢地基&#xff0c;搭建框架&#xff0c;然后自下而上地一…

AcWing 278. 数字组合

AcWing 278. 数字组合一、问题二、思路1、状态表示2、状态转移3、循环设计4、初末状态三、代码一、问题 二、思路 这道题其实看上去和我们的01背包问题是非常相似的。如果这道题我们转化为01背包问题的话&#xff0c;描述如下&#xff1a; 给很多个物品和体积&#xff0c;然后…

深入理解Mysql底层数据结构

一. 索引的本质 索引是帮助MySQL高效获取数据的排好序的数据结构。 二. 索引的数据结构 二叉树红黑树Hash表BTreeBTree mysql的索引采用的是B树的结构 mysql为什么不用二叉树&#xff0c;因为对于单边增长的数据列&#xff0c;二叉树和全表扫描差不多&#xff0c;效率没有什…

pytorch 神经网络笔记-RNN和LSTM

文章目录时间序列表示方法一般过程RNNRNN原理1RNN原理2RNN layer使用pytorch实现nn.RNN__init__forwardSingle layer RNN2 layer RNNpytorch实现nn.RNNCell时间序列波形预测例子LSTMnn.LSTMnn.LSTMCellSingle layerTwo Layersb站课程链接课时自己找一下 时间序列表示方法 卷积神…

自注意力(Self-Attention)机制浅析

一、自注意力机制概述循环神经网络由于信息传递的容量以及梯度消失问题&#xff0c;实际上也只能建立短距离依赖关系。为了建立长距离的依赖关系&#xff0c;可以增加网络的层数或者使用全连接网络。但是全连接网络无法处理变长的输入序列&#xff0c;另外&#xff0c;不同的输…

字节青训前端笔记 | Web安全

在网络时代下&#xff0c;Web 安全随处可见并且危害极大&#xff0c;Web 安全问题也越来越受到重视。本节课将讲述Web中的攻击和防御 XSS 利用开发者盲目信任客户提交的内容来实现的工具&#xff0c;恶意攻击者往 Web 页面里插入恶意 Script 代码&#xff0c;当用户浏览该页面…