【SpringCloud】Ribbon源码解析

news/2024/7/8 9:21:29 标签: spring cloud, ribbon, java

e246a1dda09849a5a89535a62441565d.png

ribbon是一个负载均衡组件,它可以将请求分散到多个服务提供者实例中,提高系统的性能和可用性。本章分析ribbon是如何实现负载均衡的

1、@LoadBalanced

消费者在引入ribbon组件后,给http客户端添加@LoadBalanced注解就可以启用负载均衡功能。@LoadBalanced注解比较简单,本身没有包含什么业务逻辑,值得一提的是@Qualifier注解。@Qualifier通常配合@Autowired一起使用,当有多个同类型的bean时可以根据@Qualifier指定的bean名称完成注入;此外在bean的入口和出口使用@Qualifier修饰,也能建立稳定的供应关系,ribbon收集所有被@LoadBalanced修饰的restTemplate,用的就是这种方法

java">@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
java">@Component
@Qualifier
public class A {}


public class B {
  
   // 精准注入A
   @Autowired
   @Qualifier
   private A a;
}

2、LoadBalancerAutoConfiguration

和负载均衡相关的配置在LoadBalancerAutoConfiguration内,查看spring-cloud-common包的spring.factories文件,我们知道这是一个自动配置类

java"># AutoConfiguration
...
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
...

LoadBalancerAutoConfiguration注入了所有使用@LoadBalanced修饰的restTemplate,为什么加了@LoadBalanced就能引入其他其他被@LoadBalanced修饰的bean,原因就是1中提到的@Qualifier。

LoadBalancerAutoConfiguration内部还有一个loadBalancedRestTemplateInitializerDeprecated方法,这个方法会对restTemplate做定制化处理

java">@LoadBalanced
@Autowired(
	required = false
)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
	return () -> {
		restTemplateCustomizers.ifAvailable((customizers) -> {
			Iterator var2 = this.restTemplates.iterator();

			while(var2.hasNext()) {
				RestTemplate restTemplate = (RestTemplate)var2.next();
				Iterator var4 = customizers.iterator();

				while(var4.hasNext()) {
					RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
					// 定制化处理
					customizer.customize(restTemplate);
				}
			}

		});
	};
}

RestTemplateCustomizer接口的实现类在LoadBalancerAutoConfiguration内,它们的定制化处理其实就是给restTemplate添加RetryLoadBalancerInterceptor拦截器

java">@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
	return (restTemplate) -> {
		List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
		list.add(loadBalancerInterceptor);

		// 客户端添加拦截器
		restTemplate.setInterceptors(list);
	};
}

3、RetryLoadBalancerInterceptor

查看RetryLoadBalancerInterceptor的拦截方法,核心语句是loadBalancer.choose,也就是说服务实例的选择功能其实是由LoadBalancerClient接口实现类完成的

java">private final LoadBalancerClient loadBalancer;

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
	...

		if (serviceInstance == null) {
			if (LOG.isDebugEnabled()) {
				LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. Reattempting service instance selection");
			}

			// 选择服务实例
			serviceInstance = this.loadBalancer.choose(serviceName);
			if (LOG.isDebugEnabled()) {
				LOG.debug(String.format("Selected service instance: %s", serviceInstance));
			}
		}
		...
		ClientHttpResponse response = (ClientHttpResponse)
		this.loadBalancer.execute(serviceName, serviceInstance, this.requestFactory.createRequest(request, body, execution));       
	...
}

RibbonLoadBalancerClient是LoadBalancerClient接口实现类之一,查看它的choose方法

java">// 选择合适的服务实例
public ServiceInstance choose(String serviceId, Object hint) {
	Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
	return server == null ? null : new RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
}

// 选择合适的服务器
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
	return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}

选择服务器的任务移交给了ILoadBalancer的实现类,如ZoneAwareLoadBalancer。ZoneAwareLoadBalancer会调用父类BaseLoadBalancer的choose方法,按照指定的策略选取服务,如轮询、加权等

java">public Server chooseServer(Object key) {
	if (this.counter == null) {
		this.counter = this.createCounter();
	}

	this.counter.increment();
	if (this.rule == null) {
		return null;
	} else {
		try {
			// 按规则选择服务
			return this.rule.choose(key);
		} catch (Exception var3) {
			logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
			return null;
		}
	}
}

再聊聊服务的来源,LoadBalancer是从哪里挑选的服务?

ZoneAwareLoadBalancer在创建时会调用父类DynamicServerListLoadBalancer的构造方法,然后调用updateListOfServers方法获取服务列表

