【SpringCloud】Ribbon如何自定义客户端配置和全局配置

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 【SpringCloud】Ribbon如何自定义客户端配置和全局配置起因事情的起因是这样的,公司内部要实现基于Zuul网关的灰度路由,在上线时进行灰度测试,故需要配置业务微服务向Eureka注册的metadata元数据,和自定义Ribbon的负载规则达到只访问灰度服务的目的。

【SpringCloud】Ribbon如何自定义客户端配置和全局配置
起因
事情的起因是这样的,公司内部要实现基于Zuul网关的灰度路由,在上线时进行灰度测试,故需要配置业务微服务向Eureka注册的metadata元数据,和自定义Ribbon的负载规则达到只访问灰度服务的目的。这样就需要自定义Ribbon的IRule,实现灰度请求只会负载到带有灰度标签元数据的业务微服务上,当自定义IRule规则开发好后,问题是如何将这个IRule规则配置给某个Ribbon Client或者全局生效。

本次使用Spring Cloud Dalston.SR5版本

在其 官方文档 中其实已经给出了一些如何针对某个Client 或者 修改默认配置的方式,但没有说明为什么这样使用

下面将按照这样的思路分析:

简单分析Spring Cloud Ribbon启动时如何自动配置的,以了解其装配到Spring中的Bean
Spring Cloud Ribbon Client的懒加载
Spring Cloud Ribbon Client的配置加载,包含全局配置及Client配置
如何自定义Client配置、全局配置
解释官方文档中的一些注意事项

Spring Cloud Ribbon自动配置
当前版本中的Netflix所有自动配置都在spring-cloud-netflix-core-xxx.jar中,根据其META-INF/spring.factories中的配置得知,Spring Cloud Ribbon的自动配置类为 RibbonAutoConfiguration

RibbonAutoConfiguration
@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)
public class RibbonAutoConfiguration {

// 所有针对某个RibbonClient指定的配置
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();

// ribbon是否懒加载的配置文件
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;

// Spring会给每个RibbonClient创建独立的ApplicationContext上下文
// 并在其上下文中创建RibbonClient对应的Bean:如IClient、ILoadbalancer等
@Bean
public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
}

// Spring创建的带负载均衡功能的Client,会使用SpringClientFactory创建对应的Bean和配置
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
    return new RibbonLoadBalancerClient(springClientFactory());
}

// 到Spring environment中加载针对某个Client的Ribbon的核心接口实现类
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
    return new PropertiesFactory();
}

// 如果不是懒加载,启动时就使用RibbonApplicationContextInitializer加载并初始化客户端配置
@Bean
@ConditionalOnProperty(value = "ribbon.eager-load.enabled", matchIfMissing = false)
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
    return new RibbonApplicationContextInitializer(springClientFactory(),
            ribbonEagerLoadProperties.getClients());
}

......

}
上面RibbonAutoConfiguration创建的Bean主要分以下几类:

为Ribbon Client创建环境及获取配置
SpringClientFactory: 会给每个Ribbon Client创建一个独立的Spring应用上下文ApplicationContext,并在其中加载对应的配置及Ribbon核心接口的实现类
PropertiesFactory: 用于从Spring enviroment环境中获取针对某个Ribbon Client配置的核心接口实现类,并实例化
创建RibbonLoadBalancerClient,并将springClientFactory注入,方便从中获取对应的配置及实现类,RibbonLoadBalancerClient是Spring对LoadBalancerClient接口的实现类,其execute()方法提供客户端负载均衡能力
懒加载相关
RibbonEagerLoadProperties: 懒加载配置项Properties,可以指定是否懒加载,及哪些Client不懒加载
RibbonApplicationContextInitializer: 启动时就加载RibbonClient配置(非懒加载)的初始化器
可以看到默认启动流程中并没有加载RibbonClient的上下文和配置信息,而是在使用时才加载,即懒加载

Spring Cloud RibbonClient的懒加载
既然是在使用时才会加载,那么以Zuul网关为例,在其RibbonRoutingFilter中会创建RibbonCommand,其包含了Ribbon的负载均衡

