首页 > 编程语言 >【深入理解SpringCloud微服务】Ribbon源码解析

【深入理解SpringCloud微服务】Ribbon源码解析

时间:2024-08-10 09:56:50浏览次数:13  
标签:... 拦截器 ILoadBalancer SpringCloud RestTemplate 源码 LoadBalancerInterceptor Ribbon

【深入理解SpringCloud微服务】Ribbon源码解析

Ribbon的原理

RestTemplate中的拦截器链

在深入理解Ribbon之前,我们先要研究一下RestTemplate。RestTemplate是Spring提供的一个用于发送http请求的工具类。而在RestTemplate的父类InterceptingHttpAccessor里面有这么个东西:

public abstract class InterceptingHttpAccessor extends HttpAccessor {

	private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
	...
}	

在这里插入图片描述

interceptors是RestTemplate里面的拦截器,拦截器类型是ClientHttpRequestInterceptor。当我们使用RestTemplate时,在RestTemplate真正发出http请求之前,请求会经过interceptors这个拦截器链处理。于是,我们可以通过给RestTemplate添加拦截器,对RestTemplate即将发送的请求做各种拦截处理。

在这里插入图片描述

Ribbon的拦截器

比如Ribbon的拦截器做的处理就是url重构,url中的域名部分原先是某微服务的服务名,重构后替换成真正的ip端口。

在这里插入图片描述

当然完整的流程应该是先通过负载均衡策略选出一个服务实例,然后再根据该实例的ip和port重构url。

在这里插入图片描述

如何将拦截器放入到RestTemplate中

在RestTemplate的父类InterceptingHttpAccessor中有一个getInterceptors() 方法:

	public List<ClientHttpRequestInterceptor> getInterceptors() {
		return this.interceptors;
	}

可以直接获取到它的拦截器链,于是我们可以调用restTemplate.getInterceptors()获取到拦截器链,然后调用list.add(interceptor)把我们的拦截器interceptor添加到RestTemplate的拦截器链中。

在这里插入图片描述

经过上面这一套动作,就可以把我们的拦截器添加到RestTemplate,但是这一套需要有一个时机以及地点去触发它。而Ribbon就利用了Spring提供的SmartInitializingSingleton这个扩展点去触发。

public interface SmartInitializingSingleton {

	void afterSingletonsInstantiated();

}

Spring会在完成了所有非懒加载单例bean的初始化之后调用注册到Spring容器中的所有SmartInitializingSingleton的afterSingletonsInstantiated()方法。于是Ribbon实现了一个SmartInitializingSingleton并注册到Spring中,在afterSingletonsInstantiated()方法中把Ribbon自己的拦截器设置到RestTemplate中。

在这里插入图片描述

Ribbon中的核心类

Ribbon中的核心类一共就以下那么几个:
在这里插入图片描述

LoadBalancerAutoConfiguration

LoadBalancerAutoConfiguration是Ribbon的自动配置类,会被SpringBoot的自动装配机制加载。LoadBalancerAutoConfiguration利用Spring的扩展点SmartInitializingSingleton设置Ribbon自己的拦截器LoadBalancerInterceptor到RestTemplate的拦截器链中。

在这里插入图片描述

LoadBalancerInterceptor

LoadBalancerInterceptor是Ribbon的拦截器,会被添加到RestTemplate的拦截器链中。当调用RestTemplate发起http请求时,请求会经过该拦截器进行负载均衡以及url重写的处理。

在这里插入图片描述

LoadBalancerClient

LoadBalancerClient是Ribbon的负载均衡客户端,会被LoadBalancerInterceptor调用,负载均衡以及url重写的工作其实是由LoadBalancerClient完成的。

在这里插入图片描述

当LoadBalancerInterceptor拿到原始url时,会从url中取出服务名serviceName(也就是域名部分),然后把serviceName传给LoadBalancerClient。LoadBalancerClient根据serviceName取得对应的ILoadBalancer,调用ILoadBalancer进行负载均衡处理,ILoadBalancer返回负载均衡选出的一个服务实例并返回。LoadBalancerClient拿到返回的服务实例,会把url中的域名(也就是服务名serviceName)改写为该实例的ip和port。

