SpringCloud之@LoadBalanced注解原理

1.概述

使用注解就像是在代码中加入了一段魔法,让我们轻而易举的就实现了至关重要的功能。

2.使用RestTemplate

RestTemplate是Spring提供的一个简单的Rest模板客户端,用来进行API的调用。

2.1 单体应用

我们可以在单体web应用中使用RestTemplate来进行对API的调用。例如:

public class LoadBalanceTest {

    public void test1() {
        RestTemplate restTemplate = new RestTemplate();
        // 对第三方服务提供的接口进行调用
        String result = restTemplate.getForObject("https://www.baidu.com", String.class);
        System.out.println(result);
    }
    
    public void test2() {
        RestTemplate restTemplate = new RestTemplate();
        // 对本地服务提供的接口进行调用
        String result = restTemplate.getForObject("http://localhost:8080/test", String.class);
        System.out.println(result);
    }
}

2.2 微服务应用

如果要在微服务应用中使用RestTemplate,并达到负载均衡的效果应该怎么做呢?我们知道,在微服务中,一般同一个服务实例不只一个,那么就涉及到请求过程中服务实例的选择问题。答案是使用@LoadBalanced注解,将该注解加在RestTemplate的Bean上,就可以实现负载均衡,就像下面这段代码:

@Configuration
public class CustomConfiguration {
    @Bean
    @LoadBalanced // 开启负载均衡能力
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

我们只是添加了一个注解,就实现了负载均衡的功能,得益于SpringCloud对底层实现逻辑的封装,才让我们在开发中使用起来如此的得心应手。此时我们的代码就会变成这个样子,使用http://provider/test,不会直接使用http://ip:port/test的形式,而是使用服务名称,根据服务名称去注册中心获取实际的服务信息,最终再转换为要请求的地址。

@RestController
public class LoadBalanceController {

    @Autowired
    private RestTemplate restTemplate;

    public void test() {
        // provider代表的是服务提供者的spring.application.name的值
        String result = restTemplate.getForObject("http://provider/test", String.class);
        System.out.println(result);
    }
}

SpringCloud之@LoadBalanced注解原理_第1张图片

3.@LoadBalanced注解分析

下面我们来一步一步揭开@LoadBalanced的庐山真面目。

/**
 * Annotation to mark a RestTemplate or WebClient bean to be configured to use a
 * LoadBalancerClient.
 * 
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

其注释中写到,该注解是用来标记要使用LoadBalancerClientRestTemplateWebClient的bean,意思就是最终底层使用的是LoadBalancerClient来进行调用,这个结论会在我们后续的分析中一步步清晰明了。

3.1 @Qualifier

@Qualifier起着限定的作用,可以筛选出符合条件的bean。

我们首先关注@Qualifier注解,我们看到接口LoadBalanced的定义上,添加了@Qualifier注解,所以我们再来回顾一下。

当SpringIOC容器中有多个同类型的bean时,在使用@Autowired进行装配时,就无法完成自动装配,原因是@Autowired是按bean的类型来装配的,可想而知,Spring也不知道我们到底要装配哪个bean,于是乎,@Qualifier的出现就是为了解决这个问题。这个时候可以结合@Qualifier,根据bean的名称来进行装配,到这里就可以明白,@Qualifier起着限定的作用。

1、添加同类型不同名称的多个bean

@Configuration
public class CustomConfiguration {
    @Bean
    public RestTemplate restTemplate1() {
        return new RestTemplate();
    }

    @Bean
    public RestTemplate restTemplate2() {
        return new RestTemplate();
    }
}

2、注入指定名称的bean

@RestController
public class LoadBalanceController {

    @Autowired
    @Qualifier("restTemplate2")
    private RestTemplate restTemplate;
}

因此,@LoadBalanced继承了该特性,这里先埋下一个猜测,SpringCloud的底层封装也会用到该注解,用于对特定的RestTemplate进行处理,不然怎么对RestTemplate进行处理呢,是吧。

3.2 LoadBalancerClient

LoadBalancerClient:见名知意,这是一个负载均衡的客户端。

前面得知,在负载均衡模式下,SpringCloud会使用LoadBalancerClient对RestTemplate发起的请求进行处理,我们先来看看这个接口。

public interface LoadBalancerClient extends ServiceInstanceChooser {

    /**
	 * 根据指定服务名称来执行请求
	 */
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    /**
	 * 根据指定服务名称以及指定的服务实例来执行请求
	 */
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    /**
	 * 获得真实的URI,将http://provider/test转换为http://localhost:8080/test
	 */
    URI reconstructURI(ServiceInstance instance, URI original);

}

