SpingCloud整合Ribbon的源码总结

先上源码的总结图,此图会在博客的后半段着重讲解:

SpringCloud整合Ribbon的配置原理图

假设我有多个微服务应用,名称都叫:SERVICE-EUREKA,当我执行RPC调用时:

restTemplate.getForObject("http://SERVICE-EUREKA/hello?name=" + name, String.class)

Ribbon的作用就是从当前容器中获取 “SERVICE-EUREKA” 对应的Eureka的服务信息,然后根据一定的规则从中选择一个服务,然后将 “SERVICE-EUREKA” 替换为所选择服务的 IP 地址,然后发起HTTP RPC 调用。

从Ribbon的功能上看挺简单的,接下来简要的梳理下SpringCloud整合Ribbon的源码。

SpringCloud-commons依赖默认引入LoadBalancerAutoConfiguration配置:

public class LoadBalancerAutoConfiguration {

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
            final List<RestTemplateCustomizer> customizers) {
        return new SmartInitializingSingleton() {
            @Override
            public void afterSingletonsInstantiated() {
                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                    for (RestTemplateCustomizer customizer : customizers) {
                        customizer.customize(restTemplate);
                    }
                }
            }
        };
    }
    ......
}

其对所有标识了@LoadBalanced的RestTemplate的Bean做自定义拓展,这样就能自定义拓展组件对Http请求做相应拓展。

Eureka默认整合Ribbon,在其Jar包下的spring.factories文件内,引入 RibbonEurekaAutoConfiguration 配置类:

......
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
    ......
}

上述配置类主要是通过@RibbonClients 引入了一个默认配置类,会将这个配置类注册为一个RibbonClientSpecification类型的Bean,EurekaRibbonClientConfiguration 这个类作为此Bean的构造函数的一个参数:

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
    ......
    private void registerClientConfiguration(BeanDefinitionRegistry registry,
            Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);            // default.RibbonClientSpecification类全名
        builder.addConstructorArgValue(configuration);   // EurekaRibbonClientConfiguration  配置类
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }
}

在neiflix-core的Jar包下的spring.factories文件内,会引入RibbonAutoConfiguration配置类,虽然此类也标识了 @RibbonClients注解,但是因为注解上没有任何有用信息,其目的引入@RibbonClient注册的解析器。虽然@RibbonClients没有属性,但也会注册相应的RibbonClientSpecification,只是构造函数没有值而已。

不过此配置类上定义了一个Bean,SpringClientFactory:

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());
    }
    ......

    @Configuration
    @ConditionalOnClass(HttpRequest.class)
    @ConditionalOnRibbonRestClient
    protected static class RibbonClientConfig {

        @Autowired
        private SpringClientFactory springClientFactory;

        @Bean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
            return new RestTemplateCustomizer() {
                @Override
                public void customize(RestTemplate restTemplate) {
                    restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
                }
            };
        }

        @Bean
        public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
            return new RibbonClientHttpRequestFactory(this.springClientFactory);
        }
    }
}

从上述三个类的代码上看,主要是定义了一个SpringClientFactory 的Bean,将刚刚说明的两个配置类填充入此Bean。然后通过SpringClientFactory 定义另外两个Bean:

  1. RibbonClientHttpRequestFactory : Http请求工厂,作用是通过URL和Method生成HttpRequest对象。将此工厂通过RestTemplate的自定义拓展器注入到所有标识了@LoadBalanced的RestTemplate的Bean中
  2. RibbonLoadBalancerClient :负载均衡客户端,此Bean会被注入一个HttpRequest的拦截器中,从服务中选择一个服务,使用其IP地址替换之前生成的HttpRequest的服务名称。

Ribbon的配置将在以上两个步骤起作用,配置信息都存放在SpringClientFactory 中。SpringClientFactory 是SpringCloud拓展NamedContextFactory,用来整合Ribbon的一个容器工厂类。

下面接着看SpringClientFactory 的实例化过程:

// Spring内置客户端工厂
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

    static final String NAMESPACE = "ribbon";

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

// 命名的上下文工厂,可以理解为可命名的内置容器工厂
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {

    private Class<?> defaultConfigType;  // 默认配置类
    private final String propertySourceName;
    private final String propertyName;
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();  // 内置容器集合,key是应用名称
    private Map<String, C> configurations = new ConcurrentHashMap<>();  // 可用配置类,key是应用名称或者default开头

    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
            String propertyName) {
        this.defaultConfigType = defaultConfigType;
        this.propertySourceName = propertySourceName;
        this.propertyName = propertyName;
    }
    // 将RibbonClientSpecification 的配置按 name > class 的形式保存
    public void setConfigurations(List<C> configurations) {
        for (C client : configurations) {
            this.configurations.put(client.getName(), client);
        }
    }
}

