【SpringCloud】spring-cloud-loadbalancer 的实现 三
- 前言
- ServiceInstanceListSupplier
- DiscoveryClientServiceInstanceListSupplier
- DelegatingServiceInstanceListSupplier
- ZonePreferenceServiceInstanceListSupplier
- 其他
- ReactorLoadBalancer
- LoadBalancerClientConfiguration
- BlockingLoadBalancerClient#execute
- 最佳实践
- 示例
- client 维度
- 配置维度
- 总结
前言
这一章节了解具体的 负载均衡 规则类了,先表达一下个人对 spring-cloud-loadbalancer
负载均衡 规则的理解:
- 根据
服务ID
获取对应的实例集合,这中间就包含一些规则:从注册中心获取?
同区域优先?
是否缓存?
等 - 从上述实例集合中选择最终确定的实例,这其中包含的规则:
轮询获取?
随机获取?
等
那对应的接口分别就是 ServiceInstanceListSupplier
和 ReactorLoadBalancer
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
根据header
的hint
属性过滤,默认属性名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
):
- 逻辑无非就是选择最终的实例来执行请求
- 底层逻辑就是从隔离好的
容器
中获取对应的ServiceInstanceListSupplier
和ReactiveLoadBalancer
来选择实例
最佳实践
文末聊一下我认为的配置类最佳实践,无非两种:
-
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
的隔离实现,真的很巧妙且提供了十分强的可拓展性