在这里插入图片描述

ILoadBalancer

ILoadBalancer是Ribbon的负载均衡器,ILoadBalancer是与服务名一一对应的,也就是每一个服务名对应一个ILoadBalancer,不同服务间的ILoadBalancer互不相干。LoadBalancerClient会通过服务名作为key从loadBalancerMap中获取对应的ILoadBalancer负载均衡器,通过ILoadBalancer的负载均衡选出一个服务实例。

在ILoadBalancer的实现类BaseLoadBalancer有四个比较重要的字段:

  • List<Server> allServerList:所有的服务实例
  • List<Server> upServerList:在线的服务实例
  • IRule rule:负载均衡策略
  • IPing ping:用于ping一个服务实例以检查是否存活的工具

ILoadBalancer使用IRule负载均衡策略从upServerList选出一个服务实例Server。然后ILoadBalancer内部有个定时任务通过IPing去ping在allServerList中的每个服务实例,得出所有在线的Server,然后更新upServerList。

在这里插入图片描述

然后在BaseLoadBalancer的子类DynamicServerListLoadBalancer中还有一个重要的字段:

  • ServerList<T> serverListImpl:向注册中心拉取服务实例列表的工具

DynamicServerListLoadBalancer有一个定时任务从调用serverListImpl拉取最新的服务实例列表,更新到allServerList中。

于是这ILoadBalancer中的这五者就是以下这样的关系:

在这里插入图片描述

ServerList

ServerList是一个用于从注册中心拉取服务实例列表的接口,不同的注册中心客户端会有不同的ServerList实现,比如nacos的NacosServerList就是通过NamingService去拉取。

在这里插入图片描述

IRule

IRule是Ribbon的负载均衡规则接口,不同的负载均衡策略有不同的IRule实现。

  • RandomRule:随机选择一个Server
  • RoundRobinRule:轮询选择
  • WeightedResponseTimeRule:根据响应时间加权的轮询策略,响应时间越长,权重越小
  • BestAvailableRule:最小并发优先策略,在众多Server中选取并发数最小的
  • ZoneAvoidanceRule:默认的负载均衡策略,在有区域的环境下,会复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,就是轮询,因此默认的负载均衡策略就是轮询

在这里插入图片描述

IPing

Iping接口就是用于判断一个Server是否可用的接口,有一个boolean isAlive(Server server)方法需要实现,判断当前这个server是否可用。

在这里插入图片描述

Ribbon核心源码解析

LoadBalancerAutoConfiguration

@Configuration(...)
...
public class LoadBalancerAutoConfiguration {

	// 把被@LoadBalanced注解修饰的RestTemplate通过Spring自动注入的方式收集到这里
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	...

	// 通过Spring提供的SmartInitializingSingleton扩展点,
	// 调用RestTemplateCustomizer把LoadBalancerInterceptor放入RestTemplate的拦截器链中
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

	...
		
		// Ribbon的负载均衡拦截器,会被放入到RestTemplate的拦截器链中
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		// Ribbon通过RestTemplateCustomizer
		// 把LoadBalancerInterceptor放入到RestTemplate的拦截器链中
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	...

}

LoadBalancerAutoConfiguration中配置了许多bean,但是重点就是以上三个。LoadBalancerAutoConfiguration通过Spring的扩展点SmartInitializingSingleton调用RestTemplateCustomizer,通过RestTemplateCustomizer把LoadBalancerInterceptor放入到RestTemplate的拦截器链中。

在这里插入图片描述

LoadBalancerInterceptor

