Ribbon自定义配置--RibbonClientSpecification

目录

NamedContextFactory

SpringClientFactory

RibbonClientConfiguration

PropertiesFactory

RibbonClientSpecification

RibbonClientConfigurationRegistrar


在微服务系统中调用微服务A和微服务B,因为业务、逻辑、性能等各种不同,我们不能对两个微服务系统使用同一套Ribbon参数配置,Spring Cloud Ribbon也支持我们针对不同服务进行定制化配置,例如超时时间、负载均衡策略等等。配置分为三种,默认/全局/各Client自定义配置,本次从源码角度来分析Ribbon三种配置。

入口自然是spring.factories中的自动配置类,只列举出了涉及到的bean  org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
		name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
		AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
		ServerIntrospectorProperties.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
		havingValue = "true", matchIfMissing = true)
public class RibbonAutoConfiguration {

	@Autowired(required = false)
	private List<RibbonClientSpecification> configurations = new ArrayList<>();

	@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
	@ConditionalOnMissingBean
	public PropertiesFactory propertiesFactory() {
		return new PropertiesFactory();
	}

}

上边代码中涉及到的Bean有四个

RibbonClientSpecification  针对某个RibbonClient自定义的配置类,name为微服务名,configuration是配置类Class,怎么来的先忽略

SpringClientFactory   继承自NamedContextFactory,RibbonClient的配置工厂类,Spring为每个RibbonClient都会创建独立的子容器,在子容器中创建Ribbon所需要的Bean(IRule、ILoadBalancer等)

RibbonLoadBalancerClient   调用SpringClientFactory获取ILoadBalancer进行负载均衡选择,随后发送请求

PropertiesFactory  Spring提供的针对某个RibbonClient的自定义实现类,也是定制化配置的一种方式

NamedContextFactory

这个类允许创建一系列的子容器,在子容器中创建不同的bean,简单说就是每个name下有独立的容器,有默认的配置类,也有所有name公用的全局配置类,也可以为每个name自定义配置类(@Configuration),每个子容器中加载 默认、全局、以及属于自己的配置类中的bean定义

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {

    // 当前NamedContextFactory名
	private final String propertySourceName;

	private final String propertyName;

    // key是name,ribbon中就是服务名,value是子容器
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    // key是name,ribbon中就是服务名,value是该服务自定义的配置类
	private Map<String, C> configurations = new ConcurrentHashMap<>();

    // 父容器,就是当前应用的spring容器
	private ApplicationContext parent;

    // 默认的配置类
	private Class<?> defaultConfigType;

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType;// 默认配置
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

    // 每个子容器的自定义配置
	public interface Specification {

		String getName();

		Class<?>[] getConfiguration();

	}
}

 获取Bean的方法如下,从map中根据name获取子容器,没有则创建,再从容器中getBean

	public <T> T getInstance(String name, Class<T> type) {
        // 根据name先获取容器
		AnnotationConfigApplicationContext context = getContext(name);
		try {
			return context.getBean(type);
		}
		catch (NoSuchBeanDefinitionException e) {
			// ignore
		}
		return null;
	}

    // 不存在则创建
	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);
	}


	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 如果有为每个name自定义的配置类。将其注册到容器
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}

        // 如果有全局的配置类,以default开头的name
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}

        // 再注册默认的配置类
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));

        // 关联父子容器,进行refresh
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

SpringClientFactory

继承自NamedContextFactory,RibbonClientConfiguration是所有子容器也就是所有RibbonClient的默认配置类,RibbonClientSpecification则就是RibbonClient的自定义配置封装类

RibbonClientConfiguration

所有RibbonClient的默认配置类,其中有两种配置方式

一种普通参数就是配置文件ribbon.connectTimeout=1000

第二种核心组件配置,先使用PropertiesFactory(RibbonAutoConfiguration中的)获取,未获取到则给出默认实现

