SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置

·  阅读 1398
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置

系列文章:

SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化

SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约

SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表

SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制

SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群

SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇

SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate

SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理

Ribbon 核心接口

前面已经了解到 Ribbon 核心接口以及默认实现如何协作来查找要调用的一个实例,这节再来看下各个核心接口的一些特性及其它实现类。

客户端配置 — IClientConfig

IClientConfig 就是管理客户端配置的核心接口,它的默认实现类是 DefaultClientConfigImpl。可以看到在创建 IClientConfig 时,设置了 Ribbon 客户端默认的连接和读取超时时间为 1秒,例如读取如果超过 1秒,就会返回超时,这两个一般需要根据实际情况来调整。

@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
    DefaultClientConfigImpl config = new DefaultClientConfigImpl();
    // 加载配置
    config.loadProperties(this.name);
    // 连接超时默认 1 秒
    config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
    // 读取超时默认 1 秒
    config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
    config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
    return config;
}
复制代码

CommonClientConfigKey 这个类定义了 Ribbon 客户端相关的所有配置的键常量,可以通过这个类来看有哪些配置。

public abstract class CommonClientConfigKey<T> implements IClientConfigKey<T> {

    public static final IClientConfigKey<String> AppName = new CommonClientConfigKey<String>("AppName"){};

    public static final IClientConfigKey<Integer> MaxAutoRetries = new CommonClientConfigKey<Integer>("MaxAutoRetries"){};

    public static final IClientConfigKey<Integer> MaxAutoRetriesNextServer = new CommonClientConfigKey<Integer>("MaxAutoRetriesNextServer"){};

    public static final IClientConfigKey<Boolean> OkToRetryOnAllOperations = new CommonClientConfigKey<Boolean>("OkToRetryOnAllOperations"){};

    public static final IClientConfigKey<Integer> ConnectTimeout = new CommonClientConfigKey<Integer>("ConnectTimeout"){};

    public static final IClientConfigKey<Integer> BackoffInterval = new CommonClientConfigKey<Integer>("BackoffTimeout"){};

    public static final IClientConfigKey<Integer> ReadTimeout = new CommonClientConfigKey<Integer>("ReadTimeout"){};

    //LoadBalancer Related
    public static final IClientConfigKey<Boolean> InitializeNFLoadBalancer = new CommonClientConfigKey<Boolean>("InitializeNFLoadBalancer"){};

    public static final IClientConfigKey<String> NFLoadBalancerClassName = new CommonClientConfigKey<String>("NFLoadBalancerClassName"){};

    public static final IClientConfigKey<String> NFLoadBalancerRuleClassName = new CommonClientConfigKey<String>("NFLoadBalancerRuleClassName"){};

    public static final IClientConfigKey<String> NFLoadBalancerPingClassName = new CommonClientConfigKey<String>("NFLoadBalancerPingClassName"){};

    public static final IClientConfigKey<Integer> NFLoadBalancerPingInterval = new CommonClientConfigKey<Integer>("NFLoadBalancerPingInterval"){};

    public static final IClientConfigKey<Integer> NFLoadBalancerMaxTotalPingTime = new CommonClientConfigKey<Integer>("NFLoadBalancerMaxTotalPingTime"){};

    public static final IClientConfigKey<String> NFLoadBalancerStatsClassName = new CommonClientConfigKey<String>("NFLoadBalancerStatsClassName"){};

    public static final IClientConfigKey<String> NIWSServerListClassName = new CommonClientConfigKey<String>("NIWSServerListClassName"){};

    public static final IClientConfigKey<String> ServerListUpdaterClassName = new CommonClientConfigKey<String>("ServerListUpdaterClassName"){};

    public static final IClientConfigKey<String> NIWSServerListFilterClassName = new CommonClientConfigKey<String>("NIWSServerListFilterClassName"){};

    public static final IClientConfigKey<Integer> ServerListRefreshInterval = new CommonClientConfigKey<Integer>("ServerListRefreshInterval"){};

    // ...
}
复制代码

进入到 DefaultClientConfigImpl,可以看到 CommonClientConfigKey 中的每个配置都对应了一个默认值。在加载配置的时候,如果用户没有定制配置,就会使用默认的配置。

public class DefaultClientConfigImpl implements IClientConfig {

    public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing"; // DummyPing.class.getName();

    public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule";

    public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer";

    public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;

    public static final int DEFAULT_MAX_AUTO_RETRIES = 0;

    public static final int DEFAULT_BACKOFF_INTERVAL = 0;

