SpringCloud之Ribbon源码分析(一)

前言 

        这篇博文着重讲源码,对于这种比较复杂的源码,我喜欢截图分析,因为图可以作标注,比直接贴源码要方便和醒目。本篇博文会有大量的图片,显得文章会很长,其实文字是不多的。建议读者自己配合源码阅读。注意版本保持一致,我的版本下面会给出。

        我的很多分析在图片上会有标注,读者请关注图片上的被红框部分以及标注部分。

一、Ribbon的作用

        Ribbon的作用是负载均衡。使用Ribbon实现负载均衡时,基本用法是注入一个RestTemplate,并使用@LoadBalanced注解标注RestTemplate,从而使RestTemplate具备负载均衡的能力。

        当Spring容器启动时,使用@LoadBalanced注解修饰的RestTemplate会被添加拦截器,拦截器中使用了LoadBalancerClient处理请求,从而达到负载均衡的目的。那么LoadBalancerClient内部是如何做到的呢?下面我们通过源码分析的方式来剖析Ribbon负载均衡的工作原理。

 二、demo

2.1 pom.xml配置增加ribbon依赖

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

2.2 application.properties配置{spring.application.name}.ribbon.listOfServers

没有配置注册中心的情况下,使用{spring.application.name}.ribbon.listOfServers配置写死服务端节点。

# 配置指定服务的提供者的地址列表
spring-cloud-order-service.ribbon.listOfServers=\
  localhost:8080,localhost:8082

2.3 应用

 三、源码分析一:RibbonLoadBalanceClient装配过程

网上的图: 

 接下来我们会根据上图来分析ribbon原理。

先分析这一部分:

图1-初始化

        首先我们要知道,RibbonLoadBalanceClient是ribbon负载的重要类。至于RibbonLoadBalanceClient在ribbon负载是怎样一个流程,我们后面再说,这一部分先看它是如何添加到spring中的。

3.1RibbonAutoConfiguration配置类

看呀,spring-cloud-netflix-ribbon-2.2.3.RELEASE.jar下面的spring.factories,有如下配置:

 如上图我说的,SPI!自动装载!

RibbonAutoConfiguration配置类上,我们可以看到下面这部分

@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
      AsyncLoadBalancerAutoConfiguration.class })

 表示RibbonAutoConfiguration.class会在LoadBalancerAutoConfiguration.class之前装载。

 注解 @AutoConfigureBefore 和 @AutoConfigureAfter 的用途

3.2 LoadBalancerAutoConfiguration配置类

 LoadBalancerAutoConfiguration配置类的生成情况:

接下来我们看LoadBalancerAutoConfiguration.class类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

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

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

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

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			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);
				restTemplate.setInterceptors(list);
			};
		}

	}

	/**
	 * Auto configuration for retry mechanism.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {
			};
		}

	}

	/**
	 * Auto configuration for retry intercepting mechanism.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

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

	}

}

在该自动化配置类中,主要做了下面三件事:

  • 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
  • 创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。
  • 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

到这里,对比图1分析下来,流程是不是还挺清晰的呀。 

一个疑问

那么我打个断点调试一下:

可以看到我@Bean生成的LoadBalanceInterceptor生成的bean对象是@6409,在下面

restTemplateCustomizer方法中,只要看看LoadBalanceInterceptor对象是不是@6409就行,然而它不是:

它是@6413,所以这俩不是一个 LoadBalanceInterceptor对象,至于 restTemplateCustomizer()方法闯进来的@6413LoadBalanceInterceptor对象是从哪里生成以及注入的,后面再说,值得一提的是这两个对象的属性引用是一致的:

 扩展

在这里插入图片描述

3.3@Qualifier和@LoadBalanced

Ribbon中@LoadBalanced注解的原理

四、从restTemplate#getForObject来分析Ribbon源码

        首先我们知道ribbon是通过拦截器来实现负载的。那么发起一个请求,是怎么如何到拦截器的呢?带着这个问题,我们来看一下源码。

看一下RestTemplate类结构图:

看下调用流程:

restTemplate#getForObject——>restTemplate#execute——>restTemplate#doExecute

接下来我们来看restTemplate#doExecute源码:

4.1 createRequest方法——得到一个ClientHttpRequest对象

我们着重看#createRequest方法,和ClientHttpRequest#execute方法。

createRequest方法是在父类中实现的,父类——HttpAccessor.class:

继续看HttpAccessor#createRequest方法:

点进InterceptingHttpAccessor#getRequestFactory查看:

其中,getRequestFactory方法代码如下,其中getInterceptors是获得当前客户端请求的所有拦截
器,需要注意的是,这里的拦截器,就包含LoadBalancerInterceptor

来解答上图②问题,这里我们前面分析过了,来看:

来看红圈③,创建出来的是InterceptorClientHttpRequestFactory工厂类,点createRequest方法:

想看它的类继承图:

它的父类是AbstactBufferingClientHttpRequest,来看父类AbstactBufferingClientHttpRequest代码:

所以得到的是InterceptingClientHttpRequest!

4.2 分析request.execute()方法,执行请求逻辑

搞明白上面的流程了,那么接下来就是到下一阶段了,查看request.execute():

类结构图:

进入AbstractBufferingClientHttpRequest#executeInternal方法:

进入到InterceptingClientHttpRequest# executeInternal:

为了证实我上面的说法,我们打断点可以看到,确实是含有拦截器的:

既然分析到这里了,我们不妨就进到LoadBalancerInterceptor拦截器里去瞅瞅:

 接着分析:

上图②:深入解析

先看一下ZoneAwareLoadBalancer:

代码追踪到这里,其他的我们先放一放。接下来看服务在LoadBancer中是怎么缓存进来的。

4.3 LoadBancer如何缓存服务

接下来的逻辑就在下图红色虚线框中了:

想看BaseLoadBalancer类:

我们可以看到BaseLoadBalancer类有allServerList和upServerList属性,看名字就知道,allServerList-所有服务列表,upServerList-目前在线的服务列表。

这里是在哪里设置,从哪里获取到的值?

 接下来的流程就是下图的深蓝色实线框内的步骤了:

 请看下面:

继续看父类DynamicServerListLoadBalancer类的有参构造函数:

进入红框①代码:

 

默认30s更新一次

进入红框②代码:

到这里,这一块就分析结束了。

4.4 ribbon如何初始化几个重要的对象

把视线再移到这一块:

看一下RibbonClientConfiguration是怎么初始化下面这几个对象的:

PollingServerListUpdater:

PollingServerListUpdater通过这个@Bean方法注入进去的。

这个方法的参数是config,config的bean是怎么创建的呢,请看下面:

 

这里先分析到这。

4.5 DynamicServerListLoadBalancer#updateListOfServers方法

下面回到updateListOfServers方法,来讲它的代码:

点进去瞅一下是怎么更新的:

4.6 心跳

再初始化BaseLoadBalancer构造函数时,有下面这一句代码:

这里我就把PingUrl.isAlive()代码贴一下,以后我们写心跳可以参考:

public boolean isAlive(Server server) {
    String urlStr = "";
    if (this.isSecure) {
        urlStr = "https://";
    } else {
        urlStr = "http://";
    }


    urlStr = urlStr + server.getId();
    urlStr = urlStr + this.getPingAppendString();
    boolean isAlive = false;
    HttpClient httpClient = new DefaultHttpClient();
    HttpUriRequest getRequest = new HttpGet(urlStr);
    String content = null;


    try {
        HttpResponse response = httpClient.execute(getRequest);
        content = EntityUtils.toString(response.getEntity());
        isAlive = response.getStatusLine().getStatusCode() == 200;
        if (this.getExpectedContent() != null) {
            LOGGER.debug("content:" + content);
            if (content == null) {
                isAlive = false;
            } else if (content.equals(this.getExpectedContent())) {
                isAlive = true;
            } else {
                isAlive = false;
            }
        }
    } catch (IOException var11) {
        var11.printStackTrace();
    } finally {
        getRequest.abort();
    }


    return isAlive;
}

好了,本篇博文先到这里结束。下一篇我们还将继续分析ribbon,敬请关注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值