在定义SpringClientFactory 的Bean过程中,将 RibbonClientConfiguration 作为内置容器的默认配置,RibbonAutoConfiguration和EurekaRibbonClientConfiguration作为可用配置,这两个配置的key都是default . 开头的,但RibbonAutoConfiguration未指定配置类,会被忽略。当然也可能有自定义的配置,比如用 @RibbonClient 来引入的配置信息,也会被放入可用配置集合中,key是注解上指定的应用名称。

接下来我们看Ribbon调用SpringClientFactory 配置的场所:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient 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();  // 获取服务名称, 也就是最开始的  "SERVICE-EUREKA"
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }
}

Ribbon的负载均衡客户端会被注入一个拦截器,此拦截器拦截RestTemplate发送的请求。

public class RibbonLoadBalancerClient implements LoadBalancerClient {

    private SpringClientFactory clientFactory;

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);   // 根据服务名称获取负载均衡器
        Server server = getServer(loadBalancer);  // 根据负载均衡器获取被选中的服务
        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);
    }
    // 从SpringClientFactory 里获取指定服务名称对应的负载均衡器
    protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
    }
    // 从SpringClientFactory 里获取指定服务名称对应的服务内省器
    private ServerIntrospector serverIntrospector(String serviceId) {
        ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
                ServerIntrospector.class);
        if (serverIntrospector == null) {
            serverIntrospector = new DefaultServerIntrospector();
        }
        return serverIntrospector;
    }
}

当然 RibbonLoadBalancerClient 里还有几个从SpringClientFactory 获取指定组件的方法,这里就不一一列举了。RibbonClientHttpRequestFactory里的从SpringClientFactory 获取指定组件的方法也类似,不再列举。

下面我们就以getLoadBalancer(serviceId) 方法为例,看看SpringClientFactory 获取指定服务的特定组件的方法,是如何关联到之前定义的Ribbon配置的:

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

    /** 获取指定服务的负载均衡器
     * Get the load balancer associated with the name.
     * @throws RuntimeException if any error occurs
     */
    public ILoadBalancer getLoadBalancer(String name) {
        return getInstance(name, ILoadBalancer.class);
    }

    @Override
    public <C> C getInstance(String name, Class<C> type) {
        C instance = super.getInstance(name, type);  // 尝试从父类获取
        if (instance != null) {
            return instance;
        }
        // 不存在,那么获取IClientConfig,然后通过此类来实例化
        IClientConfig config = getInstance(name, IClientConfig.class);  
        return instantiateWithConfig(getContext(name), type, config);
    }
}

我们先看 super.getInstance(name, type) 的实现逻辑:

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

    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();  // 内置容器集合
    private Map<String, C> configurations = new ConcurrentHashMap<>();  // 所有配置类,value类型为:RibbonClientSpecification
    private Class<?> defaultConfigType;  // 默认配置类

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

    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) {
        // 创建容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 如果配置类上含有指定应用名称的键值对,那么将此 RibbonClientSpecification 的 configuration 类都注册到内置容器中
        // 可通过@RibbonClient将自定义Ribbon服务配置引入此容器中
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        // 配置类的key 是否是 "default." 开头的,如果是,那么也注册到服务对应的内置容器中
        // EurekaRibbonClientConfiguration 就是默认的配置,在此步骤注册
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        // 注册默认的配置和环境变量替换器 , RibbonClientConfiguration 就是在此步骤注册
        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);
        }
        // 刷新容器,实例化所有Bean
        context.refresh();
        return context;
    }
}

上述代码最关键的就是创建服务对应的内置容器,三种配置类按照顺序注册入容器中。注意:他们注册的顺序是有实际含义的!!!

Ribbon默认的两个配置类里,他们定义的Bean的方法上大多数都标识了 @ConditionalOnMissingBean 注解,也就是说只有当前容器内还没有定义此类型的Bean时才会生效:

// Eureka整合Ribbon的配置
public class EurekaRibbonClientConfiguration {

    @Autowired
    private PropertiesFactory propertiesFactory;