    public static final int DEFAULT_READ_TIMEOUT = 5000;

    public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;

    public static final int DEFAULT_CONNECT_TIMEOUT = 2000;

    public static final String DEFAULT_SEVER_LIST_CLASS = "com.netflix.loadbalancer.ConfigurationBasedServerList";

    public static final String DEFAULT_SERVER_LIST_UPDATER_CLASS = "com.netflix.loadbalancer.PollingServerListUpdater";

    public String getDefaultNfloadbalancerPingClassname() {
        return DEFAULT_NFLOADBALANCER_PING_CLASSNAME;
    }

    public String getDefaultNfloadbalancerRuleClassname() {
        return DEFAULT_NFLOADBALANCER_RULE_CLASSNAME;
    }

    public String getDefaultNfloadbalancerClassname() {
        return DEFAULT_NFLOADBALANCER_CLASSNAME;
    }

    public int getDefaultMaxRetriesPerServerPrimeConnection() {
        return DEFAULT_MAX_RETRIES_PER_SERVER_PRIME_CONNECTION;
    }

    public Boolean getDefaultEnablePrimeConnections() {
        return DEFAULT_ENABLE_PRIME_CONNECTIONS;
    }

    //....
}
复制代码

也可以在配置文件中定制配置,例如配置超时和重试:

# 全局配置
ribbon:
  # 客户端读取超时时间
  ReadTimeout: 3000
  # 客户端连接超时时间
  ConnectTimeout: 3000
  # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  OkToRetryOnAllOperations: false
  # 重试次数
  MaxAutoRetries: 1
  # 最多重试几个实例
  MaxAutoRetriesNextServer: 1

# 只针对 demo-producer 客户端
demo-producer:
  ribbon:
    # 客户端读取超时时间
    ReadTimeout: 5000
    # 客户端连接超时时间
    ConnectTimeout: 3000
复制代码

均衡策略 — IRule

IRule 是最终选择 Server 的策略规则类,核心的接口就是 choose

public interface IRule{
    // 选择 Server
    public Server choose(Object key);

    // 设置 ILoadBalancer
    public void setLoadBalancer(ILoadBalancer lb);

    // 获取 ILoadBalancer
    public ILoadBalancer getLoadBalancer();
}
复制代码

Ribbon 提供了丰富的负载均衡策略,我们也可以通过配置指定使用某个均衡策略。下面是整个Ribbon提供的 IRule 均衡策略。

服务检查 — IPing

IPing 是用于定期检查 Server 的可用性的,它只提供了一个接口,用来判断 Server 是否存活:

public interface IPing {
    public boolean isAlive(Server server);
}
复制代码

IPing 也提供了多种策略可选,下面是整个 IPing 体系结构:

获取服务列表 — ServerList

ServerList 提供了两个接口,一个是第一次获取 Server 列表,一个是更新 Server 列表,其中 getUpdatedListOfServers 会每被 Loadbalancer 隔 30 秒调一次来更新 allServerList。

public interface ServerList<T extends Server> {

    public List<T> getInitialListOfServers();

    /**
     * Return updated list of servers. This is called say every 30 secs
     * (configurable) by the Loadbalancer's Ping cycle
     */
    public List<T> getUpdatedListOfServers();
}
复制代码

ServerList 也提供了多种实现,ServerList 体系结构如下:

过滤服务 — ServerListFilter

ServerListFilter 提供了一个接口用来过滤出可用的 Server。

public interface ServerListFilter<T extends Server> {
    public List<T> getFilteredListOfServers(List<T> servers);
}
复制代码

ServerListFilter 体系结构如下:

服务列表更新 — ServerListUpdater

ServerListUpdater 有多个接口,最核心的就是 start 开启定时任务调用 updateAction 来更新 allServerList

public interface ServerListUpdater {

    /**
     * an interface for the updateAction that actually executes a server list update
     */
    public interface UpdateAction {
        void doUpdate();
    }

    /**
     * start the serverList updater with the given update action
     * This call should be idempotent.
     */
    void start(UpdateAction updateAction);
}
复制代码

默认有两个实现类:

负载均衡器 — ILoadBalancer

ILoadBalancer 是负载均衡选择服务的核心接口,主要提供了如下的获取Server列表和根据客户端名称选择Server的接口。

public interface ILoadBalancer {
    // 添加Server
    public void addServers(List<Server> newServers);

    // 根据key选择一个Server
    public Server chooseServer(Object key);

    // 获取存活的Server列表,返回 upServerList
    public List<Server> getReachableServers();