//## RibbonRoutingFilter Zuul负责路由的Filter
public class RibbonRoutingFilter extends ZuulFilter {

@Override
public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    this.helper.addIgnoredHeaders();
    try {
        RibbonCommandContext commandContext = buildCommandContext(context);
        ClientHttpResponse response = forward(commandContext);
        setResponse(response);
        return response;
    }
    catch (ZuulException ex) {
        throw new ZuulRuntimeException(ex);
    }
    catch (Exception ex) {
        throw new ZuulRuntimeException(ex);
    }
}

protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
    Map<String, Object> info = this.helper.debug(context.getMethod(),
            context.getUri(), context.getHeaders(), context.getParams(),
            context.getRequestEntity());

    // 使用ribbonCommandFactory创建RibbonCommand
    RibbonCommand command = this.ribbonCommandFactory.create(context);
    try {
        ClientHttpResponse response = command.execute();
        this.helper.appendDebug(info, response.getStatusCode().value(),
                response.getHeaders());
        return response;
    }
    catch (HystrixRuntimeException ex) {
        return handleException(info, ex);
    }
}

}
在执行RibbonRoutingFilter#run()进行路由时会执行forward()方法,由于此处是在HystrixCommand内部执行Ribbon负载均衡调用,故使用ribbonCommandFactory创建RibbonCommand,Ribbon客户端的懒加载就在这个方法内,这里我们看HttpClientRibbonCommandFactory实现类

//## org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {

@Override
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
    ZuulFallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
    final String serviceId = context.getServiceId();
    // 通过SpringClientFactory获取IClient接口实例
    final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(
            serviceId, RibbonLoadBalancingHttpClient.class);
    client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));

    return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider,
            clientFactory.getClientConfig(serviceId));
}

}
创建RibbonLoadBalancingHttpClient的逻辑在 SpringClientFactory#getClient(serviceId, RibbonLoadBalancingHttpClient.class),如下:

SpringClientFactory#getInstance(name, clientClass)
NamedContextFactory#getInstance(name, type):
获取Client对应的ApplicationContext,如没有则调用createContext()创建,其中包含注册统一默认配置类RibbonClientConfiguration,或@RibbonClient、@RibbonClients(defaultConfiguration=xxx) 设置的配置类的逻辑
从ApplicationContext中根据类型获取实例,如没有使用反射创建,并通过IClientConfig配置
如上执行完毕RibbonClient就基本懒加载完成了,就可以到RibbonClient对应的ApplicationContext中继续获取其它核心接口的实现类了,这些实现类都是根据 默认/全局/Client自定义 配置创建的

//## org.springframework.cloud.netflix.ribbon.SpringClientFactory
public class SpringClientFactory extends NamedContextFactory {

static final String NAMESPACE = "ribbon";

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

/**
 * Get the rest client associated with the name.
 * @throws RuntimeException if any error occurs
 */
public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
    return getInstance(name, clientClass);
}

// name代表当前Ribbon客户端,type代表要获取的实例类型,如IClient、IRule
@Override
public <C> C getInstance(String name, Class<C> type) {
    // 先从父类NamedContextFactory中直接从客户端对应的ApplicationContext中获取实例
    // 如果没有就根据IClientConfig中的配置找到具体的实现类,并通过反射初始化后放到Client对应的ApplicationContext中
    C instance = super.getInstance(name, type);
    if (instance != null) {
        return instance;
    }
    IClientConfig config = getInstance(name, IClientConfig.class);
    return instantiateWithConfig(getContext(name), type, config);
}