结论:根据源码,可以分析出,LoadBalancerClient可以向特定的服务实例发送请求。

并且LoadBalancerClient继承自ServiceInstanceChooser,见名知意ServiceInstanceChooser应该是用来帮我们从同一个服务的多个服务实例中根据负载均衡策略选择出所需的服务实例。按照惯例,我们来看看其源码:

public interface ServiceInstanceChooser {

    // 根据指定的服务名称选择服务实例
    ServiceInstance choose(String serviceId);

    // 根据指定的服务名称以及请求选择服务实例
    <T> ServiceInstance choose(String serviceId, Request<T> request);
}

结论:ServiceInstanceChooser提供的方法,可以帮助我们根据负载均衡算法选择出实际要调用的服务实例。

3.3 LoadBalancerAutoConfiguration

继续追踪,我们找到了LoadBalancerAutoConfiguration,这就是LoadBalancer的自动配置类。以下为截取的部分源码:

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

    // 果不其然,@LoadBalanced又出现了,这里的目的是为了装配添加了@LoadBalanced注解的RestTemplate的bean
    @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)
    @Conditional(RetryMissingOrDisabledCondition.class)
    static class LoadBalancerInterceptorConfig {

        @Bean
        public LoadBalancerInterceptor loadBalancerInterceptor(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);
            };
        }

    }
}

4.注入RestTemplate

果不其然,@LoadBalanced出现在了SpringCloud的底层代码中,这里会筛选出添加了@LoadBalanced的RestTemplate,并装配到restTemplates中。

SpringCloud之@LoadBalanced注解原理_第2张图片

到这里,标注有@LoadBalanced注解的RestTemplate的bean已经装配进来,那么怎么对RestTemplate发起的请求进行处理呢?SpringCloud采用拦截器的方式。

5.为RestTemplate设置拦截器

那么为什么可以为RestTemplate设置拦截器呢?我们来看看RestTemplate的类图。

SpringCloud之@LoadBalanced注解原理_第3张图片

从图中可以看出InterceptingHttpAccessor是一个和拦截器有关系类,我们来一探究竟。

SpringCloud之@LoadBalanced注解原理_第4张图片

结论:在InterceptingHttpAccessor中为我们存储了拦截器列表,因此我们可以为RestTemplate设置拦截器。

到此,我们已经知道了拦截器的存放位置,接下来就是如何创建并设置拦截器了。

5.1 创建拦截器

我们再回到LoadBalancerAutoConfiguration中,看看自动配置的LoadBalancerInterceptor

@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {

    // 创建负载均衡拦截器
    @Bean
    public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
                                                           LoadBalancerRequestFactory requestFactory) {
        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }

}
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;

    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    // 拦截方法,使用LoadBalancerClient来执行请求
    @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));
    }

}

结论:将LoadBalancerClient以及LoadBalancerRequestFactory封装到LoadBalancerInterceptor中。并在intercept()方法中,使用LoadBalancerClient执行请求

5.2 创建RestTemplateCustomizer

第一步,我们只是创建了拦截器,还没有将拦截器设置到RestTemplate中。我们先看看RestTemplateCustomizer,后续会使用到。

RestTemplateCustomizer是一个函数式接口。

public interface RestTemplateCustomizer {

	void customize(RestTemplate restTemplate);

}
@Configuration(proxyBeanMethods = false)
@Conditional(RetryMissingOrDisabledCondition.class)
static class LoadBalancerInterceptorConfig {
    
    // 创建RestTemplateCustomizer
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
        // 利用lambda表达式来创建RestTemplateCustomizer,后续执行customize(),便会直接执行这段逻辑。
        return restTemplate -> {
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
            list.add(loadBalancerInterceptor);
            restTemplate.setInterceptors(list);
        };
    }

}

5.3 设置拦截器

第二步,已经对RestTemplateCustomizer进行了创建,接下来就是获取到所有的RestTemplateCustomizer,并调用其customize()方法,这时会回调步骤2的定义时书写的逻辑。

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

我们来看这段回调的具体逻辑,指向了customize()方法。