    // 获取所有Server列表,返回 allServerList
    public List<Server> getAllServers();
}
复制代码

ILoadBalancer 的体系结构如下:

Ribbon 相关配置类

从前面一直看下来,可以发现有很多与 Ribbon 相关的配置类,这里总结下与 Ribbon 相关的配置类,看每个配置类的配置顺序,以及都主要配置了哪些东西。

EurekaClientAutoConfiguration

首先是Eureka客户端配置类 EurekaClientAutoConfiguration,这个自动化配置类主要配置了 Ribbon 所需的 EurekaClient。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
        "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
        "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
        "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    // ....
}
复制代码

RibbonAutoConfiguration

接着是Ribbon自动化配置类 RibbonAutoConfiguration,这个类主要配置了如下类:

  • SpringClientFactory:管理 Ribbon 客户端上下文。
  • LoadBalancerClient:负载均衡客户端,默认实现类为 RibbonLoadBalancerClient(实际是在 RibbonClientConfiguration 中配置的)。
  • PropertiesFactory:用于判断配置文件中是否自定义了核心接口的实现类,如 NFLoadBalancerClassName、NFLoadBalancerPingClassName 等。
  • RibbonApplicationContextInitializer:开启饥饿配置的时候,用这个类来在启动时初始化 Ribbon 客户端上下文。
package org.springframework.cloud.netflix.ribbon;

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
// 在 EurekaClientAutoConfiguration 之后配置
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
// 在 LoadBalancerAutoConfiguration、AsyncLoadBalancerAutoConfiguration 之前配置
@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);
    }

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

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

    @Bean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }

    @Bean
    @ConditionalOnProperty("ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients());
    }
}
复制代码

LoadBalancerAutoConfiguration

接着是负载均衡器配置类 LoadBalancerAutoConfiguration,这个类主要是创建了负载均衡拦截器 LoadBalancerInterceptor,并添加到 RestTemplae 的拦截器中。

package org.springframework.cloud.client.loadbalancer;

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

    // 对 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 {

        // 创建 RestTemplate 拦截器
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,
                                                         LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        // RestTemplate 客制化器
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }
}
复制代码

RibbonClientConfiguration

之后是默认的 Ribbon 客户端配置类 RibbonClientConfiguration,这个类主要配置了 Ribbon 核心接口的默认实现。

  • IClientConfig:Ribbon 客户端配置类,默认实现是 DefaultClientConfigImpl。
  • IRule:负载均衡策略规则组件,默认实现是 ZoneAvoidanceRule。
  • IPing:判断 Server 是否存活,默认实现是 DummyPing,永远都是返回 true。
  • ServerList:获取 Server 的组件,默认实现类为 ConfigurationBasedServerList,从配置文件获取。
  • ServerListUpdater:Server 列表更新组件,默认实现类为 PollingServerListUpdater。
  • ServerListFilter:过滤可用的 Server 列表,默认实现类为 ZonePreferenceServerListFilter。
  • RibbonLoadBalancerContext:负载均衡客户端。
  • RetryHandler:重试处理器,默认实现类为 DefaultLoadBalancerRetryHandler。
package org.springframework.cloud.netflix.ribbon;

@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
        RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
    public static final int DEFAULT_READ_TIMEOUT = 1000;
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;

    @RibbonClientName
    private String name = "client";
    @Autowired
    private PropertiesFactory propertiesFactory;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
        config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
        config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
        return config;
    }

    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }

    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        if (this.propertiesFactory.isSet(IPing.class, name)) {
            return this.propertiesFactory.get(IPing.class, config, name);
        }
        return new DummyPing();
    }

    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("unchecked")
    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;
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
        return new PollingServerListUpdater(config);
    }

    @Bean
    @ConditionalOnMissingBean
    public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
            ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
            IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
        if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
            return this.propertiesFactory.get(ILoadBalancer.class, config, name);
        }
        return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
                serverListFilter, serverListUpdater);
    }

    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("unchecked")
    public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
            return this.propertiesFactory.get(ServerListFilter.class, config, name);
        }
        ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
        filter.initWithNiwsConfig(config);
        return filter;
    }

    @Bean
    @ConditionalOnMissingBean
    public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
            IClientConfig config, RetryHandler retryHandler) {
        return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
    }

    @Bean
    @ConditionalOnMissingBean
    public RetryHandler retryHandler(IClientConfig config) {
        return new DefaultLoadBalancerRetryHandler(config);
    }

    @Bean
    @ConditionalOnMissingBean
    public ServerIntrospector serverIntrospector() {
        return new DefaultServerIntrospector();
    }
}
复制代码

