掘金 后端 ( ) • 2024-04-26 10:35

一、自定义Rule

1.1 需求

在gataway服务自定义负载均衡规则,使同一个用户的请求打在同一个服务器

1.2 错误示例

pom依赖

	<!-- springboot web -->
	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- ribbon -->
	<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        <version>2.1.2.RELEASE</version>
    </dependency>

LoadBanancerConfig.java

@Configuration
public class LoadBanancerConfig {

    //自定义负载均衡
    @Bean
    public IRule ribbonRule() {
        return new UserLoadBalancerRule();
    }

    //自定义过滤器(可选)
    @Bean
    public UserLoadBalancerClientFilter userLoadBalanceClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
        return new UserLoadBalancerClientFilter(client, properties);
    }

}

UserLoadBalancerClientFilter.java

public class UserLoadBalancerClientFilter extends LoadBalancerClientFilter {

    public UserLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        super(loadBalancer, properties);
    }

    @Override
    protected ServiceInstance choose(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        Long userId = UserInfo.getCurrUserId(request);
        if(userId == null){
            return super.choose(exchange);
        }

        if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
            RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
            String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
            //这里使用userId做为选择服务实例的key
            return client.choose(serviceId, userId);
        }

        return super.choose(exchange);
    }
}

UserLoadBalancerRule.java

public class UserLoadBalancerRule extends AbstractLoadBalancerRule {
    private static Logger log = LoggerFactory.getLogger(UserLoadBalancerRule.class);

    /**
     * 自定义选择策略
     *
     * @param key userId
     * @return
     */
    public Server choose(Object key) {
        if (key == null || "default".equals(key)) {
            return this.choose(this.getLoadBalancer(), null);
        } else {
            return this.choose(this.getLoadBalancer(), Long.valueOf(String.valueOf(key)));
        }
    }

    /**
     * 自定义选择策略
     *
     * @param lb
     * @param userId
     * @return
     */
    public Server choose(ILoadBalancer lb, Long userId) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while (server == null) {
                if (Thread.interrupted()) {
                    return null;
                }

                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }
                //自定义部分代码开始
                int index = 0;

                //获取userId
                log.info("userId:{}", userId);
                if (userId == null) {
                    //无法获取userId,随机选择
                    index = chooseRandomInt(serverCount);
                } else {
                    //根据userId选择实例
                    index = (int) (userId % serverCount);
                }

                server = upList.get(index);
                log.info("server:{}", server.getId() + " " + server.getMetaInfo().getAppName());
                //自定义部分代码结束

                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }
    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
    
}

测试

@RestController
@RequestMapping("testController")
public class TestController {

    @Autowired
    private RestTemplate restTemplate;
    
    //RestTemplate开启负载均衡
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @GetMapping("test")
    public String test(){
        String b = restTemplate.getForObject("http://SM-SHUMIWEB//xxx", String.class);
        String c = restTemplate.getForObject("http://SM-FILE//xxx", String.class);
        return b + ";" + c;
    }

}

1.3 出现问题

gataway服务调用服务b再调用服务c,再调用服务b报错404

org.springframework.web.client.HttpClientErrorException: 404 null

1.4 正确示例

只需更改IRule注册到容器中的方式即可

启动类 GatewayServerApplication.java添加注解@RibbonClients

@RibbonClients(defaultConfiguration = LoadBanancerConfig.class)
@SpringBootApplication
public class GatewayServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServerApplication.class, args);
    }
}

★1.5 问题分析

1.在自定义的IRule中,使用的ILoadBalancer对象是IRule对象中保存的

public Server choose(Object key) {
        if (key == null || "default".equals(key)) {
            return this.choose(this.getLoadBalancer(), null);
        } else {
            return this.choose(this.getLoadBalancer(), Long.valueOf(String.valueOf(key)));
        }
    }

2.在RibbonClientConfiguration类中进行各个bean的初始化,由于@ConditionalOnMissingBean注解的作用,只有在容器中没有IRule对象时才在这里进行初始化,

但由于我们在LoadBanancerConfig上加了@Configuration注解,那么在项目启动时,IRule对象就注册到了Spring容器中,那么之后使用的IRule对象都是同一个对象,即在Spring容器中注册的自定义IRule bean