java">public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping, ServerList<T> serverList, ServerListFilter<T> filter, ServerListUpdater serverListUpdater) {
	...
	this.restOfInit(clientConfig);
}

void restOfInit(IClientConfig clientConfig) {
	...
	this.updateListOfServers();
	...
}

@VisibleForTesting
public void updateListOfServers() {
	List<T> servers = new ArrayList();
	if (this.serverListImpl != null) {

		// 获取服务列表
		servers = this.serverListImpl.getUpdatedListOfServers();
		LOGGER.debug("List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
		if (this.filter != null) {
			servers = this.filter.getFilteredListOfServers((List)servers);
			LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}", this.getIdentifier(), servers);
		}
	}

	this.updateAllServerList((List)servers);
}

看到服务实例自然联想到eureka,跟踪getUpdatedListOfServers方法

java">调用链:
-> DynamicServerListLoadBalancer.updateListOfServers
-> this.serverListImpl.getUpdatedListOfServers;
-> DiscoveryEnabledNIWSServerList.obtainServersViaDiscovery
-> eurekaClient.getInstancesByVipAddress
java">private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
	...
	// eureka客户端拉取服务
	List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, this.isSecure, this.targetRegion);
	Iterator var8 = listOfInstanceInfo.iterator();
	...
}

eureka客户端从远程拉取服务实例,然后ribbon从服务实例中挑选服务

3、总结

ribbon组件向restTemplate中添加拦截器,实现负载均衡功能增强


http://www.niftyadmin.cn/n/5536901.html

相关文章

一些感想。

1.double必须用double的输出&#xff08;“%lf”&#xff09; 我还以为是什么bug。。 2.sqrt&#xff0c;pow只要include cmath之后就能用了&#xff0c;我pow()没有devc艹的提示&#xff0c;还以为我记错了&#xff0c;早知道运行一下了 cnm公式写错了 #include <iostre…

MacOS下更新curl

苹果自带的curl不支持Https&#xff0c;我们可以通过curl -V看到如下结果 curl 7.72.0 (x86_64-apple-darwin18.6.0) libcurl/7.72.0 zlib/1.2.12 libidn2/2.3.7 librtmp/2.3 Release-Date: 2020-08-19 Protocols: dict file ftp gopher http imap ldap ldaps pop3 rtmp rtsp …

MISRA C 和MISRA C++:汽车软件安全的守护者

一、MISRA C与C语言 自1972年Dennis MacAlistair Ritchie在美国贝尔实验室创造C语言以来&#xff0c;它已成为当今最流行的编程语言之一。C语言以其使用的灵活性、功能的丰富性而广受欢迎&#xff0c;但同时也因其宽松的语法和不严格的数据类型给开发的产品带来了安全隐患。 …

【数据可视化01】matplotlib实例介绍6之极坐标图

目录 一、引言二、实例介绍一、引言 在极轴上的线形图的演示。 二、实例介绍 import matplotlib.pyplot as plt import numpy as npr = np.arange(0, 2, 0.01) theta = 2 * np.pi * rfig, ax

【计算机视觉系列实战教程 (实战02)】:基于特征点匹配的图像配准

这里写目录标题 1、特征点提取(1)GFTT算法提取特征点A.What&#xff08;什么是GFTT&#xff09;B.GFTT的优势C.How&#xff08;如何使用GFTT算法提取图像特征点&#xff09; (2)FAST算法提取特征点A.What&#xff08;什么是FAST角点&#xff09;B.FAST角点的强度值C.How&#x…

elementPlus表格二次封装

为何要对element-plus表格进行二次封装&#xff1f; 我们正常在开发项目中&#xff0c;表格的风格是一致的&#xff0c;但是表格或多或少会有些不同&#xff0c;有些是需要分页&#xff0c;有些是按钮功能不同&#xff0c;有些又需要加Tag&#xff0c;或者对时间进行格式化等。…

学会python——用python制作一个登录和注册窗口(python实例十八)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.登录和注册窗口 3.1 代码构思 3.2 代码实例 3.3 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读…

Rust简明教程第四章-其他类型泛型

观看B站软件工艺师杨旭的rust教程学习记录&#xff0c;有删减有补充 枚举 枚举类型可以让我们的程序使用一些固定长度和固定数值的变量值范围 enum 枚举 枚举类型中的值被称为变体 enum IpAddrKind{V4,//变体V6,//变体 } fn main(){let four IpAddrKind::V4;let six IpA…