RestTemplate
通过添加LoadBalancerInterceptor
拦截器处理请求,LoadBalancerInterceptor
依赖LoadBalancerClient
实现负载均衡,而LoadBalancerClient
实际是委托ILoadBalancer
进行负载均衡逻辑处理。
public class RibbonLoadBalancerClient implements LoadBalancerClient {
//...private SpringClientFactory clientFactory@Overridepublic <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);}protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);}//...
}
本节将关注ILoadBalancer
对象的初始化,此对象由SpringClientFactory
的父类NamedContextFactory#createContext(String name)
完成初始化。
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);}@Overridepublic <C> C getInstance(String name, Class<C> type) {
C instance = super.getInstance(name, type);if (instance != null) {
return instance;}IClientConfig config = getInstance(name, IClientConfig.class);return instantiateWithConfig(getContext(name), type, config);}@Overrideprotected AnnotationConfigApplicationContext getContext(String name) {
return super.getContext(name);}//...
}
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<>();private Class<?> defaultConfigType;private final String propertySourceName;private final String propertyName;public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,String propertyName) {
this.defaultConfigType = defaultConfigType;this.propertySourceName = propertySourceName;this.propertyName = propertyName;}public void setConfigurations(List<C> configurations) {
for (C client : configurations) {
this.configurations.put(client.getName(), client);}}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) {
// 使用双重检查控制createContext()调用if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// 创建一个应用上下文并以name命名存入this.contexts.put(name, createContext(name));}}}return this.contexts.get(name);}protected AnnotationConfigApplicationContext createContext(String name) {
// 创建基于注解的上下文(IOC容器)AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();// 注册某个服务特定的配置类对象if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);}}// 注册适用服务的配置类对象for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);}}}// SpringClientFactory初始化时指定了defaultConfigType值为RibbonClientConfiguration.class// 注册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 beanscontext.setParent(this.parent);}context.setDisplayName(generateDisplayName(name));// 刷新容器context.refresh();return context;}//...
}
判断configurations
中是否存在key
为指定服务名的NamedContextFactory.Specification
对象,若存在则向容器中注册getConfiguration()
返回的类型实例
判断configurations
中是否存在key
为以default.
开头的NamedContextFactory.Specification
对象,若存在则向容器中注册getConfiguration()
返回的类型实例
向容器中注册PropertyPlaceholderAutoConfiguration
实例和defaultConfigType表示的实例
然后从容器中获取ILoadBalancer
对象
defaultConfigType的值RibbonClientConfiguration.class
由SpringClientFactory
初始化时指定
this.configurations的值由SpringClientFactory
初始化时指定
public class RibbonAutoConfiguration {
@Autowired(required = false)private List<RibbonClientSpecification> configurations = new ArrayList<>();@Beanpublic SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();factory.setConfigurations(this.configurations);return factory;}
}
RibbonClientSpecification对象创建
@RibbonClients(defaultConfiguration = EurekaRibbonClientConfiguration.class)
public class RibbonEurekaAutoConfiguration {
}@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {
RibbonClient[] value() default {
};Class<?>[] defaultConfiguration() default {
};}
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
Map<String, Object> attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true);if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client),client.get("configuration"));}}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"));}Map<String, Object> client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true);String name = getClientName(client);if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));}}private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;}String value = (String) client.get("value");if (!StringUtils.hasText(value)) {
value = (String) client.get("name");}if (StringUtils.hasText(value)) {
return value;}throw new IllegalStateException("Either 'name' or 'value' must be provided in @RibbonClient");}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());}}
由上述代码可知,EurekaRibbonClientConfiguration.class
将通过RibbonClientSpecification
的构造函数注入,其对应的name
值将以default.
开头
至此,我们知道了前面ILoadBalancer
对象初始化时向容器中先注册了EurekaRibbonClientConfiguration
对象然后注册了RibbonClientConfiguration
对象
@Configuration
public class RibbonClientConfiguration {
//...@Bean@ConditionalOnMissingBeanpublic IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();config.loadProperties(this.name);config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);return config;}@Bean@ConditionalOnMissingBeanpublic 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@ConditionalOnMissingBeanpublic 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@ConditionalOnMissingBeanpublic ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
return new PollingServerListUpdater(config);}@Bean@ConditionalOnMissingBeanpublic 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@ConditionalOnMissingBeanpublic RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,IClientConfig config, RetryHandler retryHandler) {
return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);}}
@Configuration
public class EurekaRibbonClientConfiguration {
//...@Bean@ConditionalOnMissingBeanpublic 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@ConditionalOnMissingBeanpublic 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;}//...
}
由于EurekaRibbonClientConfiguration
先注册,且ribbonPing(IClientConfig config)
、ribbonServerList(IClientConfig config, Provider<EurekaClient> eurekaClientProvider)
上使用了 @ConditionalOnMissingBean
注解,故ILoadBalancer
中注入的IPing
、ServerList
对象由EurekaRibbonClientConfiguration
中生成。
分析完ILoadBalancer
初始化流程后,我们可知
- 我们可以通过
@RibbonClient
、@RibbonClients
为特定服务或全部服务指定配置类
@RibbonClient
为单个服务指定配置类@Configuration //@RibbonClient(value = "hello-service", configuration = MyRibbonClientConfiguration.class) @RibbonClient(name = "hello-service", configuration = MyRibbonClientConfiguration.class) public class ConsumerRibbonConfiguration { }
@RibbonClients
为多个服务或全部服务指定配置类@Configuration /*@RibbonClients(value={@RibbonClient(value="hello-service", configuration = MyRibbonClientConfiguration.class)})*/ @RibbonClients(defaultConfiguration = MyRibbonClientConfiguration.class) public class ConsumerRibbonConfiguration { }
- 配置优先级
针对单个服务的配置优先于针对全部服务的配置,针对全部的配置优先于系统默认配置(RibbonClientConfiguration) - 自定义配置类路径
Spring Cloud提供的配置类是注册在服务对应的上下文中,其父容器为应用上下文。Spring Boot默认扫描主程序所在包及其下面的所有子包里面的组件,若自定义配置类在Spring Boot默认扫描路径下,则该配置类将注册到应用上下文(即父容器)中为所用服务共享。官方建议将自定义配置放在应用程序扫描路径之外。 - 使用属性配置
<clientName>.ribbon.NFLoadBalancerClassName 其值应为实现了ILoadBalancer接口的类
<clientName>.ribbon.NFLoadBalancerRuleClassName 其值应为实现了IRule接口的类
<clientName>.ribbon.NFLoadBalancerPingClassName 其值应为实现了IPing接口的类
<clientName>.ribbon.NIWSServerListClassName 其值应为实现了ServerList接口的类
<clientName>.ribbon.NIWSServerListFilterClassName 其值应为实现了ServerListFilter接口的类
例如 为名为hello-service的服务配置IPing
hello-service.ribbon.NFLoadBalancerPingClassName=com.netflix.loadbalancer.NoOpPing
- Spring Cloud中
ILoadBalancer
的初始化发生在第一次请求该服务时,可以通过更改属性使特定服务对应的ILoadBalancer
在启动时初始化
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=hello-service
参考资料 官方文档