RestTemplate的拦截器链中有了LoadBalancerInterceptor之后,当我们使用RestTemplate发起http请求时,就会经过LoadBalancerInterceptor的拦截处理。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	// 负载均衡客户端
	private LoadBalancerClient loadBalancer;

	// Request对象工厂 
	private LoadBalancerRequestFactory requestFactory;

	...

	// RestTemplate发起http请求,会经过LoadBalancerInterceptor的intercept方法拦截处理
	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		// LoadBalancerInterceptor的intercept方法,调用LoadBalancerClient的execute方法。
		// 传入了服务名serviceName,requestFactory创建的Request对象
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

}

RestTemplate发起的http请求会被LoadBalancerInterceptor的intercept方法拦截,LoadBalancerInterceptor的intercept方法从url中取出域名部分作为服务名serviceName,然后调用LoadBalancerClient的execute方法。

在这里插入图片描述

RibbonLoadBalancerClient

LoadBalancerClient接口的实现类是RibbonLoadBalancerClient。

public class RibbonLoadBalancerClient implements LoadBalancerClient {

	...

	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		// 根据服务名(serviceId就是服务名),取得对应的ILoadBalancer
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// getServer方法里面通过ILoadBalancer选出一个服务实例Server
		Server server = getServer(loadBalancer, hint);
		...
		// Server包装为RibbonServer
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, ...);
		// 调用execute方法(重写url,发起请求)
		return execute(serviceId, ribbonServer, request);
	}

	...

	@Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException {
		...
			// 调用LoadBalancerRequest的apply方法(重写url,发起请求)
			T returnVal = request.apply(serviceInstance);
		...
	}

	...

	protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		// 调用ILoadBalancer的chooseServer方法选取一个服务实例
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

	...
	
}

RibbonLoadBalancerClient首先调用getLoadBalancer(serviceId),根据服务名(serviceId就是服务名),取得对应的ILoadBalancer。然后通过ILoadBalancer的chooseServer方法选取一个服务实例Server。最后选出的服务实例作为参数,调用LoadBalancerRequest的apply方法进行下一步操作(重写url,发起请求)

在这里插入图片描述

ILoadBalancer

我们看看ILoadBalancer中的chooseServer方法是如何选出实例的。默认的ILoadBalancer就是ZoneAwareLoadBalancer,由于我们一般不会配置zone(区域),所以最后还是会进入父类BaseLoadBalancer的chooseServer方法。

    public Server chooseServer(Object key) {
       ...
                return rule.choose(key);
       ...
    }

就是调用IRule的choose方法,默认就是轮询策略。

在这里插入图片描述

request.apply(serviceInstance)

我们再看看上面request.apply(serviceInstance)这行代码里面的逻辑。

这里的request是LoadBalancerRequestFactory的createRequest创建的。

	public LoadBalancerRequest<ClientHttpResponse> createRequest(
			final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) {
		return instance -> {
			// 以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象
			HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
					this.loadBalancer);
			...
			return execution.execute(serviceRequest, body);
		};
	}

可以看到LoadBalancerRequestFactory返回的request对象的apply方法里面,以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象。然后调用execution.execute(serviceRequest, body)方法。

		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
			...
				// request.getURI()调用到ServiceRequestWrapper的getURI()方法,里面会重写url,然后返回一个重写了url的URI对象
				// 然后这里的requestFactory(ClientHttpRequestFactory类型)拿着重写后的URI对象,创建一个新的ClientHttpRequest对象
				ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
			...
				// 调用ClientHttpRequest的execute()方法真正发起请求,返回ClientHttpResponse(响应结果)
				return delegate.execute();
		}

我们看一下ServiceRequestWrapper的getURI()方法。

	@Override
	public URI getURI() {
		// 调用LoadBalancerClient的reconstructURI方法(这里的loadBalancer是LoadBalancerClient类型)
		URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		return uri;
	}

所以总结起来request.apply(serviceInstance)里面就是调用LoadBalancerClient的reconstructURI方法重写url,然后调用ClientHttpRequestFactory的createRequest方法重新创建一个Request对象,然后调用该Request对象的execute()方法真正发起http请求。

在这里插入图片描述