// 使用IClientConfig实例化
static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
                                    Class<C> clazz, IClientConfig config) {
    C result = null;
    try {
        // 通过以IClientConfig为参数的构造创建clazz类实例
        Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
        result = constructor.newInstance(config);
    } catch (Throwable e) {
        // Ignored
    }
    
    // 如果没创建成功,使用无惨构造
    if (result == null) {
        result = BeanUtils.instantiate(clazz);
        
        // 调用初始化配置方法
        if (result instanceof IClientConfigAware) {
            ((IClientConfigAware) result).initWithNiwsConfig(config);
        }
        
        // 处理自动织入
        if (context != null) {
            context.getAutowireCapableBeanFactory().autowireBean(result);
        }
    }
    return result;
}

}

//## 父类 org.springframework.cloud.context.named.NamedContextFactory
public abstract class NamedContextFactory

    implements DisposableBean, ApplicationContextAware {
// 维护Ribbon客户端对应的ApplicationContext上下文
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

// 维护Ribbon客户端的@Configuration配置类
private Map<String, C> configurations = new ConcurrentHashMap<>();

private ApplicationContext parent;

private Class<?> defaultConfigType;  // 默认配置类为 RibbonClientConfiguration
private final String propertySourceName;  // 默认为 ribbon
private final String propertyName;  // 默认读取RibbonClient名的属性为ribbon.client.name

public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
        String propertyName) {
    this.defaultConfigType = defaultConfigType;
    this.propertySourceName = propertySourceName;
    this.propertyName = propertyName;
}

// 如果包含Client上下文直接返回
// 如果不包含,调用createContext(name),并放入contexts集合
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);
}

// 创建名为name的RibbonClient的ApplicationContext上下文
protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    
    // configurations集合中是否包含当前Client相关配置类,包含即注入到ApplicationContext
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
            context.register(configuration);
        }
    }
    
    //configurations集合中是否包含default.开头的通过@RibbonClients(defaultConfiguration=xxx)配置的默认配置类
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    
    // 注册PropertyPlaceholderAutoConfiguration、RibbonClientConfiguration
    context.register(PropertyPlaceholderAutoConfiguration.class,
            this.defaultConfigType);
    // 添加 ribbon.client.name=具体RibbonClient name的enviroment配置       
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
            this.propertySourceName,
            Collections.<String, Object> singletonMap(this.propertyName, name)));
    
    // 设置父ApplicationContext,这样可以使得当前创建的子ApplicationContext可以使用父上下文中的Bean
    if (this.parent != null) {
        // Uses Environment from parent as well as beans
        context.setParent(this.parent);
    }
    context.refresh();  //刷新Context
    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;
}   

}
上面比较重要的就是在创建每个RibbonClient的ApplicationContext的createContext(name)方法,其中包含了根据哪个@Configuration配置类创建Ribbon核心接口的实现类的逻辑,故需重点分析(Ribbon核心接口讲解 参考)

那么在createContext(name)方法创建当前Ribbon Client相关的上下文,并注入配置类时,除了默认配置类RibbonClientConfiguration是写死的,其它的配置类,如default全局配置类,针对某个Ribbon Client的配置类,又是怎么配置的呢?

Spring Cloud RibbonClient的配置加载,包含全局配置及Client配置
创建RibbonClient对应ApplicationContext,并注册所有可用的Configuration配置类
//## org.springframework.cloud.context.named.NamedContextFactory#createContext()
protected AnnotationConfigApplicationContext createContext(String name) {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

// 1、注册专门为RibbonClient指定的configuration配置类,@RibbonClient注解
if (this.configurations.containsKey(name)) {
    for (Class<?> configuration : this.configurations.get(name)
            .getConfiguration()) {
        context.register(configuration);
    }
}

// 2、将为所有RibbonClient的configuration配置类注册到ApplicationContext
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
    if (entry.getKey().startsWith("default.")) {
        for (Class<?> configuration : entry.getValue().getConfiguration()) {
            context.register(configuration);
        }
    }
}

// 3、注册defaultConfigType,即Spring的默认配置类 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);
}
context.refresh();  // 刷新上下文
return context;

}
根据如上逻辑可以看出会从3个地方将Ribbon相关的Configuration配置类注册到专门为其准备的ApplicationContext上下文,并根据配置类创建Ribbon核心接口的实现类,即达到配置RibbonClient的目的

