【深入理解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请求。

在这里插入图片描述

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值