RibbonEurekaAutoConfiguration

Ribbon Eureka 自动化配置类 RibbonEurekaAutoConfiguration,判断是否启用 Ribbon Eureka,并触发 EurekaRibbonClientConfiguration 配置类。

package org.springframework.cloud.netflix.ribbon.eureka;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {

}
复制代码

EurekaRibbonClientConfiguration

默认启用 Ribbon Eureka 的情况下,会使用 Ribbon Eureka 客户端配置类 EurekaRibbonClientConfiguration

  • IPing:替换了默认实现类 DummyPing,改为 NIWSDiscoveryPing,通过判断 InstanceInfo 的状态是否为 UP 来判断 Server 是否存活。
  • ServerList:替换了默认的实现类 ConfigurationBasedServerList,改为 DomainExtractingServerList,实际是 DiscoveryEnabledNIWSServerList,从 EurekaClient 获取 Server 列表。
package org.springframework.cloud.netflix.ribbon.eureka;

@Configuration(proxyBeanMethods = false)
public class EurekaRibbonClientConfiguration {

    @Value("${ribbon.eureka.approximateZoneFromHostname:false}")
    private boolean approximateZoneFromHostname = false;
    @RibbonClientName
    private String serviceId = "client";
    @Autowired(required = false)
    private EurekaClientConfig clientConfig;
    @Autowired(required = false)
    private EurekaInstanceConfig eurekaConfig;
    @Autowired
    private PropertiesFactory propertiesFactory;

    public EurekaRibbonClientConfiguration() {
    }

    public EurekaRibbonClientConfiguration(EurekaClientConfig clientConfig, String serviceId, EurekaInstanceConfig eurekaConfig, boolean approximateZoneFromHostname) {
        this.clientConfig = clientConfig;
        this.serviceId = serviceId;
        this.eurekaConfig = eurekaConfig;
        this.approximateZoneFromHostname = approximateZoneFromHostname;
    }

    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        if (this.propertiesFactory.isSet(IPing.class, serviceId)) {
            return this.propertiesFactory.get(IPing.class, config, serviceId);
        }
        NIWSDiscoveryPing ping = new NIWSDiscoveryPing();
        ping.initWithNiwsConfig(config);
        return ping;
    }

    @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;
    }
}
复制代码

各个配置类所属模块

spring-cloud-netflix-eureka-client:

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration
  • org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
  • org.springframework.cloud.netflix.ribbon.eureka.EurekaRibbonClientConfiguration

spring-cloud-netflix-ribbon:

  • org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
  • org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

spring-cloud-commons:

  • org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

客户端 Ribbon定制

RibbonClientConfiguration 中创建 IRule、IPing、ServerList<Server>、ServerListFilter<Server>、ILoadBalancer 时,都先通过 propertiesFactory.isSet 判断是否已配置了对应类型的实现类,没有才使用默认的实现类。

也就是说针对特定的服务,这几个类可以自行定制化,也可以通过配置指定其它的实现类。这节我们就来看下 Ribbon 如何自定义一些配置。

客户端 Ribbon 有三个方面的配置:全局策略配置基于注解的配置配置文件配置,这几种配置方式的优先级顺序是 配置文件配置 > @RibbonClients注解配置 > 全局配置 > 默认配置

全局策略配置

如果想要全局更改配置,需要加一个配置类,比如像下面这样:

@Configuration
public class GlobalRibbonConfiguration {

    @Bean
    public IRule ribbonRule() {
        return new RandomRule();
    }

    @Bean
    public IPing ribbonPing() {
        return new NoOpPing();
    }
}
复制代码

基于注解的配置

如果想针对某一个服务定制配置,可以通过 @RibbonClients 来配置特定服务的配置类。

需要先定义一个服务配置类:

@Configuration
public class ProducerRibbonConfiguration {

    @Bean
    public IRule ribbonRule() {
        return new RandomRule();
    }

    @Bean
    public IPing ribbonPing() {
        return new NoOpPing();
    }
}
复制代码

@RibbonClients 注解为服务指定特定的配置类,并排除掉,不让 Spring 扫描到,否则就变成了全局配置了。

@RibbonClients({
    @RibbonClient(name = "demo-producer", configuration = ProducerRibbonConfiguration.class)
})
@ComponentScan(excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ProducerRibbonConfiguration.class)
})
public class XxxConfiguration {
}
复制代码