    ......
    @Bean
    @ConditionalOnMissingBean
    public ServerList<?> ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider) {
        if (this.propertiesFactory.isSet(ServerList.class, serviceId)) {
            return this.propertiesFactory.get(ServerList.class, config, serviceId);
        }
        DiscoveryEnabledNIWSServerList discoveryServerList = new DiscoveryEnabledNIWSServerList(
                config, eurekaClientProvider);
        DomainExtractingServerList serverList = new DomainExtractingServerList(
                discoveryServerList, config, this.approximateZoneFromHostname);
        return serverList;
    }
}
// Ribbon的原生配置
public class RibbonClientConfiguration {

    @Autowired
    private PropertiesFactory propertiesFactory;

    ......
    @Bean
    @ConditionalOnMissingBean
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerList.class, name)) {
            return this.propertiesFactory.get(ServerList.class, config, name);
        }
        ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
        serverList.initWithNiwsConfig(config);
        return serverList;
    }
}

就像上述对于ServerList< ? > 类型的Bean的定义,两个配置类都定义了,但是因为他们都标识了@ConditionalOnMissingBean 注解,也就是说哪个配置类先加载,就用哪个配置类的Bean。

我们在看服务对应的内置容器的初始化过程源码,他们先加载服务名称对应的配置类,然后加载 “default .” 开头的配置类,最后加载默认的配置类。也就是我们通过 @RibbonClient 引入的配置类最先加载,然后加载后续的配置。

如果我们通过@RibbonClient注解的方式引入一个自定义配置类:


@RibbonClient(value = "SERVICE-EUREKA", configuration = {DefaultRibbonClientConfig.class})

@Configuration
public class DefaultRibbonClientConfig {

    /**
     * 自定义负载均衡准则
     *
     * @return
     */
    @Bean
    public IRule ribbonRule() {
        return new RoundRobinRule();
    }
}

那么我们就自定义了IRule 这个类型的Bean,然后将此Bean引入了内置容器中,对服务名称为 “SERVICE-EUREKA” 的服务其作用。

最好不要将此配置类加载到Spring主容器中,因为其仅仅对某一个服务起作用。这样我们可以对不同的服务定制不同的配置,Bean的类型和名称都相同,他们都存在于各自服务名称对应的子容器中,互不干扰。

如果指定的组件Bean不在自定义配置类,或者说没有自定义配置类,那么接着加载 “default.”开头的配置类: EurekaRibbonClientConfiguration 。如果还不存在,那么最后加载默认的配置类:RibbonClientConfiguration。

如上述情况,两个配置类中都定义了ServerList 类型的Bean,按照加载顺序,最终会使用 EurekaRibbonClientConfiguration 定义的 ServerList 。

在实例化EurekaRibbonClientConfiguration 的ServerList 的Bean时,会先查看 PropertiesFactory 里 是否已经设置了此组件对应的数据。如果是,那么返回预先定义好的;如果不是,那么实时生成。下面我们就看看PropertiesFactory 里的组件设置源码:

public class PropertiesFactory {
    @Autowired
    private Environment environment;

    private Map<Class, String> classToProperty = new HashMap<>();
    // 初始化组件类型和key的映射关系
    public PropertiesFactory() {  
        classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
        classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
        classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
        classToProperty.put(ServerList.class, "NIWSServerListClassName");
        classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
    }
    // 环境里是否设置了指定服务的指定组件实现
    public boolean isSet(Class clazz, String name) {
        return StringUtils.hasText(getClassName(clazz, name));
    }
    // 获取指定服务的指定组件在环境里的key
    public String getClassName(Class clazz, String name) {
        if (this.classToProperty.containsKey(clazz)) {
            String classNameProperty = this.classToProperty.get(clazz);
            String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
            return className;
        }
        return null;
    }

    // 获取指定组件
    public <C> C get(Class<C> clazz, IClientConfig config, String name) {
        String className = getClassName(clazz, name);
        if (StringUtils.hasText(className)) {
            try {
                Class<?> toInstantiate = Class.forName(className);
                return (C) instantiateWithConfig(toInstantiate, config);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name);
            }
        }
        return null;
    }
}

从上述代码上能够看出,如果在Spring的环境里配置了相应组件的键值对,比如:

SERVICE-EUREKA.ribbon.NFLoadBalancerRuleClassName = com.netflix.loadbalancer.RoundRobinRule

那么服务SERVICE-EUREKA的负载均衡准则就会使用RoundRobinRule,忽略EurekaRibbonClientConfiguration 和 RibbonClientConfiguration 的默认配置。

配置的加载和使用策略就如本篇博客最开始的那张图一样。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值