系列文章:
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