SpringCloud之@LoadBalanced注解原理_第5张图片

这里用到了SmartInitializingSingleton,为什么注入一个SmartInitializingSingletonbean,就能帮助我们为RestTemplate设置拦截器呢?这就不得不提到bean的初始化过程。

先来看看SmartInitializingSingleton

public interface SmartInitializingSingleton {

	void afterSingletonsInstantiated();

}

再看bean的初始化过程,最后最终源码可得知其调用过程:

refresh()-->finishBeanFactoryInitialization()-->preInstantiateSingletons()

SpringCloud之@LoadBalanced注解原理_第6张图片

最终完成了对SmartInitializingSingletonafterSingletonsInstantiated()的调用,从而成功为RestTemplate设置了LoadBalancerInterceptor

到目前为止,我们理清楚了对RestTemplate设置拦截器的原理,接下来就是分析如何对RestTemplate进行的请求进行拦截的。

6.RestTemplate调用过程

这里以RestTemplate的get请求为例进行分析:

public void test() {
    // provider代表的是服务提供者的spring.application.name的值
    String result = restTemplate.getForObject("http://provider/test", String.class);
    System.out.println(result);
}

SpringCloud之@LoadBalanced注解原理_第7张图片

6.1 创建InterceptingClientHttpRequest

1)RestTemplate.doExecute()

2)封装ClientHttpRequest

3)使用ClientHttpRequest执行请求

SpringCloud之@LoadBalanced注解原理_第8张图片

封装ClientHttpRequest的内部实现:

SpringCloud之@LoadBalanced注解原理_第9张图片

4)调用InterceptingHttpAccessor.getRequestFactory(),获取InterceptingClientHttpRequestFactory

SpringCloud之@LoadBalanced注解原理_第10张图片

5)调用InterceptingClientHttpRequestFactory.createRequest(),获取InterceptingClientHttpRequest

SpringCloud之@LoadBalanced注解原理_第11张图片

6.2 执行拦截

1)调用InterceptingClientHttpRequest.executeInternal()

SpringCloud之@LoadBalanced注解原理_第12张图片

2)将InterceptingClientHttpRequest封装为ServiceRequestWrapper,然后使用LoadBalancerClient执行请求

SpringCloud之@LoadBalanced注解原理_第13张图片

这里在执行LoadBalancerClientexecute()方法时,首先要构造一个LoadBalancerRequest,跟进创建方式时,发现使用lambda表达式进行构建,在调用其方法时,就会进行回调。

public interface LoadBalancerRequest<T> {

	T apply(ServiceInstance instance) throws Exception;

}

构造一个LoadBalancerRequest,并最终将HttpRequest封装到ServiceRequestWrapper

SpringCloud之@LoadBalanced注解原理_第14张图片

6.3 负载均衡获取ServiceInstance

调用LoadBalancerClientexecute(),通过负载均衡算法获取ServiceInstance

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
    String hint = getHint(serviceId);
    LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
                                                                                                    buildRequestContext(request, hint));
    Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
    supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
    // 根据服务名称以及请求获取服务实例,并使用复杂均衡策略
    ServiceInstance serviceInstance = choose(serviceId, lbRequest);
    if (serviceInstance == null) {
        supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
            new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    return execute(serviceId, serviceInstance, lbRequest);
}

6.4 发送请求

1)获取ServiceInstance的真实host、port

当执行完所有的拦截器方法后,就来到了获取真实请求地址的host、port环节

SpringCloud之@LoadBalanced注解原理_第15张图片

前面我们知道InterceptingClientHttpRequest被封装到了ServiceRequestWrapper,因此来到了ServiceRequestWrapper.getURI(),实际上使用的LoadBalancerClient.reconstructURI(),前面我们提到过,这个方法就是获取真实URI。

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

}

5.小结

1、在创建RestTemplate的bean时添加@LoadBalanced注解

2、LoadBalancerAutoConfiguration自动配置筛选出添加@LoadBalanced注解的RestTemplate

3、为RestTemplate设置LoadBalancerInterceptor,目的是使用LoadBalancerClient来发起请求

4、请求过程中,根据负载均衡策略,调用LoadBalancerClient.choose()方法获取最终ServiceInstance

5、根据ServiceInstance获取真实host、port,发起最后请求

你可能感兴趣的:(Spring,Cloud,spring,cloud)