标签:...,拦截器,ILoadBalancer,SpringCloud,RestTemplate,源码,LoadBalancerInterceptor,Ribbon
From: https://blog.csdn.net/weixin_43889578/article/details/136691829

相关文章

  • OpenCV与AI深度学习 | 手把手教你用Python和OpenCV搭建一个半自动标注工具(详细步骤 +
    本文来源公众号“OpenCV与AI深度学习”,仅用于学术分享,侵权删,干货满满。原文链接:手把手教你用Python和OpenCV搭建一个半自动标注工具(详细步骤+源码)导 读    本文将手把手教你用Python和OpenCV搭建一个半自动标注工具(包含详细步骤+源码)。背景介绍    样本标......
  • [jetson]jetson上torchvision源码下载地址汇总jetson上安装torchvision方法
    这个是jetson上使用的torchvision源码,解压后使用sudopython3setup.pyinstall即可安装,编译大约耗时30分钟完成,请耐心等待,安装这个源码之前您必须安装好由nvidia官方提供对应torchwhl文件,因此需要必须先安装好pytorch才能使用源码编译。目前我主要用这个源码给同学们安装......
  • 门店收银系统源码+同城即时零售多商户入驻商城源码
    一、我们为什么要开发这个系统?1.商户经营现状“腰尾部”商户,无小程序运营能力;自营私域商城流量渠道单一;无法和线下收银台打通,库存不同步,商品不同步,订单不同步;2.平台服务商现状想整合身边资源做本地平台;多门店入驻资金结算难;平台针对入驻门店抽佣分账难;3.行业大势所趋国......
  • HTML5 拖放(附带源码及动画演示)
    HTML5拖放效果概述拖放是一种常见的特性,即抓取对象以后拖到另一个位置,在HTML5中,拖放是标准的一部分,任何元素都能够拖放。实例如下是实现从左侧容器中拖拽图片到右侧的容器:实现方法:<!--以下代码实现图片可以从第一个容器拖放到第二个容器--><!DOCTYPEhtml><ht......
  • SpringBoot+Vue房屋租赁(租房)系统 - 附源码已配套论文
    摘 要在网络高速发展的时代,众多的软件被开发出来,给用户带来了很大的选择余地,而且人们越来越追求更个性的需求。在这种时代背景下,房东只能以用户为导向,所以开发租房网站是必须的。系统采用了Java技术,将所有业务模块采用以浏览器交互的模式,选择MySQL作为系统的数据库,开发工具......
  • SpringBoot+Vue宠物医院管理系统-附源码与配套论文
    1.1课题背景在信息技术高速发展的今天,新知识、新技术层出不穷,计算机技术早已广泛的应用于各行各业之中,利用计算机的强大数据处理能力和辅助决策能力叫,实现行业管理的规范化、标准化、效率化。管理信息系统(ManagementInformationSystem,简称MIS〉是一个以人为主导,利用计算......
  • 排序简单篇——冒泡排序、选择排序、插入排序、希尔排序、快速排序全解析【附完整源码
    ......
  • SSM酒店后台管理系统 ---附源码13123
    目 录摘要Abstract1绪论1.1课题目的及意义1.2研究背景1.3研究方法1.4论文结构与章节安排2 酒店后台管理系统系统分析2.1可行性分析2.2系统功能分析2.2.1功能性分析2.3.2非功能性分析2.4 系统用例分析2.5本章小结3酒店后台管理系统......
  • 从Java类加载器源码浅析到线上热部署实现
    1Java代码的执行过程写了这么多代码,有没有想过我们的代码是怎么执行的?或者说定义了那么多类,我们的class是怎么加载到内存的?Java语言属于一种高级语言,而cpu能执行的只有机器码,所以Java代码的运行离不开jvm虚拟机的编译,下面用一张图说明在HotSpot虚拟机中Java代码加载到cpu执行的......
  • 计算机毕业设计选题-基于BS招投标管理系统【源码+文档+ppt】
    ......