TFdream / blog

个人技术博客,博文写在 Issues 里。
Apache License 2.0
129 stars 18 forks source link

【SpringCloud源码分析】SpringCloud @LoadBalanced实现原理 #283

Open TFdream opened 4 years ago

TFdream commented 4 years ago

我们在使用RestTemplate类调用其他服务的时候,官方文档上都会出现以下代码:

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

这样如果服务端有多个服务实例提供,那么自动就有了客户端负载均衡的效果,使用RestTemplate的时候就非常的方便,在这里不得不说spring的这些大师真的很厉害。

注:本文中spring-cloud-dependencies版本为Hoxton.SR5,spring-cloud-loadbalancer 版本为2.2.3.RELEASE,spring-cloud-netflix-ribbon版本为 2.2.3.RELEASE。

LoadBalancerClient

在这里就会想,为什么加上上@LoadBalanced注解就有了这种神奇的效果。于是点看@LoadBalanced的源码,发现这个注解就是一个普通的注解而已,没有什么神奇的地方:


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

}

注意上面的注释中提到了这个注解是供org.springframework.cloud.client.loadbalancer.LoadBalancerClient 使用的,那我们来看一眼LoadBalancerClient类,发现它是一个接口,这个接口在spring-cloud-commons的jar包中,如下:

package org.springframework.cloud.client.loadbalancer;

/**
 * Represents a client-side load balancer.
 *
 * @author Spencer Gibb
 */
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 reconstructURI(ServiceInstance instance, URI original);

}

由于是个接口,说明这只是为了定义一种能力,源码上的注解说是Represents a client side load balancer,也就是提供一种客户端负载均衡的能力,这个能力还需要依赖具体的实现类去实现。

IDEA中使用快捷键 ctrl+H 发现这个类有2个实现类

RibbonLoadBalancerClient 在spring-cloud-netflix-core jar里面,说明springcloud官方为了接入netflix的ribbon组件,专门写了RibbonLoadBalancerClient这个实现类。

而BlockingLoadBalancerClient 在spring-cloud-loadbalancer jar中,是spring cloud官方LoadBalancerClient 实现。

RibbonLoadBalancerClient注入

RibbonLoadBalancerClient可以找到是在RibbonAutoConfiguration类注入到spring容器中的,代码如下:

package org.springframework.cloud.netflix.ribbon;

/**
 * Auto configuration for Ribbon (client side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
        name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {

    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();

    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    @Bean
    public HasFeatures ribbonFeature() {
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }
    //省略其它方法

}

我们重点关注一下 LoadBalancerClient 注入:

    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

BlockingLoadBalancerClient注入

BlockingLoadBalancerClient可以找到是在BlockingLoadBalancerClientAutoConfiguration类注入到spring容器中的,代码如下:

/**
 * An autoconfiguration for {@link BlockingLoadBalancerClient}.
 *
 * @author Olga Maciaszek-Sharma
 * @since 2.1.3
 */
@Configuration(proxyBeanMethods = false)
@LoadBalancerClients
@AutoConfigureAfter(LoadBalancerAutoConfiguration.class)
@AutoConfigureBefore({
        org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
public class BlockingLoadBalancerClientAutoConfiguration {

    @Bean
    @ConditionalOnClass(
            name = "org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient")
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
            matchIfMissing = true)
    public BlockingLoadBalancerClientRibbonWarnLogger blockingLoadBalancerClientRibbonWarnLogger() {
        return new BlockingLoadBalancerClientRibbonWarnLogger();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RestTemplate.class)
    @Conditional(OnNoRibbonDefaultCondition.class)
    protected static class BlockingLoadbalancerClientConfig {

        @Bean
        @ConditionalOnBean(LoadBalancerClientFactory.class)
        @Primary
        public BlockingLoadBalancerClient blockingLoadBalancerClient(
                LoadBalancerClientFactory loadBalancerClientFactory) {
            return new BlockingLoadBalancerClient(loadBalancerClientFactory);
        }

    }

}

这里注意 @Conditional(OnNoRibbonDefaultCondition.class) 字面意思是Ribbon不存在时才会注入,其代码如下:

/**
 * A {@link Condition} that verifies that Ribbon is either not present or disabled.
 *
 * @author Olga Maciaszek-Sharma
 * @since 2.2.1
 */
public class OnNoRibbonDefaultCondition extends AnyNestedCondition {

    public OnNoRibbonDefaultCondition() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }

    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
            havingValue = "false")
    static class RibbonNotEnabled {

    }

    @ConditionalOnMissingClass("org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient")
    static class RibbonLoadBalancerNotPresent {

    }

}

到目前为止,两个LoadBalancerClient的实现类已经注入到spring容器中了,接下来我们看看哪里会使用到。

LoadBalancerAutoConfiguration

接下来我们看一看 org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,它位于 spring-cloud-commons jar包中,这个类就是关键所在。源码如下:

package org.springframework.cloud.client.loadbalancer;

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

这里会将容器中所有RestTemplate实例对象并执行 RestTemplateCustomizer#customize 回调方法,restTemplateCustomizers从哪里来的呢?

这里分了2种情形:容器中不存在RetryTemplate 和 存在 RetryTemplate 情况。

容器中不存在RetryTemplate 初始化如下:


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

    }

可以看出,上述代码会在没有 RetryTemplate 时生成 LoadBalancerInterceptor ribbonInterceptor ,如果容器中存在RetryTemplate对象会执行以下代码:


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

    }

RestTemplate内部提供了拦截器这种机制,来可以对restTemplate的一些行为做干预。说明拦截器起到了客户端负载均衡的效果。

LoadBalancerInterceptor

接下来我们看看 org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor类,如下:

/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 * @author William Tran
 */
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));
    }

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

}