从configurations这个Map中根据RibbonClient name获取专门为其指定的configuration配置类,并注册到其对应的ApplicationContext上下文
从configurations这个Map中找到 default. 开头 的配置类,即为所有RibbonClient的默认配置,并注册到其对应的ApplicationContext上下文
如果不是开发者单独指定的话,前两项都是没有数据的,还会注册Spring Cloud的默认配置类RibbonClientConfiguration
那么configurations这个Map里的配置类数据是从哪儿来的呢??下面逐步分析

//## RibbonAutoConfiguration
@Autowired(required = false)
private List configurations = new ArrayList<>();

@Bean
public SpringClientFactory springClientFactory() {

SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;

}
首先是在RibbonAutoConfiguration自动配置类创建SpringClientFactory是设置的,这个configurations集合是@Autowired的Spring容器内的RibbonClientSpecification集合,那么RibbonClientSpecification集合是何时被注册的??

//## org.springframework.cloud.netflix.ribbon.RibbonClientConfigurationRegistrar
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    // 1、@RibbonClients注解
    Map<String, Object> attrs = metadata.getAnnotationAttributes(
            RibbonClients.class.getName(), true);
    // 1.1 value是RibbonClient[],遍历针对具体的RibbonClient配置的configuration配置类,并注册
    if (attrs != null && attrs.containsKey("value")) {
        AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
        for (AnnotationAttributes client : clients) {
            registerClientConfiguration(registry, getClientName(client),
                    client.get("configuration"));
        }
    }
    // 1.2 找到@RibbonClients注解的defaultConfiguration,即默认配置
    //     注册成以default.Classname.RibbonClientSpecification为名的RibbonClientSpecification
    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"));
    }
    
    // 2、@RibbonClient注解
    // 注册某个具体Ribbon Client的configuration配置类
    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());
}

}
如上可知,configurations配置类集合是根据@RibbonClient 和 @RibbonClients 注解配置的,分别有 针对具体某个RibbonClient的配置 和 default默认配置

总结一下,Ribbon相关的@Configuration配置类是如何加载的

在创建完RibbonClient对应的AnnotationConfigApplicationContext后,先从根据@RibbonClient 和 @RibbonClients 注解加载的configurations集合中找当前RibbonClient name对应的配置类,如有,就注册到上下文
再从configurations集合中找根据@RibbonClients注解加载的 default.开头 的默认配置类,如有,就注册到上下文
最后注册Spring Cloud默认的 RibbonClientConfiguration

上面说是如何创建RibbonClient相关的ApplicationContext上下文及注册Ribbon Client相关的配置类的逻辑,在确定配置类后,其中会用到Ribbon的IClientConfig相关的客户端配置来加载Ribbon客户端相关的配置信息,如超时配置、具体创建哪个核心接口的实现类等,可以从Spring Cloud默认注册的 RibbonClientConfiguration来一探究竟

RibbonClientConfiguration配置加载及Ribbon核心接口实现类创建
//## org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {

@Value("${ribbon.client.name}")
private String name = "client";

// TODO: maybe re-instate autowired load balancers: identified by name they could be
// associated with ribbon clients

@Autowired
private PropertiesFactory propertiesFactory;

@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
    DefaultClientConfigImpl config = new DefaultClientConfigImpl();
    config.loadProperties(this.name);
    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;
}

上面只截取了一段代码,给出了Ribbon相关的 IClientConfig客户端配置 和 某一个核心接口IRule实现类 是如何加载配置并创建的

IClientConfig

IClientConfig就是Ribbon客户端配置的接口,可以看到先是创建了DefaultClientConfigImpl默认实现类,再config.loadProperties(this.name)加载当前Client相关的配置

//## com.netflix.client.config.DefaultClientConfigImpl#loadProperties()
/**

  • Load properties for a given client. It first loads the default values for all properties,
  • and any properties already defined with Archaius ConfigurationManager.
    */