@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
		RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
    // ribbon的普通参数,超时时间
	public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
	public static final int DEFAULT_READ_TIMEOUT = 1000;

	@RibbonClientName
	private String name = "client";

	@Autowired
	private PropertiesFactory propertiesFactory;

	@Autowired
	private Environment environment;

	@Bean
	@ConditionalOnMissingBean
	public IClientConfig ribbonClientConfig() {
		DefaultClientConfigImpl config = new DefaultClientConfigImpl();

		config.loadProperties(this.name);

        // 超时时间通过application.properties中的ribbon.connectTimeout来设置
		config.set(CommonClientConfigKey.ConnectTimeout, getProperty(
				CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT));

		config.set(CommonClientConfigKey.ReadTimeout,
				getProperty(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT));

		return config;
	}

	private Integer getProperty(IClientConfigKey<Integer> connectTimeout,
			int defaultConnectTimeout) {
		return environment.getProperty("ribbon." + connectTimeout, Integer.class,
				defaultConnectTimeout);
	}


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

PropertiesFactory

定义了五种核心组件的自定义参数,如想自定义某个client的负载均衡规则,则如下设置, testclient是服务名

testclient.ribbon.NFLoadBalanceRuleClassName=com.netflix.loadbalancer.RandomRule

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

	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 + "." + "ribbon"+ "." + classNameProperty);
			return className;
		}
		return null;
	}
}

RibbonClientSpecification

RibbonClient的自定义配置

 在@RibbonClients和@RibbonClient中被引入

RibbonClientConfigurationRegistrar

 将@RibbonClients和@RibbonClient中配置的服务名和@Configuration类封装为RibbonClientSpecification,注册到bean容器中

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(RibbonClients.class.getName(), true);

        // @RibbonClients注解中配置的多个@RibbonClient注解
		if (attrs != null && attrs.containsKey("value")) {
			AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
			for (AnnotationAttributes client : clients) {
				registerClientConfiguration(registry, getClientName(client),
						client.get("configuration"));
			}
		}
        // @RibbonClients注解中如果有这个属性,表示这个配置类是全局的配置,所有RibbonClient公用的配置,name以default开头
		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"));
		}

        // 注册单个@RibbonClient的配置类
		Map<String, Object> client = metadata
				.getAnnotationAttributes(RibbonClient.class.getName(), true);
		String name = getClientName(client);
		if (name != null) {
			registerClientConfiguration(registry, name, client.get("configuration"));
		}
	}

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

所以自定义Ribbon配置还可以如下操作

定义一个配置类,内部自定义组件,例如负载均衡策略

 配置注解@RibbonClient,name是微服务名,就完成了自定义ribbon配置

但是要注意的是一点,自定义的配置类不能被启动类@ComponentScan扫描到,一旦扫描到,ribbon的默认配置中又都是@ConditionOnMissingBean,就变成了全局配置了。

还有官网说自定义配置类必须注解Configuration,但是我测试不注解也能生效

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于Ribbon自定义负载均衡,你可以通过以下步骤进行操作: 1. 添加Ribbon依赖:在你的项目中添加Ribbon的依赖,例如使用Maven管理项目依赖的话,在`pom.xml`文件中添加如下依赖: ```xml <dependencies> ... <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> ... </dependencies> ``` 2. 创建一个自定义的负载均衡器:可以实现`IRule`接口来定制自己的负载均衡策略。例如,你可以创建一个名为`CustomRule`的类来实现自己的负载均衡算法,继承自`AbstractLoadBalancerRule`。 ```java import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; public class CustomRule extends AbstractLoadBalancerRule { @Override public Server choose(Object key) { // 自定义负载均衡算法逻辑 // 返回一个Server对象作为选择结果 // 可以根据自己的需求进行算法实现 ... } } ``` 3. 配置自定义负载均衡器:在应用的配置文件中,指定使用自定义的负载均衡器。例如,在`application.properties`或`application.yml`文件中添加如下配置: ``` spring: cloud: loadbalancer: ribbon: rule: com.example.CustomRule ``` 4. 启用Ribbon负载均衡:确保在应用的启动类上添加`@EnableDiscoveryClient`注解,以启用Ribbon负载均衡功能。 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` 通过以上步骤,你就可以自定义Ribbon的负载均衡策略了。注意,这里只是简单的介绍了一种实现方式,你可以根据自己的需求进行更详细的定制和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值