配置文件配置

通过配置文件的方式来配置,配置的格式就是 <服务名称>.ribbon.<属性>:

demo-producer:
  ribbon:
    # ILoadBalancer
    NFLoadBalancerClassName: com.netflix.loadbalancer.NoOpLoadBalancer
    # IRule
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    # IPing
    NFLoadBalancerPingClassName:
    # ServerList<Server>
    NIWSServerListClassName:
    # ServerListFilter<Server>
    NIWSServerListFilterClassName:
复制代码

Ribbon 上下文饥饿加载

上篇文章说过,在从 SpringClientFactory 中获取 ILoadBalancer 等组件时,是在运行时创建获取的。也就是这个Ribbon客户端的应用上下文默认是懒加载的,并不是在启动的时候就加载上下文,而是在第一次调用的时候才会去初始化。

如果想服务启动时就初始化,可以指定Ribbon客户端的具体名称,在启动的时候就加载配置项的上下文:

ribbon:
  eager-load:
    enabled: true
    # 要在启动时就理解初始化的客户端名称
    clients: demo-producer,demo-xxx
复制代码

RibbonAutoConfiguration 配置类中可以找到这个饥饿配置,如果开启了饥饿加载,就会创建 RibbonApplicationContextInitializer 来在启动时初始化上下文。

@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
    return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients());
}

public class RibbonApplicationContextInitializer implements ApplicationListener<ApplicationReadyEvent> {
    private final SpringClientFactory springClientFactory;
    // List of Ribbon client names
    private final List<String> clientNames;

    public RibbonApplicationContextInitializer(SpringClientFactory springClientFactory, List<String> clientNames) {
        this.springClientFactory = springClientFactory;
        this.clientNames = clientNames;
    }

    protected void initialize() {
        if (clientNames != null) {
            for (String clientName : clientNames) {
                // 提前初始化上下文
                this.springClientFactory.getContext(clientName);
            }
        }
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        initialize();
    }
}
复制代码

Ribbon 脱离Eureka使用

在默认情况下,Ribbon 客户端会从 EurekaClient 获取服务列表,其实就是间接从注册中心读取服务注册信息列表,来达到动态负载均衡的功能。但如果不想从 EurekaClient 读取,可以禁用 Ribbon 的 Eureka 功能。 在 Ribbon 中禁用Eureka功能,可以做如下配置:

ribbon:
  eureka:
    enabled: false
复制代码

ribbon.eureka.enabled 是如何控制禁用 Eureka 的呢?看 RibbonEurekaAutoConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnRibbonAndEurekaEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {

}
复制代码

这个配置类通过 @RibbonClients 指定了默认的客户端配置类为 EurekaRibbonClientConfiguration,但生效的前提是 @ConditionalOnRibbonAndEurekaEnabled,进去可以看到这个条件注解就是判断 Ribbon Eureka 是否启用了,就可以设置 ribbon.eureka.enabled=false 来禁用 RIbbon Eureka。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 条件类 OnRibbonAndEurekaEnabledCondition
@Conditional(ConditionalOnRibbonAndEurekaEnabled.OnRibbonAndEurekaEnabledCondition.class)
public @interface ConditionalOnRibbonAndEurekaEnabled {

    class OnRibbonAndEurekaEnabledCondition extends AllNestedConditions {

        OnRibbonAndEurekaEnabledCondition() {
            super(ConfigurationPhase.REGISTER_BEAN);
        }

        // 引入了类:DiscoveryEnabledNIWSServerList
        @ConditionalOnClass(DiscoveryEnabledNIWSServerList.class)
        // 存在bean对象:SpringClientFactory
        @ConditionalOnBean(SpringClientFactory.class)
        // ribbon.eureka.enabled=true
        @ConditionalOnProperty(value = "ribbon.eureka.enabled", matchIfMissing = true)
        static class Defaults {

        }

        // 存在bean对象:EurekaClient
        @ConditionalOnBean(EurekaClient.class)
        static class EurekaBeans {

        }

        // eureka.client.enabled=true
        @ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
        @ConditionalOnDiscoveryEnabled
        static class OnEurekaClientEnabled {

        }
    }
}
复制代码

如果想从其它地方获取服务列表,可以自定义接口实现 ServerList<Server> 来获取,也可以在配置文件中设置地址列表:

<client-name>:
  ribbon:
    listOfServers: http://10.215.0.92:8010,http://10.215.0.92:8011
复制代码
分类:
后端
分类:
后端
收藏成功!
已添加到「」, 点击更改