Spring Cloud

Spring Cloud Ribbon 分析(一)之Ribbon

2021-01-08  本文已影响0人  Blog

随着微服务的项目越来越多,对Ribbon的使用也逐渐增多,之前小编在项目中也只是简单的了解Ribbon的作用,对于Ribbon是如何进行负载的原理也不全面,所以在此进行分析和总结,在后续也会分析和总结Spring Boot2.4.x版本之后默认的负载均衡Spring Cloud LoadBalance!


RibbonAutoConfiguration配置文件

作为Ribbon的入口配置文件,通过spring.factories进行加载

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.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<>();

    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }

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

以上为配置文件比较重要的几个点,下文会逐一分析具体作用


@RibbonClients注解

@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {

    RibbonClient[] value() default {};

    Class<?>[] defaultConfiguration() default {};

}

@Import(RibbonClientConfigurationRegistrar.class),字面意思不难理解,就是将@RibbonClients注解中配置的value与defaultConfiguration配置类注册到Spring容器中(实例RibbonClientSpecification),但是为什么要通过这样动态方式去注册配置类呢?仔细想想也不难理解,因为需要根据用户的配置(@RibbonClients、@RibbonClient)进行注册实例


RibbonClientConfigurationRegistrar

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        ......
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    attrs.get("defaultConfiguration"));
        }
        ......
    }
    ......
    private void registerClientConfiguration(BeanDefinitionRegistry registry,
            Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }
}

通过代码片段得知@RibbonClients注解配置会生成RibbonClientSpecification实例


SpringClientFactory

/**
* 非常重要的一个工厂类,Ribbon的很多获取实例Bean都是通过该工厂
* 创建客户端、负载平衡器和客户端配置实例的工厂
* 为每个客户端名称创建一个Spring ApplicationContext,可以从中获取需要的bean
*/
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

    static final String NAMESPACE = "ribbon";

    public SpringClientFactory() {
        super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
    }
    ......

    public ILoadBalancer getLoadBalancer(String name) {
        return getInstance(name, ILoadBalancer.class);
    }

    public IClientConfig getClientConfig(String name) {
        return getInstance(name, IClientConfig.class);
    }
    ......

这样的解释感觉有点牵强,我们继续分析下,SpringClientFactory继承NamedContextFactory,这个NamedContextFactory我们下文会继续讲解,我们把焦点关注到构造函数,3个参数,分别是配置类、命名空间、属性字段名,通过构造函数,我们就将RibbonClientConfiguration配置类引入进来了,简单理解就是RibbonClientConfiguration这个配置类被Spring扫描到了,该配置类下面的@Bean配置在后续就会通过懒加载方式进行访问,最后一个\color{#000000}{(ribbon.client.name)}这个属性名会在后续的分析中提到


NamedContextFactory

上文讲到SpringClientFactory继承NamedContextFactory,这是一个用命名空间划分的一个上下文工厂类,那这个类具体有什么作用呢,我们接着讲

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {

    public interface Specification {
        String getName();

        Class<?>[] getConfiguration();
    }
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }

    protected AnnotationConfigApplicationContext createContext(String name) {
        ......
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType);
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,
                Collections.<String, Object> singletonMap(this.propertyName, name)));
        if (this.parent != null) {
            // Uses Environment from parent as well as beans
            context.setParent(this.parent);
        }
        context.setDisplayName(generateDisplayName(name));
        context.refresh();
        return context;
    }
    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return context.getBean(type);
        }
        return null;
    }
    ......
}

SpringClientFactory.getInstance获取对应的实例时候,我们可以看到为每一个客户端名称(name属性)都会创建一个Spring ApplicationContext,然后这个客户端上下文就可以获取Spring容器中的Bean实例,比如当前业务中我的聚合服务A访问领域服务B,通过@FeignClient方式进行访问,那么此时getInstance中的name就是@FeignClient注解中的name或者value值,this.propertyName(\color{#000000}{(ribbon.client.name)})这个参数对应的值就为@FeignClient注解中的name或者value值


LoadBalancerClient

Spring Cloud 对外提供的负载均衡客户端接口,默认实现类也能看出使用的是RibbonLoadBalancerClient,那这个类是怎么用起来的呢?通过什么方式才能被用起来?我们放到Spring Cloud Ribbon 分析(二)之LoadBalancerAutoConfiguration进行总结!


入口配置类比较重要的几个分析总结告一段落!

上一篇下一篇

猜你喜欢

热点阅读