@Bean
    public IRule ribbonRule() {
        return new ResourceIpLoadBalancerRule();
    }

3.SpringClientFactory默认情况下,每个eureka的serviceId(服务),都会分配自己独立的Spring的上下文,即ApplicationContext,这个上下文中包含了必要的一些bean,比如: ILoadBalancer ServerListFilter 等。

即每个服务都会进入该方法进行初始化。

	@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);
		}
        //进入到BaseLoadBalancer中实例化对象
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

4.在BaseLoadBalancerinitWithConfig方法实例化ILoadBalancer对象,并且会对rule对象赋值

void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
        this.config = clientConfig;
        String clientName = clientConfig.getClientName();
        this.name = clientName;
        int pingIntervalTime = Integer.parseInt(""
                + clientConfig.getProperty(
                        CommonClientConfigKey.NFLoadBalancerPingInterval,
                        Integer.parseInt("30")));
        int maxTotalPingTime = Integer.parseInt(""
                + clientConfig.getProperty(
                        CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
                        Integer.parseInt("2")));

        setPingInterval(pingIntervalTime);
        setMaxTotalPingTime(maxTotalPingTime);

       	//在这个方法中对rule对象赋值
        setRule(rule);
        setPing(ping);

        setLoadBalancerStats(stats);
        rule.setLoadBalancer(this);
        if (ping instanceof AbstractLoadBalancerPing) {
            ((AbstractLoadBalancerPing) ping).setLoadBalancer(this);
        }
        logger.info("Client: {} instantiated a LoadBalancer: {}", name, this);
        boolean enablePrimeConnections = clientConfig.get(
                CommonClientConfigKey.EnablePrimeConnections, DefaultClientConfigImpl.DEFAULT_ENABLE_PRIME_CONNECTIONS);

        if (enablePrimeConnections) {
            this.setEnablePrimingConnections(true);
            PrimeConnections primeConnections = new PrimeConnections(
                    this.getName(), clientConfig);
            this.setPrimeConnections(primeConnections);
        }
        init();
    }

IRule对象中保存了一份ILoadBalancer对象,rule.setLoadBalancer(this)中的this为当前正在实例化的lb

public void setRule(IRule rule) {
        if (rule != null) {
            this.rule = rule;
        } else {
            /* default rule */
            this.rule = new RoundRobinRule();
        }
        if (this.rule.getLoadBalancer() != this) {
            //给rule中的lb赋值
            this.rule.setLoadBalancer(this);
        }
    }

5.综上,访问b服务后再访问c服务,在实例化c服务的ILoadBalancer对象时,重新对IRule对象赋了值,这个值即为c服务的lb,

同时rule对象是Spring实例化的,有且仅有一份,第二次访问c服务就把rule中的lb对象覆盖了,

而在我们自定义的IRule中选择服务时,使用的lb是rule中保存的值,所以再进行访问b服务时,lb还是c服务的,进行choose服务时就找不到正确的service实例。

★1.6 正确示例分析

从获取ILoadBalancer实例开始分析

1.RibbonLoadBalancerClient中,通过SpringClientFactory工厂获取实例

	private SpringClientFactory clientFactory;		

	protected ILoadBalancer getLoadBalancer(String serviceId) {
        //获取SpringClientFactory获取ILoadBalancer实例
		return this.clientFactory.getLoadBalancer(serviceId);
	}

2.SpringClientFactory

2.1 调用方法getLoadBalancer

	public ILoadBalancer getLoadBalancer(String name) {
		return getInstance(name, ILoadBalancer.class);
		

2.2 调用方法getInstance,该方法是NamedContextFactory类中getInstance(String name, Class<T> type)的重载方法。参数name是serviceId(服务),type是要获取对象的Class

	@Override
	public <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);

3.SpringClientFactory父类NamedContextFactory

3.1 调用方法getInstance(String name, Class<T> type)

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

3.2 调用方法protected AnnotationConfigApplicationContext getContext(String name)

	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
                    //第一次初始化ILoadBalancer,创建该服务的instance
					this.contexts.put(name, createContext(name));
				}
			}
		}
        //如果SpringClientFactory工厂中存该服务的instance,直接获取
		return this.contexts.get(name);
	}