@Override
public void loadProperties(String restClientName){

enableDynamicProperties = true;
setClientName(restClientName);

// 1、使用Netflix Archaius的ConfigurationManager从Spring env中加载“ribbon.配置项”这类默认配置
//   如没加载到有默认静态配置
loadDefaultValues();

// 2、使用Netflix Archaius的ConfigurationManager从Spring env中加载“client名.ribbon.配置项”这类针对某个Client的配置信息
Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
    String key = keys.next();
    String prop = key;
    try {
        if (prop.startsWith(getNameSpace())){
            prop = prop.substring(getNameSpace().length() + 1);
        }
        setPropertyInternal(prop, getStringValue(props, key));
    } catch (Exception ex) {
        throw new RuntimeException(String.format("Property %s is invalid", prop));
    }
}

}
根据如上注释,如果你没有在项目中指定ribbon相关配置,那么会使用DefaultClientConfigImpl中的默认静态配置,如果Spring enviroment中包含“ribbon.配置项”这类针对所有Client的配置会被加载进来,有“client名.ribbon.配置项”这类针对某个Client的配置信息也会被加载进来

静态配置如下:

RibbonClient核心接口实现类配置加载及创建

上面说完IClientCOnfig配置项是如何加载的,按道理说其中已经包含了当前RibbonClient使用哪个核心接口实现类的配置,但Spring Cloud在此处定义了自己的实现逻辑

@Autowired
private PropertiesFactory propertiesFactory;

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {

// 查看propertiesFactory是否有关于当前接口的配置,如有就使用,并创建实例返回
if (this.propertiesFactory.isSet(IRule.class, name)) {
    return this.propertiesFactory.get(IRule.class, config, name);
}

// spring cloud 默认配置
ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
rule.initWithNiwsConfig(config);
return rule;

}
下面看看PropertiesFactory的逻辑

public class PropertiesFactory {

@Autowired
private Environment environment;

private Map<Class, String> classToProperty = new HashMap<>();

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

// 查看当前clazz是否在classToProperty管理的几个核心接口之一
// 如是,查看Spring environment中是否能找到 “clientName.ribbon.核心接口配置项”的配置信息
public boolean isSet(Class clazz, String name) {
    return StringUtils.hasText(getClassName(clazz, name));
}

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

// 也是先调用getClassName()获取Spring enviroment中配置的核心接口实现类名
// 再使用IClientConfig配置信息创建其实例
@SuppressWarnings("unchecked")
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;
}

}
故以上面创建IRule接口实现类的逻辑

先通过propertiesFactory查看Spring enviroment中是否配置了针对当前Ribbon Client的IRule核心接口实现类的配置信息,如有,就创建其实例返回(相关配置格式: clientName.ribbon.NFLoadBalancerRuleClassName=具体IRule实现类)
如没有,那么没有直接使用Netflix在其DefaultClientConfigImpl中的静态配置,而是使用Spring Cloud自定义的默认实现类,拿IRule规则接口来说是ZoneAvoidanceRule
总结:

首先会创建RibbonClient的ApplicationContext上下文,并确定使用哪个Configuration配置类

1、@RibbonClients注册的全局默认配置类

2、@RibbonClient注册的某个Client配置类

3、Spring Cloud 默认的RibbonClientConfiguration配置类

确定配置类后就是加载Client相关的IClientConfig配置信息,并创建核心接口实现类

如果没有自定义全局/客户端配置类,那么就是使用RibbonClientConfiguration,而其规则是

对于超时等配置(除核心接口实现类以外):使用Netflix的配置逻辑,通过 ribbon.xxx 作为默认配置,以 clientName.ribbon.xxx 作为客户端定制配置

对于核心接口实现类配置:客户端定制配置仍然使用 clientName.ribbon.xxx,但默认配置是Spring Cloud在RibbonClientConfiguration方法中写死的默认实现类

已经知道大概的逻辑了,下面就看看具体如何自定义Client配置、全局配置

如何自定义RibbonClient配置、全局配置
这部分在Spring Cloud官方reference中有说明 16.2 Customizing the Ribbon Client

