当前位置: 代码迷 >> 综合 >> Spring Cloud Ribbon 客户端负载均衡~ ILoadBalancer 初始化
  详细解决方案

Spring Cloud Ribbon 客户端负载均衡~ ILoadBalancer 初始化

热度:97   发布时间:2023-12-08 17:14:04.0

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.classSpringClientFactory初始化时指定
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中注入的IPingServerList对象由EurekaRibbonClientConfiguration中生成。

分析完ILoadBalancer初始化流程后,我们可知

  1. 我们可以通过@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 {
          }
    
  2. 配置优先级
    针对单个服务的配置优先于针对全部服务的配置,针对全部的配置优先于系统默认配置(RibbonClientConfiguration)
  3. 自定义配置类路径
    Spring Cloud提供的配置类是注册在服务对应的上下文中,其父容器为应用上下文。Spring Boot默认扫描主程序所在包及其下面的所有子包里面的组件,若自定义配置类在Spring Boot默认扫描路径下,则该配置类将注册到应用上下文(即父容器)中为所用服务共享。官方建议将自定义配置放在应用程序扫描路径之外。 在这里插入图片描述
  4. 使用属性配置

<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
  1. Spring Cloud中ILoadBalancer的初始化发生在第一次请求该服务时,可以通过更改属性使特定服务对应的ILoadBalancer在启动时初始化
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=hello-service

参考资料 官方文档

  相关解决方案