3.3 调用方法protected AnnotationConfigApplicationContext createContext(String name)

	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //通过命名找到对应的configuration,完成configuration包含的bean的定义
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
        
        //key为"default."开头的configuration,所有的子context公用,完成default.配置下的configuration包含的bean的定义。
        //!!!完成自定义配置类LoadBanancerConfig的定义!!!
		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)));
		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));
        
        //!!!完成bean的实例化!!!
		context.refresh();
        
		return context;
	}

自定义的IRule的Bean在context.refresh()中完成创建,每个服务都有自己独立的上下文,并且在自己的上下文中都各自创建了IRule的Bean。

1714036563761.png

4.总结

使用@RibbonClients(defaultConfiguration = LoadBanancerConfig.class)引入自定义的配置类,不同的服务在创建自己的context上下文时,在各自的context中创建了IRule的Bean。

而不通过@RibbonClients注解,只通过@Configuration引入自定义的的配置类,只会在项目启动时,在Spring容器中创建IRule的Bean,而这个Bean被不同服务的context共用。

二、源码解读

2.1 源码入口

通过springboot的spi启动配置类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

2.2 主要配置类

2.2.1 RibbonAutoConfiguration

org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration在项目启动时加载

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
//加载完这个类之后加载类LoadBalancerAutoConfiguration
@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);
	}

    //spring创建loadbalancer和clientConfig的工厂
	@Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}
	
    //创建ribbion负载均衡选择的客户端
	@Bean
	@ConditionalOnMissingBean(LoadBalancerClient.class)
	public LoadBalancerClient loadBalancerClient() {
		return new RibbonLoadBalancerClient(springClientFactory());
	}

    //创建RibbionloadBalanced重试工厂
	@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();
	}

    //饥饿加载初始化
    //ribbion默认是在调用服务的时候初始化,此配置可以设置服务在启动时初始化
	@Bean
	@ConditionalOnProperty("ribbon.eager-load.enabled")
	public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
		return new RibbonApplicationContextInitializer(springClientFactory(),
				ribbonEagerLoadProperties.getClients());
	}

	@Configuration
	@ConditionalOnClass(HttpRequest.class)
	@ConditionalOnRibbonRestClient
	protected static class RibbonClientHttpRequestFactoryConfiguration {

		@Autowired
		private SpringClientFactory springClientFactory;

		@Bean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
			return restTemplate -> restTemplate
					.setRequestFactory(ribbonClientHttpRequestFactory);
		}

		@Bean
		public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
			return new RibbonClientHttpRequestFactory(this.springClientFactory);
		}

	}

	// TODO: support for autoconfiguring restemplate to use apache http client or okhttp

	@Target({ ElementType.TYPE, ElementType.METHOD })
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	@Conditional(OnRibbonRestClientCondition.class)
	@interface ConditionalOnRibbonRestClient {

	}

	private static class OnRibbonRestClientCondition extends AnyNestedCondition {

		OnRibbonRestClientCondition() {
			super(ConfigurationPhase.REGISTER_BEAN);
		}

		@Deprecated // remove in Edgware"
		@ConditionalOnProperty("ribbon.http.client.enabled")
		static class ZuulProperty {

		}

		@ConditionalOnProperty("ribbon.restclient.enabled")
		static class RibbonProperty {

		}

	}

	/**
	 * {@link AllNestedConditions} that checks that either multiple classes are present.
	 */
	static class RibbonClassesConditions extends AllNestedConditions {

		RibbonClassesConditions() {
			super(ConfigurationPhase.PARSE_CONFIGURATION);
		}

		@ConditionalOnClass(IClient.class)
		static class IClientPresent {

		}

		@ConditionalOnClass(RestTemplate.class)
		static class RestTemplatePresent {

		}

		@ConditionalOnClass(AsyncRestTemplate.class)
		static class AsyncRestTemplatePresent {

		}

		@ConditionalOnClass(Ribbon.class)
		static class RibbonPresent {

		}

	}

}

2.2.2 LoadBalancerAutoConfiguration

org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration

在RibbonAutoConfiguration之后加载,设置ClientHttpRequestInterceptor拦截器到restTemplate

@Configuration
@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();

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

		@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			//创建拦截器
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				//设置拦截器
				restTemplate.setInterceptors(list);
			};
		}

	}

2.3 初始化相关

2.3.1 PropertiesFactory

org.springframework.cloud.netflix.ribbon.PropertiesFactory

属性工厂,ILoadBalancer、IRule等设置了映射字符串,这些类的对象通过get()方法创建,通过环境变量中配置serviceName.ribbon.NFLoadBalancerClassName可以指定调用服务的配置。

get()在RibbonClientConfiguration通过@Bean注解创建bean对象时被调用。

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

    //调用SpringClientFactory根据className创建对象
	@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) SpringClientFactory.instantiateWithConfig(toInstantiate,config);
			}
			catch (ClassNotFoundException e) {
				throw new IllegalArgumentException("Unknown class to load " + className
						+ " for class " + clazz + " named " + name);
			}
		}
		return null;
	}

}

2.3.2 NamedContextFactory

org.springframework.cloud.context.named.NamedContextFactory

此类是springCloud提供的一个工厂类,通常在spring项目中,只有一个springApplicationContext,但是在微服务体系中,一个服务中会调用多个服务,每个被调用服务都有自己的springApplicationContext,它们的父context为当前服务的context。

获取bean不再是context.getBean(classType),而是context.getInstanse(serviceName,classType)。

ribbion配置类加载SpringClientFactory时会调用构造方法,config为RibbonClientConfiguration类。

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

	private final String propertySourceName;

	private final String propertyName;

	//被调用服务的spring上下文
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

	private Map<String, C> configurations = new ConcurrentHashMap<>();

	private ApplicationContext parent;

	private Class<?> defaultConfigType;

	//构造器,子类SpringClientFactory创建的时候调用,defaultConfigType为RibbonClientConfiguration
	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;//通过ApplicationContextAware得到父context并设置
	}
	
	public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {//设置被调用服务的configuration
			this.configurations.put(client.getName(), client);
		}
	}

	@Override
	public void destroy() {//遍历销毁
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			context.close();
		}
		this.contexts.clear();
	}

	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();
		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);
				}
			}
		}
		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);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();//刷新context,初始化bean
		return context;
	}
	//从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;
	}

2.3.3 SpringClientFactory

org.springframework.cloud.netflix.ribbon.SpringClientFactory

此类是基于NamedContextFactory封装的工厂类,用来获取ILoadBalancer和IClientConfig

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
	//固定值ribbon命名空间
	static final String NAMESPACE = "ribbon";

	public SpringClientFactory() {
		//调用父类NamedContextFactory构造器设置属性
		//设置配置类为RibbonClientConfiguration
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}
    
	/**
	 * 获取rest客户端,restTemplate拦截器设置请求时创建,用来远程调用服务接口
	 */
	public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
		return getInstance(name, clientClass);
	}

	/**
	 * 通过服务名获取负载均衡器实例,通过这个按策略选择服务调用,restTemplate拦截器设置请求时创建
	 */
	public ILoadBalancer getLoadBalancer(String name) {
		return getInstance(name, ILoadBalancer.class);
	}

	/**
	 * 获取rest客户端配置,restTemplate拦截器设置请求时创建,用来远程调用服务接口
	 */
	public IClientConfig getClientConfig(String name) {
		return getInstance(name, IClientConfig.class);
	}

	static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
		return instantiateWithConfig(null, clazz, config);
	}

	/**
	 * 通过服务名和类型获取实例,每个服务都有自己的applicationContext
	 */
	@Override
	public <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);
	}

	/**
	 * 通过服务名获取自己的applicationContext
	 */
	@Override
	protected AnnotationConfigApplicationContext getContext(String name) {
		return super.getContext(name);
	}
}

2.3.4 RibbonClientConfiguration

org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration

每个被调用服务加载配置刷新上下文的时候调用,创建ribbion客户端的每个bean,会先通过PropertiesFactory找环境变量中的配置创建对应类型的组件bean,没有则调用无参构造器创建。

