SpringCloud Ribbon原理-1-LoadBalanced注解原理

SpringCloud Ribbon 客户端负载均衡原理

1. Ribbon实现负载均衡

相比于nginx, nginx是在服务端进行赋值均衡,由nginx服务器通过负载均衡算法获取到某个服务提供者的节点后,再将请求转发到该节点,而ribbon是在客户端进行负载均衡,也就是服务的调用方,获取到服务的提供者的地址列表,通过某种负载均衡算法,选择其中一个服务提供者节点进行服务调用, 下面通过两个项目来学习Ribbon 负载均衡的实现以及原理;

  • Ribbon实现负载均衡的两种方式

通过LoadBalancerClient 实现负载均衡
通过@LoadBalanced注解实现负载均衡

  1. 创建两个项目order-service user-service,一个作为服务提供者,一个作为服务消费者
    服务提供者order-service:
    在这里插入图片描述
    配置文件application.properties文件中配置端口号: server.port=8080 ,启动服务,然后修改端口号为8082,再启动一个服务,这样就得到了一个服务提供者的集群
    服务消费者user-service:
    在这里插入图片描述
package com.gupaoedu.springcloud.example.springclouduserservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class UserController {
    
    

    @Autowired
    RestTemplate restTemplate;

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
    
    
        return restTemplateBuilder.build();
    }
    @Autowired
    LoadBalancerClient loadBalancerClient;

    @GetMapping("/user/{id}")
    public String findById(@PathVariable("id")int id){
    
    
        //TODO
        // 调用订单的服务获得订单信息
        // HttpClient  RestTemplate  OkHttp   JDK HttpUrlConnection
        ServiceInstance serviceInstance=loadBalancerClient.choose("spring-cloud-order-service");
        String url=String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/orders");
        return restTemplate.getForObject(url,String.class);
    }
}

配置文件: 指定服务消费者端口,以及服务提供者的地址列表,ribbon会读取spring-cloud-order-service.ribbon.listOfServers 这个配置项获取服务提供者的集群地址列表

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

然后启动项目,不断访问http://localhost:8088/user/1 , 会看到order-service项目的两个控制台轮流打印端口号(ribbon默认的负载均衡算法是轮询)
上面是通过LoadBalancerClient 实现负载均衡,LoadBalancerClient 可以使用@LoadBalanced注解来代替,只需要在RestTemplate Bean的定义上面加上 @LoadBalanced注解即可:

package com.gupaoedu.springcloud.example.springclouduserservice;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
    
    
    @Autowired
    RestTemplate restTemplate;
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
    
    
        return restTemplateBuilder.build();
    }
//    @Autowired
//    LoadBalancerClient loadBalancerClient;
    @GetMapping("/user/{id}")
    public String findById(@PathVariable("id")int id){
    
    
        return restTemplate.getForObject("http://spring-cloud-order-service/orders",String.class);
    }
}

有个细节需要注意到,就是上面的请求地址是改成了http://spring-cloud-order-service/orders ,即 http://服务名/接口名的形式。
那么这个注解是如何实现负载均衡的,我们暂时不得而知,但是可以肯定的是,它肯定实现了以下功能:

  1. 通过服务名来解析服务地址列表
  2. 在通过restTemplate 进行http请求调用前,修改请求url
  3. 通过restTemplate 进行http请求调用,返回结果

2. @LoadBalanced注解原理

查看LoadBalanced 注解,可以看到它本质上就是一个@Qualifier注解,这个注解的作用就是给Bean打上一个标记(详见https://blog.csdn.net/weixin_41300437/article/details/109192723

@Target({
    
    ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
    
    
}

并不能看出什么端倪,从springboot自动装配原理,我们猜想应该有一个负载均衡的配置类,在IDEA中全局搜索LoadBalance, 找到LoadBalancerAutoConfiguration 这个自动配置类

@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(equired = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    public LoadBalancerAutoConfiguration() {
    
    
    }

可以看到restTemplates 上面加了 @Autowired注解和一个@LoadBalanced注解,也就等同于加了一个@Qualifier注解,那么我们就能得出一个结论,只要是加了@LoadBalanced 的restTemplate bean, 那么都会被添加到restTemplates 这个list中。不加@LoadBalanced注解的,就不能被添加进去。LoadBalancerAutoConfiguration 这个自动装配类,会在spring容器启动时,完成ribbon所需要的一些bean的自动装配,完整代码如下:

package org.springframework.cloud.client.loadbalancer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;

/**
 * Auto-configuration for Ribbon (client-side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 * @author Gang Li
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
    
    

	// 将所有加了@LoadBalanced注解的restTemplate bean放到restTemplates这个list里面
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

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

   // 3. 遍历restTemplates,用RestTemplateCustomizer对每一个加了@LoadBalanced注解的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);
				}
			}
		});
	}

	@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 {
    
    

         // 1.定义LoadBalancerInterceptor 拦截器Bean
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
    
    
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		// 2.对restTemplate进行包装,依赖注入loadBalancerInterceptor 这个bean,对加了@LoadBalanced注解的Restemplate实例添加LoadBalancerInterceptor拦截器
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
    
    
			return restTemplate -> {
    
    
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());  // 获取restTemplate默认的拦截器 
				list.add(loadBalancerInterceptor); // 对restTemplate添加新的拦截器
				restTemplate.setInterceptors(list); // 将新的拦截器链设置到restTemplate里
			};
		}
	}

	/**
	 * 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);
			};
		}

	}

}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_41300437/article/details/109192219