【SpringCloud】spring-cloud-loadbalancer 的实现 三

  • 前言
  • ServiceInstanceListSupplier
  • DiscoveryClientServiceInstanceListSupplier
  • DelegatingServiceInstanceListSupplier
  • ZonePreferenceServiceInstanceListSupplier
  • 其他
  • ReactorLoadBalancer
  • LoadBalancerClientConfiguration
  • BlockingLoadBalancerClient#execute
  • 最佳实践
  • 示例
  • client 维度
  • 配置维度
  • 总结


前言

这一章节了解具体的 负载均衡 规则类了,先表达一下个人对 spring-cloud-loadbalancer 负载均衡 规则的理解:

  1. 根据 服务ID 获取对应的实例集合,这中间就包含一些规则:从注册中心获取? 同区域优先? 是否缓存?
  2. 从上述实例集合中选择最终确定的实例,这其中包含的规则:轮询获取? 随机获取?

那对应的接口分别就是 ServiceInstanceListSupplierReactorLoadBalancer

ServiceInstanceListSupplier

public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {

	// 服务id
	String getServiceId();

	// 实例列表
	default Flux<List<ServiceInstance>> get(Request request) {
		return get();
	}

	// 构造器
	static ServiceInstanceListSupplierBuilder builder() {
		return new ServiceInstanceListSupplierBuilder();
	}

}

该接口提供符合条件的实例列表,并提供了 builder 方法返回 ServiceInstanceListSupplierBuilder 实例用来构造 ServiceInstanceListSupplier

整个 ServiceInstanceListSupplier 的实现类都是 rx式 编程风格,但核心逻辑不难看懂,下面就贴出几个实现类简单了解下

DiscoveryClientServiceInstanceListSupplier

public class DiscoveryClientServiceInstanceListSupplier implements ServiceInstanceListSupplier {

	// ...

	public DiscoveryClientServiceInstanceListSupplier(DiscoveryClient delegate, Environment environment) {
		this.serviceId = environment.getProperty(PROPERTY_NAME);
		resolveTimeout(environment);
		this.serviceInstances = Flux.defer(() -> Flux.just(delegate.getInstances(serviceId)))
				.subscribeOn(Schedulers.boundedElastic()).timeout(timeout, Flux.defer(() -> {
					logTimeout();
					return Flux.just(new ArrayList<>());
				})).onErrorResume(error -> {
					logException(error);
					return Flux.just(new ArrayList<>());
				});
	}

主要逻辑在构造方法中,等价于 this.serviceInstances = discoveryClient.getInstances(serviceId),不难理解:从注册中心拉去实例列表

DelegatingServiceInstanceListSupplier

public abstract class DelegatingServiceInstanceListSupplier
		implements ServiceInstanceListSupplier, InitializingBean, DisposableBean {

	protected final ServiceInstanceListSupplier delegate;

	public DelegatingServiceInstanceListSupplier(ServiceInstanceListSupplier delegate) {
		Assert.notNull(delegate, "delegate may not be null");
		this.delegate = delegate;
	}

	// 	...

}

装饰层,内嵌一个代理对象,一般就是 DiscoveryClientServiceInstanceListSupplier 来获取实例列表,装饰逻辑就是过滤对应的列表

ZonePreferenceServiceInstanceListSupplier

public class ZonePreferenceServiceInstanceListSupplier extends DelegatingServiceInstanceListSupplier {

	// ...

	// delegate.get 后进行 filteredByZone 过滤
	@Override
	public Flux<List<ServiceInstance>> get() {
		return getDelegate().get().map(this::filteredByZone);
	}

	private List<ServiceInstance> filteredByZone(List<ServiceInstance> serviceInstances) {
		if (zone == null) {
			zone = zoneConfig.getZone();
		}
		if (zone != null) {
			List<ServiceInstance> filteredInstances = new ArrayList<>();

			/**
			 * 实例中获取 zone 进行匹配过滤
			 */
			for (ServiceInstance serviceInstance : serviceInstances) {
				String instanceZone = getZone(serviceInstance);
				if (zone.equalsIgnoreCase(instanceZone)) {
					filteredInstances.add(serviceInstance);
				}
			}
			if (filteredInstances.size() > 0) {
				return filteredInstances;
			}
		}
		return serviceInstances;
	}

	// ...

}

区域优先规则,delegate 获取实例列表后根据 实例元数据 来匹配 zone 属性

其他

还有其他实现类诸如:

  • HintBasedServiceInstanceListSupplier 根据 headerhint 属性过滤,默认属性名 X-SC-LB-Hint
  • SameInstancePreferenceServiceInstanceListSupplier 返回上一次返回的实例
  • 等等

ReactorLoadBalancer

public interface ReactorLoadBalancer<T> extends ReactiveLoadBalancer<T> {

	@SuppressWarnings("rawtypes")
	Mono<Response<T>> choose(Request request);

	default Mono<Response<T>> choose() {
		return choose(REQUEST);
	}

}

该接口从 ServiceInstanceListSupplier 返回的实例中选择最终目标,spring-cloud-loadbalancer 默认只提供了两个实现:

  • RandomLoadBalancer:随机选择
  • RoundRobinLoadBalancer:轮询

代码细节略

LoadBalancerClientConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {

	/**
	 * 实例选择规则:默认轮询
	 */
	@Bean
	@ConditionalOnMissingBean
	public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
			LoadBalancerClientFactory loadBalancerClientFactory) {
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RoundRobinLoadBalancer(
				loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
	}

	/**
	 * WebFlux 环境下的默认 ServiceInstanceListSupplier 配置
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnReactiveDiscoveryEnabled
	@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER)
	public static class ReactiveSupportConfiguration {

		// ...

	}

	/**
	 * 普通 web 环境下的配置
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnBlockingDiscoveryEnabled
	@Order(REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER + 1)
	public static class BlockingSupportConfiguration {

		/**
		 * 默认配置:withBlockingDiscoveryClient().withCaching()
		 * 		服务发现、缓存
		 */
		@Bean
		@ConditionalOnBean(DiscoveryClient.class)
		@ConditionalOnMissingBean
		@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default",
				matchIfMissing = true)
		public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
				ConfigurableApplicationContext context) {
			return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withCaching().build(context);
		}

		/**
		 * 区域优先配置
		 */
		@Bean
		@ConditionalOnBean(DiscoveryClient.class)
		@ConditionalOnMissingBean
		@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference")
		public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
				ConfigurableApplicationContext context) {
			return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withZonePreference()
					.withCaching().build(context);
		}

		/**
		 * 健康检查配置
		 * @param context
		 * @return
		 */
		@Bean
		@ConditionalOnBean({ DiscoveryClient.class, RestTemplate.class })
		@ConditionalOnMissingBean
		@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "health-check")
		public ServiceInstanceListSupplier healthCheckDiscoveryClientServiceInstanceListSupplier(
				ConfigurableApplicationContext context) {
			return ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().withBlockingHealthChecks()
					.build(context);
		}

		// ...

	}

	// ...

}

现在回过头来看每个 LoadBalancerClient 容器实例下默认注册的配置类 LoadBalancerClientConfiguration,如代码所示:

  • 默认的实例选择规则是 轮询
  • 对应的实例列表获取规则取决于 spring.cloud.loadbalancer.configurations 属性配置

BlockingLoadBalancerClient#execute

@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
		
		// ...
		
		// 实例选择
		ServiceInstance serviceInstance = choose(serviceId, lbRequest);
		
		// ...
		
		return execute(serviceId, serviceInstance, lbRequest);
	}

	-----------------------------------------------

	@Override
	public <T> ServiceInstance choose(String serviceId, Request<T> request) {

		/**
		 * 获取 serviceId 容器中的 ReactiveLoadBalancer 实例
		 */
		ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
		if (loadBalancer == null) {
			return null;
		}
		Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
		if (loadBalancerResponse == null) {
			return null;
		}
		return loadBalancerResponse.getServer();
	}

最后回到 BlockingLoadBalancerClient#execute 逻辑(容器中默认装配的 LoadBalancerClient):

  • 逻辑无非就是选择最终的实例来执行请求
  • 底层逻辑就是从隔离好的 容器 中获取对应的 ServiceInstanceListSupplierReactiveLoadBalancer 来选择实例

最佳实践

文末聊一下我认为的配置类最佳实践,无非两种:

  • client 维度,每一个 client 隔离一份配置,最后通过 @LoadBalancerClients 注解整合到一份配置类中,具体的配置建议以内部类的形式维护,缺点是:client 的规则发生变化时是需要修改对应配置类的
  • 配置维度,每一种配置隔离一份,这样通过 @LoadBalancerClients 注解整合到一份配置类中时,client 选择对应配置即可,缺点是:配置组合有很多种,因此配置类可能会较多,但好处是配置的变更仅仅需要修改 @LoadBalancerClients 的属性值即可

示例

client 维度

@LoadBalancerClients({
        @LoadBalancerClient(value = "eureka-client-1", configuration = ClietConfig.EurekaClient1Config.class)
        , @LoadBalancerClient(value = "eureka-client-2", configuration = ClietConfig.EurekaClient2Config.class)
})
public class ClietConfig {
    
    static class EurekaClient1Config {
        
        // ...
        
    }

    static class EurekaClient2Config {

        // ...

    }
}

配置维度

@LoadBalancerClients({
        @LoadBalancerClient(value = "eureka-client-1", configuration = LoadBalanceConfig.RandomLoadBalancerConfig.class)
        , @LoadBalancerClient(value = "eureka-client-2", configuration = LoadBalanceConfig.ZonePerferServiceListConfig.class)
})
public class LoadBalanceConfig {

    static class RandomLoadBalancerConfig {

        // ...
        
    }

    static class HintServiceListConfig {

        // ...
        
    }

    // ...
    
}
值得注意的是:负载均衡配置类是不需要加 @Configuration 注解的,否则就会
被同时注册进当前容器的

总结

至此,spring-cloud-loadbalancer 的基本实现原理就了解完了,我觉得 负载均衡 的逻辑固然重要,那更令我感兴趣的是 SpringCloud 对所有 LoadBalancerClient 的隔离实现,真的很巧妙且提供了十分强的可拓展性