@Configuration
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
		RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {

	/**
	 * Ribbon client default connect timeout.
	 */
	public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

	/**
	 * Ribbon client default read timeout.
	 */
	public static final int DEFAULT_READ_TIMEOUT = 1000;

	/**
	 * Ribbon client default Gzip Payload flag.
	 */
	public static final boolean DEFAULT_GZIP_PAYLOAD = true;

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

2.3.5 LoadBalancerInterceptor

org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor

http拦截器,restTemplate请求的时候会执行ClientHttpRequestInterceptor的intercept方法,上述配置类会构造此拦截器并设置到restTemplate中。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;

	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
			LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();//从request中解析到uri
		String serviceName = originalUri.getHost();//从uri中获取服务名
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName,//执行过滤器
				this.requestFactory.createRequest(request, body, execution)//创建请求);
	}

}

2.3.6 初始化过程总结

1.通过spi加载RibbonAutoConfiguration,创建bean对象

SpringClientFactory(获取ribbion的loadBalancer和ClientConfig的工厂)

RibbonLoadBalancerClient(负载均衡客户端,继承LoadBalancerClient,主要有选择服务choose(serviceId)和执行请求execute(serviceId, LoadBalancerRequest request)方法)

PropertiesFactory(属性工厂,可以通过环境变量配置创建各被调用服务的组件)

2.加载LoadBalancerAutoConfiguration,创建LoadBalancerInterceptor拦截器,将上述配置的loadBalancerClient设置到里面,再将拦截器设置到restTemplate里面

3.ribbion时会从SpringClientFactory中获取需要的bean,当不存在时,首次加载时会创建被调用服务的子applicationContext,然后加载RibbonClientConfiguration时创建里面的各组件bean

2.4 调用流程

1.调用restTemplate.getForObject(),进入restTemplate的doExecute方法,执行ClientHttpRequestInterceptor的intercept方法,进入LoadBalancerInterceptor的intercept方法。

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

2.this.requestFactory.createRequest(request, body, execution)方法创建一个LoadBalancerRequest后续调用。

public LoadBalancerRequest<ClientHttpResponse> createRequest(
			final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) {
		return instance -> {
			HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
					this.loadBalancer);
			if (this.transformers != null) {
				for (LoadBalancerRequestTransformer transformer : this.transformers) {
					serviceRequest = transformer.transformRequest(serviceRequest,
							instance);
				}
			}
			return execution.execute(serviceRequest, body);
		};
	}

3.进入到RibbonLoadBalancerClient中的execute方法

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
    	//获取负载均衡器
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    	//选择服务
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
		//执行request请求返回结果
		return execute(serviceId, ribbonServer, request);
	}

若不存在对应的ILoadBalancer实例,则进入到RibbonClientConfiguration中初始化。

初始化顺序为:

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

	//创建IRule实例
	@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 ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
		return new PollingServerListUpdater(config);
	}

	//创建ILoadBalancer实例
	@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
	public RetryHandler retryHandler(IClientConfig config) {
		return new DefaultLoadBalancerRetryHandler(config);
	}

	@Bean
	@ConditionalOnMissingBean
	public RibbonLoadBalancerContext ribbonLoadBalancerContext(
        ILoadBalancer loadBalancer,IClientConfig config, RetryHandler retryHandler) {
		return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
	}

以上bean的创建都被注解@ConditionalOnMissingBean修饰,所以第二次及之后访问就不再需要创建。

4.执行execute方法,request.apply(serviceInstance)最终会调到第2步的execution.execute(serviceRequest, body)方法,返回最终结果

public <T> T execute(String serviceId, ServiceInstance serviceInstance,
			LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
		if (serviceInstance instanceof RibbonServer) {
			server = ((RibbonServer) serviceInstance).getServer();
		}
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}

		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			return returnVal;
		}
		// catch IOException and rethrow so RestTemplate behaves correctly
		catch (IOException ex) {
			statsRecorder.recordStats(ex);
			throw ex;
		}
		catch (Exception ex) {
			statsRecorder.recordStats(ex);
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}


public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
    private LoadBalancerClient loadBalancer;

    public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        return (ListenableFuture)this.loadBalancer.execute(serviceName, new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
        	//上面apply接口的实现,会执行上述第2步最后的执行
            public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance) throws Exception {
                HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
                return execution.executeAsync(serviceRequest, body);
            }
        });
    }
}