大致意思如下:

一部分配置(非核心接口实现类的配置)可以使用Netflix原生API提供的方式,即使用如 .ribbon.* 的方式配置,具体有哪些配置项,可以参考 com.netflix.client.config.CommonClientConfigKey

如果想比较全面的控制RibbonClient并添加一些额外配置,可以使用 @RibbonClient 或 @RibbonClients 注解,并配置一个配置类,如上的 FooConfiguration

@RibbonClient(name = "foo", configuration = FooConfiguration.class) 是针对名为 foo 的RibbonClient的配置类,也可以使用@RibbonClients({@RibbonClient数组}) 的形式给某几个RibbonClient设置配置类

@RibbonClients( defaultConfiguration = { xxx.class } ) 是针对所有RIbbonClient的默认配置

官方文档说 FooConfiguration配置类 必须是@Configuration的,这样就必须注意,SpringBoot主启动类不能扫描到FooConfiguration,否则针对某个RibbonClient的配置就会变成全局的,原因是在创建每个RibbonClient时会为其创建ApplicationContext上下文,其parent就是主启动类创建的ApplicationContext,子ApplicationContext中可以使用父ApplicationContext中的Bean,且创建Bean时都使用了@ConditionalOnMissingBean,所以FooConfiguration如果被主启动类的上下文加载,且创建了比如IRule的实现类,在某个RIbbonClient创建其子ApplicationContext并@Bean想创建其自定义IRule实现类时,会发现parent ApplicationContext已经存在,就不会创建了,配置就失效了

但在我的实验中,即使FooConfiguration不加@Configuration注解也可以加载为RibbonClient的配置,且由于没有@Configuration了,也不会被主启动类扫描到

所以主要分成2种配置:

(1)超时时间等静态配置,使用 ribbon. 配置所有Client,使用 .ribbon. 配置某个Client

(2)使用哪种核心接口实现类配置,使用@RibbonClients注解做默认配置,使用@RibbonClient做针对Client的配置(注意@Configuration不要被SpringBoot主启动类扫描到的问题)

原文链接:https://www.cnblogs.com/trust-freedom/p/11216280.html
作者:Trust_FreeDom - 博客园
博客主页:http://www.cnblogs.com/trust-freedom/
欢迎转载,但请保留作者和本文链接,谢谢!

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
13天前
|
负载均衡 监控 网络协议
SpringCloud之Ribbon使用
通过以上步骤,就可以在Spring Cloud项目中有效地使用Ribbon来实现服务调用的负载均衡,提高系统的可靠性和性能。在实际应用中,根据具体的业务场景和需求选择合适的负载均衡策略,并进行相应的配置和优化,以确保系统的稳定运行。
40 15
|
13天前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
35 5
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
1月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
37 0
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
3月前
|
负载均衡 算法 Java
SpringCloud之Ribbon使用
通过 Ribbon,可以非常便捷的在微服务架构中实现请求负载均衡,提升系统的高可用性和伸缩性。在实际使用中,需要根据实际场景选择合适的负载均衡策略,并对其进行适当配置,以达到更佳的负载均衡效果。
64 13
|
2月前
|
负载均衡 Java 开发者
Ribbon框架实现客户端负载均衡的方法与技巧
Ribbon框架为微服务架构中的客户端负载均衡提供了强大的支持。通过简单的配置和集成,开发者可以轻松地在应用中实现服务的发现、选择和负载均衡。适当地使用Ribbon,配合其他Spring Cloud组件,可以有效提升微服务架构的可用性和性能。
34 0
|
3月前
|
Java 微服务 Spring
Spring Cloud全解析:配置中心之解决configserver单点问题
但是如果该configserver挂掉了,那就无法获取最新的配置了,微服务就出现了configserver的单点问题,那么如何避免configserver单点呢?
|
3月前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
3月前
|
Java 数据库连接 Nacos
SpringCloud微服务配置管理、配置热更新
SpringCloud微服务配置管理、配置热更新
110 0
下一篇
无影云桌面