Ribbon是如何通过一个@LoadBalanced注解就实现负载均衡的

一.介绍下测试用到的服务
在这里插入图片描述
从Eureka注册中心中可以可以看出有EUREKA-CLIENT和RIBBON-CLIENT的服务,其中EUREKA-CLIENT有两个节点作为服务提供者,而RIBBON-CLIENT则是服务消费者,通过RestTemplate来消费EUREKA-CLIENT的服务。

下面代码就是简单实现Ribbon负载均衡的配置类:

@Configuration
public class RibbonConfig {

    @Bean
    @LoadBalanced
    RestTemplate getRestTemlate() {
        return new RestTemplate();
    }
}

这样简单的通过一个@LoadBalanced注解在RestTemplate上 ,在RestTemplate 远程调用的时候,就会出现负载均衡的效果。

二.一步一步理清Ribbon负载均衡的逻辑

  1. 首先全局搜索@LoadBalanced这个注解,发现在LoadBalancerAutoConfiguration类有用到该注解:
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
    
    /**
    *  这段代码的作用是将有用@LoadBalanced注解的RestTemplate注入
    */
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
}

分析以上代码:

  • 通过@Configuration表明这是一个配置类
  • 通过@ConditionalOnClass(RestTemplate.class)可以知道RestTemplate类要在类路径上存在才会实例化LoadBalancerAutoConfiguration
  • 通过@ConditionalOnBean(LoadBalancerClient.class)可以知道LoadBalancerClient类要存在才会实例化LoadBalancerAutoConfiguration
  • @EnableConfigurationProperties(LoadBalancerRetryProperties.class)是用来使用@ConfigurationProperties注解的类LoadBalancerRetryProperties生效,贴上部分LoadBalancerRetryProperties类的代码,会更清晰:
@ConfigurationProperties("spring.cloud.loadbalancer.retry")
public class LoadBalancerRetryProperties {

	private boolean enabled = true;

	/**
	 * Returns true if the load balancer should retry failed requests.
	 * @return True if the load balancer should retry failed requests; false otherwise.
	 */
	public boolean isEnabled() {
		return this.enabled;
	}
  1. 所以重启下RIBBON-CLIENT服务,Debug继续看LoadBalancerAutoConfiguration 类的代码,发现在启动时会先进入LoadBalancerAutoConfigurationloadBalancerRequestFactory方法,实例化出LoadBalancerRequestFactory
    @Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

接下去断点进入LoadBalancerAutoConfiguration 类中的静态内部类LoadBalancerInterceptorConfigribbonInterceptor方法,可以看出这是为了实例化出LoadBalancerInterceptor 拦截器

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

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

继续跟断点,进入了loadBalancedRestTemplateInitializerDeprecated方法,可以看出这个方法里主要的逻辑代码是customizer.customize(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);
				}
			}
		});
	}

继续Debug,断点进入LoadBalancerAutoConfiguration类中的静态内部类LoadBalancerInterceptorConfig

@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}
	}

通过 list.add(loadBalancerInterceptor)和restTemplate.setInterceptors(list)两段代码可以看出,这是要给restTemplate加上loadBalancerInterceptor拦截器。

那么接下来看看loadBalancerInterceptor拦截器里做了什么,通过页面发起一个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();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

截图看下信息:
在这里插入图片描述
可以看到该方法取得了request里的url和servicName,然后将这些参数交给loadBalancer.execute去执行方法。而loadBalancer是LoadBalancerClient类的实例。
看下LoadBalancerClient的类图,可以看到LoadBalancerClient继承了ServiceInstanceChooserLoadBalancerClient的实现类是RibbonLoadBalancerClient
在这里插入图片描述
逻辑继续,断点进入了RibbonLoadBalancerClient的execute方法

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

跟着断点一步一步看方法:

  • ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    在这里插入图片描述
    经过这个方法,得到loadBalancer,从截图里可以看到,loadBalancer里有个allServerList集合,里面有两个对象,端口号分别是8763和8762,这就是我们提供的服务节点。

  • Server server = getServer(loadBalancer, hint)
    在这里插入图片描述
    从图里可以看出,通过这个getServer方法,会返回给我们一个当前可调用的服务节点,而至于怎么返回服务节点,会再写一篇分析,写完后会更新链接到该篇。

  • 生成RibbonServer 作为参数传入execute方法

  • 运行execute方法

接着跟进execute方法
在这里插入图片描述
可以看该方法里的关键执行方法是:
T returnVal = request.apply(serviceInstance);
接着看apply方法,发现它是LoadBalancerRequest接口的方法,该接口却没有具体的实现类:

public interface LoadBalancerRequest<T> {

	T apply(ServiceInstance instance) throws Exception;

}

思路回溯,是request对象调用的apply方法,而request其实是execute方法传进来的参数,追溯到源头,发现是LoadBalancerInterceptor类的intercept方法里this.requestFactory.createRequest(request, body, execution)生成了LoadBalancerRequest,然后作为参数传入,之后再调用了apply方法

@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);
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

跟进createRequest方法里:

在这里插入图片描述
可以从图中看到,经过一些操作后,生成的serviceRequest对象里的serviceIdeureka-client,也就是我们的服务节点名,而serverlocalhost:8763,这是具体的服务节点ip,之后作为参数调用org.springframework.http.client包下的InterceptingClientHttpRequest类中的execute方法

断点进入该方法:
在这里插入图片描述
可以看出通过requestFactory.createRequest(request.getURI(), method)方法生成了ClientHttpRequest类的实例delegate,它的url就是我们最后真正要请求的,最后正常调用delegate.execute()方法取得返回ClientHttpResponse就好了。

而这里产生了一个疑问,url是怎么产生的?重新发起请求断点试下
发现关键在LoadBalancerRequestFactory类中的createRequest方法中的这句:

HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,his.loadBalancer);

跟进ServiceRequestWrapper类中,发现它继承了HttpRequestWrapper 类,同时重写了getURI方法

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 uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		return uri;
	}

}

断点打在getURI方法里:
在这里插入图片描述
可以看到该方法返回了我们最后需要的url。

最后,关于Ribbon是如何通过一个@LoadBalanced注解就实现负载均衡的分析就到这了,还是有很多疏漏的地方,但是大致的逻辑就是这样的了,还有一些更深层的比如如何根据策略选出当前提供服务的节点等,留待后续补充,来日方长~

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值