2.5 负载均衡器LoadBalance

1699430615688.png

顶层接口ILoadBalancer提供了添加、选择、获取、标记下线服务的方法

1699430648865.png

通过RibbonClientConfiguration可知默认会使用ZoneAwareLoadBalancer,创建时会一级级的构造父类,其祖父类BaseLoadBalancer初始化的时候会设置Rule和Ping组件。

2.6 负载均衡策略IRule

1699430749834.png 顶层接口IRule提供了选择服务、设置和获取负载均衡器的方法

1699430778196.png

2.6.1 未实现IRule接口

1.RibbonClientConfiguration这个类中

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

由于注解@ConditionalOnMissingBean,因为未实现IRule接口,容器中就没有这个bean,就会执行这个方法,从而会返回一个默认的ZoneAvoidanceRule对象。

2.相同类中,执行ribbonLoadBalancer()方法

	@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);
		}
        //这个rule就是刚刚初始化的ZoneAvoidanceRule对象
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

3.一直跟进方法,进入return new ZoneAwareLoadBalancer<>();并持续跟进

    public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, 				  		            				 ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        //继续跟进
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }


    public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, 
                                         IPing ping,
                                         ServerList<T> serverList,
                                         ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
       //继续跟进
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        restOfInit(clientConfig);
    }

    public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
        //继续跟进
        initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
    }


	void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
        this.config = clientConfig;
        String clientName = clientConfig.getClientName();
        this.name = clientName;
        int pingIntervalTime = Integer.parseInt(""
                + clientConfig.getProperty(
                        CommonClientConfigKey.NFLoadBalancerPingInterval,
                        Integer.parseInt("30")));
        int maxTotalPingTime = Integer.parseInt(""
                + clientConfig.getProperty(
                        CommonClientConfigKey.NFLoadBalancerMaxTotalPingTime,
                        Integer.parseInt("2")));

        setPingInterval(pingIntervalTime);
        setMaxTotalPingTime(maxTotalPingTime);
        
        ////继续跟进
        setRule(rule);
        setPing(ping);
		
		//省略...
    }

4.进入到BaseLoadBalancer类中setRule方法

    public void setRule(IRule rule) {
        if (rule != null) {
            this.rule = rule;
        } else {
            /* default rule */
            this.rule = new RoundRobinRule();
        }
        if (this.rule.getLoadBalancer() != this) {
            this.rule.setLoadBalancer(this);
        }
    }

这个rule就是开始创建的ZoneAvoidanceRule对象

2.6.2 实现IRule接口

@Configuration
public class LoadBanancerConfig {

    //这里创建了IRule这个Bean
    @Bean
    public IRule ribbonRule() {
        return new ResourceIpLoadBalancerRule();
    }

}

因为@ConditionalOnMissingBean注解的存在,并且已经存在了IRule这个Bean,就不会执行ribbonRule()这个方法了,之后的执行流程都是一样的。

2.6.3 总结:自定义IRule实现方式

ribbion的IRule的覆盖实现方式:

  • 自定义ribbonRule()方法,并注册Bean到Spring容器中

  • @ConditionalOnMissingBean注解,Spring容器中已存在IRule对象,不再执行默认赋值方法。

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

2.7 @RibbonClients与NamedContextFactory

此处不是重点,不多赘述,参考文章:https://blog.csdn.net/qq_22351805/article/details/113566246

​ SpringCloud的子上下文,NamedContextFactory目的是为其集合configurations中每个元素创建启动一个新的子应用上下文容器ApplicationContent。

​ 子上下文维护自身所有的bean,可从父context依赖到父context的bean,但是父context无法依赖到子context的bean。

​ 通过命名不同的子context,不同的子context之间可以共用configuration,也可以各自在共享的基础上自定义差异化的configuration。@RibbonClients就是使用场景之一,针对每个要调用的服务名,每个服务名定义一个子context对象,各自维护定期更新服务列表,server选择等任务。

@RibbonClients使用注解的方式完成Specification的实现和SpringClientFactory创建基于此概念。