微服务组件源码2——Spring Ribbon原理(基于RibbonLoadBalancerClient)

1、基本原理( LoadBalancerClient + 拦截器)

Ribbon 负载组件的内部就是集成了 LoadBalancerClient 负载均衡客户端,所以 Ribbon 负载均衡的原理本质也跟上面介绍的 LoadBalancerClient 原理一致,负载均衡器 Ribbon 默认会通过 Eureka Client 向 Eureka 服务端的服务注册列表中获取服务的信息,并缓存一份在本地 JVM 中,根据缓存的服务注册列表信息,可以通过 LoadBalancerClient 来选择不同的服务实例,从而实现负载均衡。主要做如下的增强处理:

基本用法就是注入一个 RestTemplate,并使用@LoadBalance注解标注 RestTemplate,从而使RestTemplate具备负载均衡的能力。当 Spring 容器启动时,使用@LoadBalanced 注解修饰的 RestTemplate 会被添加拦截器 LoadBalancerInterceptor,拦截器会拦截 RestTemplate 发送的请求,转而执行LoadBalancerInterceptor 中的 intercept() 方法,并在 intercept()方法中使用LoadBalancerClient 处理请求,从而达到负载均衡的目的。

那么 RestTemplate 添加 @LoadBalanced 注解后,为什么会被拦截呢?这是因为 LoadBalancerAutoConfiguration 类维护了一个被 @LoadBalanced 修饰的 RestTemplate 列表,在初始化过程中,通过调用 customizer.customize(restTemplate) 方法为 RestTemplate 添加了 LoadBalancerInterceptor 拦截器,该拦截器中的方法将远程服务调用的方法交给了 LoadBalancerClient 去处理,从而达到了负载均衡的目的。

Ribbon会提供一个serviceId对应的服务实例,此外就不会做其他事了,基于此服务实例进行远程调用、返回结果处理等都是由RestTemplate去处理的

2、自动配置概述

2.1、RibbonAutoConfiguration

* 收集@RibbonClients注解信息封装为RibbonClientSpecification(功能和@LoadBalancerClients注解类似)

会收集注册@RibbonClients、RibbonClient注解,name和指定的配置类(包括默认)为RibbonClientSpecification(String name, Class<?>\[\] configuration)类

@Configuration

@RibbonClient(name = "orderService",configuration = HelloRibbonConfiguration.class)

@RibbonClients(defaultConfiguration = MyRibbonConfiguration.class)

public class RibbonConfiguration {}

* 创建SpringClientFactory extends NamedContextFactory<RibbonClientSpecification>命名上下文

①会基于容器中List<RibbonClientSpecification> configurations(上一步注册的配置集合),为每一个指定的name 创建一个上下文,父上下文为顶层Spring容器

每个上下文里包含可独自指定的RibbonClientSpecification配置和默认配置类RibbonClientConfiguration,如IClientConfig客户端配置、IRule策略的配置、超时配置等

②其中在RibbonClientConfiguration里的IClientConfig客户端配置的默认配置为DefaultClientConfigImpl,它还会加载文件配置的信息

从Spring env中加载"ribbon.配置项"这类全局默认配置、和加载"client名.ribbon.配置项"这类针对某个Client的配置信息

* 创建RibbonLoadBalancerClient(springClientFactory()) 用户使用的顶级LoadBalancerClient 接口对象

①List<RibbonClientSpecification>收集全局配置和单个服务的配置信息

②SpringClientFactory会为每一个服务创建独立的上下文

③new RibbonLoadBalancerClient(springClientFactory())

@Configuration
@Conditional(org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration.RibbonClassesConditions.class )
// 其内 RibbonClientConfigurationRegistrar 会收集注册 @RibbonClients RibbonClient 注解内的那么, name 和指定的配置类(包括默认)为 RibbonClientSpecification
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration" )
// @LoadBalanced 注解修饰的 RestTemplate 会被添加拦截器 LoadBalancerInterceptor(loadBalancerClient, requestFactory)
@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 );
}
// 客户端配置容器工厂:会为每一个 ClientName 和指定的配置类 ( 包含了默认配置类 RibbonClientConfiguration) 创建一个独立的上下文
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this .configurations );
return factory;
}
// 使用客户端配置容器工厂创建顶层对象, RibbonLoadBalancerClient ,实现了 LoadBalancerClient 接口
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class )
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}

//

}

// SpringClientFactory

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

// 通过 NamedContextFactory 获取指定容器内的某个 type 实例,获取不到时,会通过 IClientConfig 进行创建,
// 调用容器的 autowireBean 进属性注入,但是最终实例不会放入容器中
@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);
}

//
}

2.2、 RibbonClientConfiguration 默认配置(serviceId维度)

* 是默认的Ribbon客户端配置类,在创建SpringClientFactory是指定的默认配置类

* 每个独立的服务ID上下文中都会注入此类配置的信息

  • 指定了robbin会用到的所有组件的bean

  • 同时还import指定了具体的httpClient的自动配置

@Import({ HttpClientConfiguration.class , OkHttpRibbonConfiguration.class ,

RestClientRibbonConfiguration.class , HttpClientRibbonConfiguration.class })

2.2.1、Name容器中6大可配置组件
  1. Spring Ribbon的6大可配置组件类及默认配置

|----------------------------|---------------|-------------------------------------------------------------------------|-------------------------------------------------------|
| 自动化配置接口 | 描述 | 默认实现 | 说明 |
| IClientConfig | Ribbon的客户端配置 | com.netflix.client.config.DefaultClientConfigImpl | 对如下组件及其他信息的配置项 |
| IRule | Ribbon的负载均衡策略 | com.netflix.loadbalancer.ZoneAvoidanceRule | 该策略能在多区域环境下选出最佳区域的实例进行访问 |
| IPing | Ribbon的实例检查策略 | com.netflix.loadbalancer.NoOpPing | 该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认所有的实例都是可用的 |
| ServerList<Server> | 服务实例清单维护机制 | com.netflix.loadbalancer.ConfigurationBasedServerList | |
| ServerListFilter<Server> | 服务实例清单过滤机制 | org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter | 该策略能够优先过滤出与请求调用方处于同一个区域的服务清单 |
| ILoadBalancer | (负载均衡器主类) | com.netflix.loadbalancer.ZoneAwareLoadBalancer | 该策略具备服务感知能力,封装了如上几个组件 |

  1. 与Eureka、Ncaos集成

* 当在Spring Cloud中同时引入Spring Cloud Eureka 和 Spring Cloud Ribbon 时,会触发Eureka对于Ribbon的自动化配置,那么Ribbon的相关默认实现类就会有所变化。

|----------------------|---------------|--------------------------------------------------------------|--------------------------|
| 自动化配置接口 | 描述 | 默认实现 | 说明 |
| IPing | Ribbon的实例检查策略 | com.netflix.niws.loadbalancer.NIWSDiscoveryPing | 该实现将实例检查的任务交给服务治理框架来进行维护 |
| ServerList<Server> | 服务实例清单维护机制 | com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList | 该实现会将服务清单列表交给Eureka来维护 |

在与Spring Cloud Eureka结合使用时,我们的配置会更简单,例如上一步中提到的客户端配置EUREKA-CLIENT.ribbon.listOfServers,就不需要再这么麻烦的进行配置,

因为Eureka的自动配置类会为我们维护所有实例的清单。

我们也可以通过参数配置来禁用Eureka对Ribbon服务实例的维护实现:ribbon.eureka.enabled=false

* 同样的,当在Spring Cloud中同时引入Spring Cloud Nacos和 Spring Cloud Ribbon 时,且禁用了Eureka,此时也会只有Nacos自定义的实现类覆盖关默认实现类

例如我们希望使用Nacos的IRule实现,那么在配置类中加上

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

// Spring Cloud Ribbon中对RibbonClient的默认配置类

@Configuration(proxyBeanMethods = false )
@EnableConfigurationProperties
// 具体通信工具配置 HttpClient
@Import({ HttpClientConfiguration.class , OkHttpRibbonConfiguration.class ,
RestClientRibbonConfiguration.class , HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {

public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
public static final boolean DEFAULT_GZIP_PAYLOAD = true ;

@RibbonClientName
private String name = "client" ;

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

//

}

2.2.2、 OkHttpRibbonConfiguration

配置具体的通信工具,例如当项目存在OkHttpClient类且存在配置属性ribbon.okhttp.enabled,会加载这个配置类

* OkHttpLoadBalancingClient和RetryableOkHttpLoadBalancingClient实现类bean

是实现负载均衡功能中顶级接口com.netflix.client.IClient

* OkHttpClientConfiguration

是存粹的OkHttpClient的配置,包括配置连接池、生成OkHttpClient对象

注意这里OkHttpClient的生成,会从IClientConfig config配置中获取Ribbon相关的配置(超时配置),设置在OkHttpClient中

注:基于RestTemplate使用ribbon的方法内,是没有用到此配置的bean。是直接借助RestTemplate的通信功能进行访问。Ribbon值提供serverInstan的选择

@Configuration(proxyBeanMethods = false )
@ConditionalOnProperty("ribbon.okhttp.enabled" )
@ConditionalOnClass(name = "okhttp3.OkHttpClient" )
public class OkHttpRibbonConfiguration {

@RibbonClientName
private String name = "client" ;

@Bean
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class )
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate" )
public RetryableOkHttpLoadBalancingClient retryableOkHttpLoadBalancingClient(
IClientConfig config, ServerIntrospector serverIntrospector,
ILoadBalancer loadBalancer, RetryHandler retryHandler,
LoadBalancedRetryFactory loadBalancedRetryFactory, OkHttpClient delegate,
RibbonLoadBalancerContext ribbonLoadBalancerContext) {
RetryableOkHttpLoadBalancingClient client = new RetryableOkHttpLoadBalancingClient(
delegate, config, serverIntrospector, loadBalancedRetryFactory);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
client.setRibbonLoadBalancerContext(ribbonLoadBalancerContext);
Monitors.registerObject ("Client_" + this .name , client);
return client;
}

@Bean
@ConditionalOnMissingBean(AbstractLoadBalancerAwareClient.class )
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate" )
public OkHttpLoadBalancingClient okHttpLoadBalancingClient(IClientConfig config,
ServerIntrospector serverIntrospector, ILoadBalancer loadBalancer,
RetryHandler retryHandler, OkHttpClient delegate) {
OkHttpLoadBalancingClient client = new OkHttpLoadBalancingClient(delegate, config,
serverIntrospector);
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
Monitors.registerObject ("Client_" + this .name , client);
return client;
}

@Configuration(proxyBeanMethods = false )
protected static class OkHttpClientConfiguration {

private OkHttpClient httpClient ;

@Bean
@ConditionalOnMissingBean(ConnectionPool.class )
public ConnectionPool httpClientConnectionPool(IClientConfig config,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
RibbonProperties ribbon = RibbonProperties.from (config);
int maxTotalConnections = ribbon.maxTotalConnections();
long timeToLive = ribbon.poolKeepAliveTime();
TimeUnit ttlUnit = ribbon.getPoolKeepAliveTimeUnits();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}

@Bean
@ConditionalOnMissingBean(OkHttpClient.class )
public OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool, IClientConfig config) {
RibbonProperties ribbon = RibbonProperties.from (config);
this .httpClient = httpClientFactory.createBuilder(false )
.connectTimeout(ribbon.connectTimeout(), TimeUnit.MILLISECONDS )
.readTimeout(ribbon.readTimeout(), TimeUnit.MILLISECONDS )
.followRedirects(ribbon.isFollowRedirects())
.connectionPool(connectionPool).build();
return this .httpClient ;
}

@PreDestroy
public void destroy() {
if (httpClient != null ) {
httpClient .dispatcher().executorService().shutdown();
httpClient .connectionPool().evictAll();
}
}

}

}

2.3、 LoadBalancerAutoConfiguration

* 把标注了@LoadBalanced注解的所有RestTemplate实例,添加一个LoadBalancerInterceptor(loadBalancerClient, requestFactory)

  • loadBalancerClient是在RibbonAutoConfiguration中指定的RibbonLoadBalancerClient(springClientFactory()) 类型bean

  • requestFactory是本配置类中指定的LoadBalancerRequestFactory类型bean

  • @LoadBalanced注解就是@Qualifier类注解,用于匹配bean的标志

@Qualifier
public @interface LoadBalanced {}

* 执行RestTemplates时,会被这个拦截器拦截,

①LoadBalancerRequestFactory会根据传递进来的ClientHttpRequestExecution创建具体通信工具封装后的LoadBalancerRequest

②RestTemplates可以设置ClientHttpRequestFactory,默认为SimpleClientHttpRequestFactory来创建ClientHttpRequest

③LoadBalancerClient负载均衡器会根据serviceName选择合适的路径,进行请求

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

// 把标注的了 @LoadBalanced 所有 RestTemplate ,添加一个 LoadBalancerInterceptor(loadBalancerClient, requestFactory)
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this .restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}

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

@Configuration(proxyBeanMethods = false )
@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);
};
}

}

//

}

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) {
// for backwards compatibility
this (loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}

// 执行 RestTemplates 时,会被这个拦截器拦截,
//LoadBalancerRequestFactory 会根据传递进来的 ClientHttpRequestExecution 创建具体通信工具封装后的 LoadBalancerRequest
// RestTemplates 可以设置 ClientHttpRequestFactory ,默认为 SimpleClientHttpRequestFactory 来创建 ClientHttpRequest
//LoadBalancerClient 负载均衡器会根据 serviceName 选择合适的路径,进行请求
//
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte \[\] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this .loadBalancer .execute(serviceName,
this .requestFactory .createRequest(request, body, execution));
}

3、Name容器中6大可配置组件

3.1、概述

  1. Spring Ribbon的6大可配置组件类及默认配置

|----------------------------|---------------|-------------------------------------------------------------------------|-------------------------------------------------------|
| 自动化配置接口 | 描述 | 默认实现 | 说明 |
| IClientConfig | Ribbon的客户端配置 | com.netflix.client.config.DefaultClientConfigImpl | 对如下组件及其他信息的配置项 |
| IRule | Ribbon的负载均衡策略 | com.netflix.loadbalancer.ZoneAvoidanceRule | 该策略能在多区域环境下选出最佳区域的实例进行访问 |
| IPing | Ribbon的实例检查策略 | com.netflix.loadbalancer.NoOpPing | 该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认所有的实例都是可用的 |
| ServerList<Server> | 服务实例清单维护机制 | com.netflix.loadbalancer.ConfigurationBasedServerList | |
| ServerListFilter<Server> | 服务实例清单过滤机制 | org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter | 该策略能够优先过滤出与请求调用方处于同一个区域的服务清单 |
| ILoadBalancer | (负载均衡器主类) | com.netflix.loadbalancer.ZoneAwareLoadBalancer | 该策略具备服务感知能力,封装了如上几个组件 |

3.2、IClientConfig配置项组件

3.2.1、 IClientConfig 接口

* ClientName和NameSpace的获取

这是为了隔离不同的服务调用端的配置

* 其他接口就是属性的增删改查

public interface IClientConfig {

public String getClientName();
public String getNameSpace();

public void loadProperties(String clientName);
public void loadDefaultValues();

public Map<String, Object> getProperties();
public boolean containsProperty(IClientConfigKey key);

public String resolveDeploymentContextbasedVipAddresses();
public int getPropertyAsInteger(IClientConfigKey key, int defaultValue);
public String getPropertyAsString(IClientConfigKey key, String defaultValue);
public boolean getPropertyAsBoolean(IClientConfigKey key, boolean defaultValue);
public <T> T get(IClientConfigKey<T> key);
public <T> T get(IClientConfigKey<T> key, T defaultValue);
public <T> IClientConfig set(IClientConfigKey<T> key, T value);

}

3.2.2、DefaultClientConfigImpl实现类

* 作用是保存了所有的配置项,基于Archaius实现,支持动态的获取配置项的最新值

* 默认实现是DefaultClientConfigImpl,此类作用:

①保存各个组件的默认实现类,比如

public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule";

public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer";

注:此默认配置在DefaultClientConfigImpl和RibbonClientConfiguration (配置bean加入到独立的服务上下文中)均有指定

②保存各种属性的默认配置,支持动态比如

public static final int DEFAULT_READ_TIMEOUT = 5000;

public static final int DEFAULT_CONNECTION_MANAGER_TIMEOUT = 2000;

public static final int DEFAULT_CONNECT_TIMEOUT = 2000;

* loadProperties会加载环境对象中如下属性,保存到本地缓存Map<String, Object> properties,或者支持动态刷新的Map<String, DynamicStringProperty> dynamicProperties

①从Spring env中加载"ribbon.配置项"这类,作为默认配置

使用 ribbon.<key>=<value> 的形式配置,例如全局配置连接超时时间:

ribbon:

ConnectTimeout: 250

②和加载"client名.ribbon.配置项"这类针对某个Client的配置信息

指定客户端配置方式采用 <client>.ribbon.<key>=<value>,使用样例如下所示,同时,如果同时配置了全局配置和指定客户端配置,那么以指定客户端的配置为准。

EUREKA-CLIENT:

ribbon:

listOfServers: localhost:8001,localhost:8002

* 基本原理(更多原理见Archaius文档)

  • 基于Archaius作为数据源根据,对于Springboot项目来说,由ArchaiusAutoConfiguration进行自动配置,这个数据源一般为ConfigurableEnvironmentConfiguration

此AbstractConfiguration实现类,以Spring容器的环境Environmen对象作为数据源进行获取,而不会对其进行反向设置

也会组合其他的数据源,一并配置到ConfigurationManager.install(config);

  • 同时也注入了一个监听器ApplicationListener<EnvironmentChangeEvent>

当监听 到环境配置修改时,会获取ConfigurableEnvironmentConfiguration中设置好的自动的动态刷新监听器,执行

listener.configurationChanged(new ConfigurationEvent(source, type,key, value, beforeUpdate));

这样Map<String, Object> properties,或者支持动态刷新的Map<String, DynamicStringProperty> dynamicProperties里对应的值就会被刷新

  • config.loadProperties(clientname)方法会从ConfigurableEnvironmentConfiguration中在抽取出指定格式key的键值对作为新的AbstractConfiguration实现类

即SubsetConfiguration以实现数据源的隔离,例如SubsetConfiguration对抽象方法的实现,parent就是原始ConfigurableEnvironmentConfiguration配置

getParentKey(key)会拼接clientname前缀

public void addPropertyDirect(String key, Object value) {

parent.addProperty(getParentKey(key), value);

}

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

public class DefaultClientConfigImpl implements IClientConfig {

public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE ;
public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing" ; // DummyPing.class.getName();
public static final String DEFAULT_NFLOADBALANCER_RULE_CLASSNAME = "com.netflix.loadbalancer.AvailabilityFilteringRule" ;
public static final String DEFAULT_NFLOADBALANCER_CLASSNAME = "com.netflix.loadbalancer.ZoneAwareLoadBalancer" ;
public static final boolean DEFAULT_USEIPADDRESS_FOR_SERVER = Boolean.FALSE ;
public static final String DEFAULT_CLIENT_CLASSNAME = "com.netflix.niws.client.http.RestClient" ;
public static final String DEFAULT_VIPADDRESS_RESOLVER_CLASSNAME = "com.netflix.client.SimpleVipAddressResolver" ;
public static final int DEFAULT_MAX_TOTAL_TIME_TO_PRIME_CONNECTIONS = 30000;
//

public void loadProperties(String restClientName) {
// 启用动态属性 如果开启属性将具有动态 性 这里也是唯一一处将该值设置成 true 的地方
// 所以如果仅仅是默认值 不支持动态属性的
enableDynamicProperties = true ;
// 设置 clientName
setClientName(restClientName);
// 加载默认属性
// 依赖 Archaius 获取对应 key 的配置值。但是 但是有一点需要特别注意。这里的 ConfigurationManager.getConfigInstance().getString() 方法获取的配置和
// 例如我们配置的值是 a,b,c 那么 getStringValue 获取的值是 a,b,c getString() 获取的却是 a ! 所以默认值字符串类型的 配置里面出现逗号那就有问题了。
//
loadDefaultValues();
// 这里可以看到底层的配置还是通过 Archaius 来配置的 所以我们把配置写在 classpath config.properties 中是生效的
// 对于 subset 这个方法 举个例子可能会更清楚 例如我们在 config 文件中的配置是 coredy.ribbon.ReadTimeout
// 我们调用 subset("coredy") 那就会给我们返回所有以 coredy 开头的 Configuration
Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
// 遍历得到的 Configuration 将属性放到全局的配置里面 properties
for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ) {
String key = keys.next();
String prop = key;
try {

// 指定了NameSpace的情况,默认你为ribbon,需要截断这个前缀
if (prop.startsWith(getNameSpace())) {
prop = prop.substring(getNameSpace().length() + 1);
}
// 特别注意这里是使用 getStringValue(props, key) 来获取值得
// 意为着 你配置的属性是 a,b,c 那么最终在全局配置 properties 里面的值也是 a,b,c
setPropertyInternal(prop, getStringValue(props, key));
} catch (Exception ex) {
throw new RuntimeException(String.format ("Property %s is invalid" , prop));
}
}

//
}

3.3、ServerList<Server>获取指定serviceId服务列表组件

3.3.1、 ServerList<T extends Server >接口

* 定义获取指定serviceId服务列表的方法

public interface ServerList<T extends Server> {

// 初始 可用的服务列表
public List<T> getInitialListOfServers();
// 经过ping进行心跳正常过滤后的最新可用的服务列表
public List<T> getUpdatedListOfServers();

}

* 一个Server对象表示一个服务实例的信息

public class Server {

public static final String UNKNOWN_ZONE = "UNKNOWN" ;
private String host ;
private int port = 80;
private String scheme ;
private volatile String id ;// 通常为 http域名+端口
private volatile boolean isAliveFlag ;
private String zone = UNKNOWN_ZONE ;
private volatile boolean readyToServe = true ;

private MetaInfo simpleMetaInfo = new MetaInfo() {
/**
* 服务实例对应服务器的名称和服务组
*/

@Override
public String getAppName() {
return null ;
}
@Override
public String getServerGroup() {
return null ;
}
/**
* 服务实例的别名
*/
@Override
public String getServiceIdForDiscovery() {
return null ;
}

@Override
public String getInstanceId() {
return id ;
}
};

public Server(String host, int port) {
this (null , host, port);
}

public Server(String scheme, String host, int port) {
this .scheme = scheme;
this .host = host;
this .port = port;
this .id = host + ":" + port;
isAliveFlag = false ;
}

//..

}

3.3.2、NacosServerList

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

* 继承AbstractServerList,这里值初始化了过滤器实现类

  • 优先取NIWSServerListFilterClassName配置的,

  • 如果为null,取NIWSServerListFilterClassName类

  • 在RibbonClientConfiguration自动配置类中,默认配置为ZonePreferenceServerListFilter

* NacosServerList实现类

  • 逻辑简单,直接借助Nacos提供的客户端NacosDiscoveryProperties 获取服务列表

List<Instance> instances = discoveryProperties.namingServiceInstance().selectInstances(serviceId, true);

  • 再适配为ribbon需要的Server类型即可

  • 一个iClientConfig.getClientName(),即一个ServiceId对应一个NacosServerList

public class NacosServerList extends AbstractServerList<NacosServer> {

private NacosDiscoveryProperties discoveryProperties ;

private String serviceId ;

public NacosServerList(NacosDiscoveryProperties discoveryProperties) {
this .discoveryProperties = discoveryProperties;
}

@Override
public List<NacosServer> getInitialListOfServers() {
return getServers();
}

@Override
public List<NacosServer> getUpdatedListOfServers() {
return getServers();
}

private List<NacosServer> getServers() {
try {
List<Instance> instances = discoveryProperties .namingServiceInstance()
.selectInstances(serviceId , true );
return instancesToServerList(instances);
}
catch (Exception e) {
throw new IllegalStateException(
"Can not get service instances from nacos, serviceId=" + serviceId ,
e);
}
}

private List<NacosServer> instancesToServerList(List<Instance> instances) {
List<NacosServer> result = new ArrayList<>();
if (null == instances) {
return result;
}
for (Instance instance : instances) {
result.add(new NacosServer(instance));
}

return result;
}

public String getServiceId() {
return serviceId ;
}

@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
this .serviceId = iClientConfig.getClientName();
}
}

3.4、ServerListFilter<T extends Server>服务列表过滤组件

3.4.1、 ServerListFilter 接口

public interface ServerListFilter<T extends Server> {

public List<T> getFilteredListOfServers(List<T> servers);

}

3.4.2、ZoneAffinityServerListFilter实现类

* getFilteredListOfServers的实现逻辑,分两步过滤

  • 初步过滤:

对指定serverId下的所有实例列表默认使用ZoneAffinityPredicate使用过滤:过滤配置指定zone的服务实例

一般的服务发现客户端会配置好这个zone值

  • 对初步过滤的结果再次判断

①配置zoneAffinity(默认flase)和zoneExclusive(默认flase)均没有true时,不过滤zone,选择所有实例列表

②当zoneExclusive为true,使用初步过滤的(指定zone过滤)

③当zoneAffinity为true,表示需要再次过滤判断:基于LoadBalancerStats判断这些初步过滤的服务实例的状态是否符合配置的阈值

符合就使用过滤的,否则不过滤

((double) circuitBreakerTrippedCount) / instanceCount >= blackOutServerPercentageThreshold.get()

|| loadPerServer >= activeReqeustsPerServerThreshold.get()

|| (instanceCount - circuitBreakerTrippedCount) < availableServersThreshold.get())

public class ZoneAffinityServerListFilter<T extends Server> extends
AbstractServerListFilter<T> implements IClientConfigAware {

private volatile boolean zoneAffinity = DefaultClientConfigImpl.DEFAULT_ENABLE_ZONE_AFFINITY ;
private volatile boolean zoneExclusive = DefaultClientConfigImpl.DEFAULT_ENABLE_ZONE_EXCLUSIVITY ;
private DynamicDoubleProperty activeReqeustsPerServerThreshold ;
private DynamicDoubleProperty blackOutServerPercentageThreshold ;
private DynamicIntProperty availableServersThreshold ;
private Counter overrideCounter ;
private ZoneAffinityPredicate zoneAffinityPredicate = new ZoneAffinityPredicate();

private static Logger logger = LoggerFactory.getLogger (ZoneAffinityServerListFilter.class );

String zone ;

public ZoneAffinityServerListFilter() {
}

public ZoneAffinityServerListFilter(IClientConfig niwsClientConfig) {
initWithNiwsConfig(niwsClientConfig);
}

// 从配置组件中获取属性
@Override
public void initWithNiwsConfig(IClientConfig niwsClientConfig) {
String sZoneAffinity = "" + niwsClientConfig.getProperty(CommonClientConfigKey.EnableZoneAffinity , false );
if (sZoneAffinity != null ){
zoneAffinity = Boolean.parseBoolean (sZoneAffinity);
}
String sZoneExclusive = "" + niwsClientConfig.getProperty(CommonClientConfigKey.EnableZoneExclusivity , false );
if (sZoneExclusive != null ){
zoneExclusive = Boolean.parseBoolean (sZoneExclusive);
}
if (ConfigurationManager.getDeploymentContext () != null ) {
zone = ConfigurationManager.getDeploymentContext ().getValue(ContextKey.zone );
}
activeReqeustsPerServerThreshold = DynamicPropertyFactory.getInstance ().getDoubleProperty(niwsClientConfig.getClientName() + "." + niwsClientConfig.getNameSpace() + ".zoneAffinity.maxLoadPerServer" , 0.6d);
blackOutServerPercentageThreshold = DynamicPropertyFactory.getInstance ().getDoubleProperty(niwsClientConfig.getClientName() + "." + niwsClientConfig.getNameSpace() + ".zoneAffinity.maxBlackOutServesrPercentage" , 0.8d);
availableServersThreshold = DynamicPropertyFactory.getInstance ().getIntProperty(niwsClientConfig.getClientName() + "." + niwsClientConfig.getNameSpace() + ".zoneAffinity.minAvailableServers" , 2);
overrideCounter = Monitors.newCounter("ZoneAffinity_OverrideCounter" );
Monitors.registerObject("NIWSServerListFilter_" + niwsClientConfig.getClientName());
}

@Override
public List<T> getFilteredListOfServers(List<T> servers) {
if (zone != null && (zoneAffinity || zoneExclusive ) && servers !=null && servers.size() > 0){
// 初步过滤:使用指定 zone 过滤的
List<T> filteredServers = Lists.newArrayList (Iterables.filter (
servers, this .zoneAffinityPredicate .getServerOnlyPredicate()));
// 再次过滤判断,符合才使用初步过滤的,否则放行所有
if (shouldEnableZoneAffinity(filteredServers)) {
return filteredServers;
} else if (zoneAffinity ) {
overrideCounter .increment();
}
}
return servers;
}

//zoneAffinity zoneExclusive 均没有 true 时,过滤
// zoneExclusive true ,使用初步过滤的(指定 zone 过滤)
// zoneAffinity true ,表示需要再次过滤:基于 LoadBalancerStats 判断这些初步过滤的服务实例的状态是否符合配置的阈值
// 符合就使用过滤的,否则不过滤
private boolean shouldEnableZoneAffinity(List<T> filtered) {
if (!zoneAffinity && !zoneExclusive ) {
return false ;
}
if (zoneExclusive ) {
return true ;
}
LoadBalancerStats stats = getLoadBalancerStats();
if (stats == null ) {
return zoneAffinity ;
} else {
ZoneSnapshot snapshot = stats.getZoneSnapshot(filtered);
double loadPerServer = snapshot.getLoadPerServer();
int instanceCount = snapshot.getInstanceCount();
int circuitBreakerTrippedCount = snapshot.getCircuitTrippedCount();
if (((double ) circuitBreakerTrippedCount) / instanceCount >= blackOutServerPercentageThreshold .get()
|| loadPerServer >= activeReqeustsPerServerThreshold .get()
|| (instanceCount - circuitBreakerTrippedCount) < availableServersThreshold .get()) {
return false ;
} else {
return true ;
}

}
}

}

3.4.3、ZonePreferenceServerListFilter实现类

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

* 在RibbonClientConfiguration自动配置中,默认实现为ZonePreferenceServerListFilter

* ZonePreferenceServerListFilter继承ZoneAffinityServerListFilter

当ZoneAffinityServerListFilter过滤失败(即调用父类过滤后,的实例数不变))时,使用指定zone(如有)进行过滤

public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter<Server> {

private String zone ;

@Override
public void initWithNiwsConfig(IClientConfig niwsClientConfig) {
super .initWithNiwsConfig(niwsClientConfig);
if (ConfigurationManager.getDeploymentContext () != null ) {
this .zone = ConfigurationManager.getDeploymentContext ()
.getValue(ContextKey.zone );
}
}

@Override
public List<Server> getFilteredListOfServers(List<Server> servers) {
List<Server> output = super .getFilteredListOfServers(servers);
if (this .zone != null && output.size() == servers.size()) {
List<Server> local = new ArrayList<>();
for (Server server : output) {
if (this .zone .equalsIgnoreCase(server.getZone())) {
local.add(server);
}
}
if (!local.isEmpty()) {
return local;
}
}
return output;
}

}

3.5、IPing连通性检测组件

// 各个组件的默认实现

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

}

* 默认的逻辑始终为true,即不会进行ping处理

这时为了性能考虑,不需要主动的去ping每一个实例的连通性,因为在ZoneAwareLoadBalancer中会进行过滤,

如果实例有问题,那么必定存在socket相关的异常,那么就会触发此实例的断路,

public interface IPing {
public boolean isAlive(Server server);
}

public class DummyPing extends AbstractLoadBalancerPing {

public DummyPing() {
}

public boolean isAlive(Server server) {
return true ;
}

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}

3.6、IRule选取规则组件

3.6.1、IRule接口

由接口方法即可知道:核心方法为choose选择一个serverId下的一个服务实例,而且逻辑或基于ILoadBalancer实现类

public interface IRule{

public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}

3.6.2、ZoneAvoidanceRule实现类
3.6.2.1、主要流程

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

}

* 是RibbonAutoConfiguration指定的默认规则

* 其核心逻辑是:当前serviceId下的每一个服务实例经过其内配置的compositePredicate进行过滤后的候选服务实例集合,再进行轮询即可

注:每一服务实例都需要走一遍下面的流程,比如ZoneAvoidanceRule.getAvailableZones方法会调用多此(其实没必要)

  • ZoneAvoidancePredicate校验

①基于LoadBalancerStats获取当前服务实例对应的zoneSnapshot

使用ZoneAvoidanceRule.getAvailableZones计算出可用有效的分区availableZones,再进行下一步AvailabilityPredicate的校验

getAvailableZones具体算法见下

②如果此服务实例对应的zone或者zoneSnapshot 不存在,那么会直接放行,进行下一步AvailabilityPredicate的校验

  • AvailabilityPredicate校验

①所有实例不处于断路状态

②所有实例的活跃请求数小于niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit配置,默认为int最大值

  • 两个Predicate校验都满足后,最为候选加入List<Server> eligible

如果最终候选为0,那么再遍历一次,符合AvailabilityPredicate这个校验即可

如果二次校验AvailabilityPredicate还是失败,那么默认全部实例放行

  • 对最终结果List<Server> eligible进行轮询

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {

public abstract AbstractServerPredicate getPredicate();

@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//getPredicate() 就是子类 ZoneAvoidanceRule CompositePredicate compositePredicate
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null ;
}
}
}

public class ZoneAvoidanceRule extends PredicateBasedRule {

private static final Random random = new Random();
private CompositePredicate compositePredicate ;

public ZoneAvoidanceRule() {
super ();
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this );
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this );
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}

private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
return CompositePredicate.withPredicates (p1, p2)
.addFallbackPredicate(p2)
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue ())
.build();

}

//..

}

//CompositePredicate 的父类,其中chooseRoundRobinAfterFiltering如下
public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {

protected IRule rule ;
private volatile LoadBalancerStats lbStats ;

private final Random random = new Random();

private final AtomicInteger nextIndex = new AtomicInteger();

private final Predicate<Server> serverOnlyPredicate = new Predicate<Server>() {
@Override
public boolean apply(@Nullable Server input) {
return AbstractServerPredicate.this .apply(new PredicateKey(input));
}
};

public static AbstractServerPredicate alwaysTrue() {
return new AbstractServerPredicate() {
@Override
public boolean apply(@Nullable PredicateKey input) {
return true ;
}
};
}

// 同理:在过滤之后进行随机
public Optional<Server> chooseRandomlyAfterFiltering(List<Server> servers) {
List<Server> eligible = getEligibleServers(servers);
if (eligible.size() == 0) {
return Optional.absent ();
}
return Optional.of (eligible.get(random .nextInt(eligible.size())));
}

// 在过滤之后进行随机 轮询
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers) {
List<Server> eligible = getEligibleServers(servers);
if (eligible.size() == 0) {
return Optional.absent ();
}
return Optional.of (eligible.get(incrementAndGetModulo(eligible.size())));
}

public List<Server> getEligibleServers(List<Server> servers) {
return getEligibleServers(servers, null );
}

// 遍历每一服务实例,使用子类的apply进行校验过滤,返回符合的候选集合
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
if (loadBalancerKey == null ) {
return ImmutableList.copyOf (Iterables.filter (servers, this .getServerOnlyPredicate()));
} else {
List<Server> results = Lists.newArrayList ();
for (Server server: servers) {
if (this .apply(new PredicateKey(loadBalancerKey, server))) {
results.add(server);
}
}
return results;
}
}

// 轮询
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex .get();
int next = (current + 1) % modulo;
if (nextIndex .compareAndSet(current, next) && current < modulo)
return current;
}
}
// ...
}

public class ZoneAvoidancePredicate extends AbstractServerPredicate {

private volatile DynamicDoubleProperty triggeringLoad = new DynamicDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer.triggeringLoadPerServerThreshold" , 0.2d);
private volatile DynamicDoubleProperty triggeringBlackoutPercentage = new DynamicDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer.avoidZoneWithBlackoutPercetage" , 0.99999d);
private static final DynamicBooleanProperty ENABLED = DynamicPropertyFactory
.getInstance ().getBooleanProperty(
"niws.loadbalancer.zoneAvoidanceRule.enabled" , true );

public ZoneAvoidancePredicate(LoadBalancerStats lbStats,
IClientConfig clientConfig) {
super (lbStats, clientConfig);
initDynamicProperties(clientConfig);
}
private void initDynamicProperties(IClientConfig clientConfig) {
if (clientConfig != null ) {
triggeringLoad = DynamicPropertyFactory.getInstance ().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + clientConfig.getClientName() + ".triggeringLoadPerServerThreshold" , 0.2d);

triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance ().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + clientConfig.getClientName() + ".avoidZoneWithBlackoutPercetage" , 0.99999d);
}

}

@Override
public boolean apply(@Nullable PredicateKey input) {
if (!ENABLED .get()) {
return true ;
}
String serverZone = input.getServer().getZone();
if (serverZone == null ) {
return true ;
}
LoadBalancerStats lbStats = getLBStats();
if (lbStats == null ) {
return true ;
}
if (lbStats.getAvailableZones().size() <= 1) {
return true ;
}
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot (lbStats);
if (!zoneSnapshot.keySet().contains(serverZone)) {
return true ;
}
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones (zoneSnapshot, triggeringLoad .get(), triggeringBlackoutPercentage .get());
if (availableZones != null ) {
return availableZones.contains(input.getServer().getZone());
} else {
return false ;
}
}
}

public class AvailabilityPredicate extends AbstractServerPredicate {

private static final DynamicBooleanProperty CIRCUIT_BREAKER_FILTERING =
DynamicPropertyFactory.getInstance ().getBooleanProperty("niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped" , true );
private static final DynamicIntProperty ACTIVE_CONNECTIONS_LIMIT =
DynamicPropertyFactory.getInstance ().getIntProperty("niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit" , Integer.MAX_VALUE );
private ChainedDynamicProperty.IntProperty activeConnectionsLimit = new ChainedDynamicProperty.IntProperty(ACTIVE_CONNECTIONS_LIMIT );

public AvailabilityPredicate(LoadBalancerStats lbStats, IClientConfig clientConfig) {
super (lbStats, clientConfig);
initDynamicProperty(clientConfig);
}
private void initDynamicProperty(IClientConfig clientConfig) {
String id = "default" ;
if (clientConfig != null ) {
id = clientConfig.getClientName();
activeConnectionsLimit = new ChainedDynamicProperty.IntProperty(id + "." + clientConfig.getNameSpace() + ".ActiveConnectionsLimit" , ACTIVE_CONNECTIONS_LIMIT );
}
}

@Override
public boolean apply(@Nullable PredicateKey input) {
LoadBalancerStats stats = getLBStats();
if (stats == null ) {
return true ;
}
return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
}

private boolean shouldSkipServer(ServerStats stats) {
if ((CIRCUIT_BREAKER_FILTERING .get() && stats.isCircuitBreakerTripped())
|| stats.getActiveRequestsCount() >= activeConnectionsLimit .get()) {
return true ;
}
return false ;
}

}

3.6.2.2、getAvailableZones获取有效分区

getAvailableZones(Map<String, ZoneSnapshot> snapshot, double triggeringLoad, double triggeringBlackoutPercentage)

* 参数:

①Map<String, ZoneSnapshot> snapshot每一个分区的快照,是基于LoadBalancerStats获取的

对一个分区中的所有服务实例进行计算后封装为new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer)

  • instanceCount:当前服务数

  • circuitBreakerTrippedCount:当前滑动窗口内断路的服务数

  • activeConnectionsCount:当前滑动窗口内此分区所有服务实例的有效连接总数数

  • LoadPerServer = activeConnectionsCount / (instanceCount - circuitBreakerTrippedCount)--此值越大表示,此分区剩余可用连接越少,负载就越大

②triggeringLoad和triggeringBlackoutPercentage都是配置的阈值

triggeringLoadPerServerThreshold,默认为0.2

avoidZoneWithBlackoutPercetage,默认为0.99999d

* getAvailableZones流程

①获取Set<String> availableZones当前可用的候选的Zones集合,取Map<String, ZoneSnapshot> snapshot的keys

②获取配置值

triggeringLoadPerServerThreshold,默认为0.2

avoidZoneWithBlackoutPercetage,默认为0.99999d

③去除服务实例数为0的分区

④去除 loadPerServer小于0 或者 断路数/服务实例数>= avoidZoneWithBlackoutPercetage的分区

⑤计算LoadPerServer 最大的几个分区到worstZones集合,这个worstZones集合最大LoadPerServer 和最下LoadPerServer 相差必须小于0.000001d,

如果最大的LoadPerServer 小于triggeringLoadPerServerThreshold且上面步骤没有排除过任何一个分区不存在,那么就直接availableZones,而不用过滤worstZones

⑥对worstZones进行过滤,

获取在worstZones中所有分区的实例数总数,在此数范围内随机选择一个数,遍历worstZones,第一个实例数小于此数的分区为选中服务

从availableZones中去除此分区

⑦最终得到Set<String> availableZones

static Map<String, ZoneSnapshot> createSnapshot(LoadBalancerStats lbStats) {
Map<String, ZoneSnapshot> map = new HashMap<String, ZoneSnapshot>();
for (String zone : lbStats.getAvailableZones()) {
ZoneSnapshot snapshot = lbStats.getZoneSnapshot(zone);
map.put(zone, snapshot);
}
return map;
}

//LoadBalancerStats
public ZoneSnapshot getZoneSnapshot(List<? extends Server> servers) {
if (servers == null || servers.size() == 0) {
return new ZoneSnapshot();
}
int instanceCount = servers.size();
int activeConnectionsCount = 0;
int activeConnectionsCountOnAvailableServer = 0;
int circuitBreakerTrippedCount = 0;
double loadPerServer = 0;
long currentTime = System.currentTimeMillis ();
for (Server server: servers) {
ServerStats stat = getSingleServerStat(server);
if (stat.isCircuitBreakerTripped(currentTime)) {
circuitBreakerTrippedCount++;
} else {
activeConnectionsCountOnAvailableServer += stat.getActiveRequestsCount(currentTime);
}
activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
}
if (circuitBreakerTrippedCount == instanceCount) {
if (instanceCount > 0) {
// should be NaN, but may not be displayable on Epic
loadPerServer = -1;
}
} else {
loadPerServer = ((double ) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
}
return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
}

public static Set<String> getAvailableZones(
Map<String, ZoneSnapshot> snapshot, double triggeringLoad,
double triggeringBlackoutPercentage) {
if (snapshot.isEmpty()) {
return null ;
}
Set<String> availableZones = new HashSet<String>(snapshot.keySet());
if (availableZones.size() == 1) {
return availableZones;
}
Set<String> worstZones = new HashSet<String>();
double maxLoadPerServer = 0;
boolean limitedZoneAvailability = false ;

for (Map.Entry<String, ZoneSnapshot> zoneEntry : snapshot.entrySet()) {
String zone = zoneEntry.getKey();
ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
int instanceCount = zoneSnapshot.getInstanceCount();
if (instanceCount == 0) {
availableZones.remove(zone);
limitedZoneAvailability = true ;
} else {
double loadPerServer = zoneSnapshot.getLoadPerServer();
if (((double ) zoneSnapshot.getCircuitTrippedCount())
/ instanceCount >= triggeringBlackoutPercentage
|| loadPerServer < 0) {
availableZones.remove(zone);
limitedZoneAvailability = true ;
} else {
if (Math.abs (loadPerServer - maxLoadPerServer) < 0.000001d) {
// they are the same considering double calculation
// round error
worstZones.add(zone);
} else if (loadPerServer > maxLoadPerServer) {
maxLoadPerServer = loadPerServer;
worstZones.clear();
worstZones.add(zone);
}
}
}
}

if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
// zone override is not needed here
return availableZones;
}
String zoneToAvoid = randomChooseZone (snapshot, worstZones);
if (zoneToAvoid != null ) {
availableZones.remove(zoneToAvoid);
}
return availableZones;

}

static String randomChooseZone(Map<String, ZoneSnapshot> snapshot,
Set<String> chooseFrom) {
if (chooseFrom == null || chooseFrom.size() == 0) {
return null ;
}
String selectedZone = chooseFrom.iterator().next();
if (chooseFrom.size() == 1) {
return selectedZone;
}
int totalServerCount = 0;
for (String zone : chooseFrom) {
totalServerCount += snapshot.get(zone).getInstanceCount();
}
int index = random .nextInt(totalServerCount) + 1;
int sum = 0;
for (String zone : chooseFrom) {
sum += snapshot.get(zone).getInstanceCount();
if (index <= sum) {
selectedZone = zone;
break ;
}
}
return selectedZone;
}

3.7、ILoadBalancer总控组件

3.7.1、ILoadBalancer接口

接口作用是服务实例的增改查

public interface ILoadBalancer {

// 添加服务实例
public void addServers(List<Server> newServers);
// 选择一个服务实例
public Server chooseServer(Object key);
// LB这边把一个指定的服务标为下线
public void markServerDown(Server server);
@Deprecated
public List<Server> getServerList(boolean availableOnly);
// 只返回可用的服务实例
public List<Server> getReachableServers();
// 所有服务实例
public List<Server> getAllServers();
}

3.7.1.1、接口作用

* 是负载均衡器主类ILoadBalancer接口的一个实现,ILoadBalancer是面向客户调用的接口

该策略具备复制均衡的所有能力,因为封装了必要的几个组件

* 一个serviceId维度有一个自己的ILoadBalancer实现类

* 在RibbonClientConfiguration 中的默认配置为ZoneAwareLoadBalancer,从构造方法看,这个类封装所有相关的组件

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

①IClientConfig:Ribbon的客户端配置,对如下组件及其他信息的配置,默认为DefaultClientConfigImpl

②IRule:Ribbon的负载均衡策略,该策略能在多区域环境下选出最佳区域的实例进行访问,默认为ZoneAvoidanceRule

③IPing Ribbon:实例连通性检查策略,默认NoOpPing该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认所有的实例都是可用的

④ServerList<Server> :服务实例清单维护机制,默认为.ConfigurationBasedServerList

⑤ServerListFilter<Server>:服务实例清单过滤机制,默认为ZonePreferenceServerListFilter 该策略能够优先过滤出与请求调用方处于同一个区域的服务清单

⑥ServerListUpdater:进行动态服务列表的更新,默认为PollingServerListUpdater,这时一个定时拉取原始服务列表的策略

3.7.1.2、接口体系

①ILoadBalancer接口的,定义了service实例的增删查改

如addServers(List<Server> newServers)、chooseServer(Object key)等

②BaseLoadBlance,实现了整体的功能,如下

  • 抽象父类AbstractLoadBalancer,仅仅是定义了枚举ServerGroup:ALL,STATUS_UP,STATUS_NOT_UP

  • 两个List保存此服务的可用的所有的服务实例列表,以读写锁来处理线程安全

此serviceId的所有的服务实例:List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>());

此serviceId的上线的服务实例:List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>());

  • 通过IPing定时的对服务实例列表的可用性进行检查

  • 通过IRule实现ILoadBalancer接口chooseServer方法以选择指定策略的服务实例

  • 通过LoadBalancerStats记录了每个服务实例的状态ServerStats和每个区域的状态zoneStatsMap,

包括访问次数、响应时间、熔断(时间)控制等等,通过zone划分了服务实例

③LoadBalancerContext

  • 此类的作用类似一个记录工具类,所有方法都需要指定LoadBalancerStats的ServerStats参数

  • 穿插在请调用的整个流程,提供记录和更新服务状态ServerStats的功能

比如在打开请求时,会调用noteOpenConnectio(ServerStats serverStats)n方法,此方法会把对应的ServerStats的活跃请求指标activeRequestsCount + 1

④DynamicServerListLoadBalancer

  • 从指定的ServerList<T>实现类,如Eruka的DiscoveryEnabledNIWSServerList实现类,会获取所有服务列表,执行BaseLoadBlance父类的setServersList

就会写入BaseLoadBlance的两个List保存此服务的可用的所有的服务实例列表

  • 基于服务列表构建好LoadBalancerStats的两个以Zone划分的Map

Map<String, ZoneStats> zoneStatsMap ;

Map<String, List<? extends Server>> upServerListZoneMap ;

  • 会基于关联的ServerListUpdater进行动态服务列表的更新,默认为PollingServerListUpdater,这时一个定时拉取默认1秒)原始服务列表的策略

会从serverListImpl.getUpdatedListOfServers()获取最新的数据,使用ServerListFilter.getFilteredListOfServers(servers)过滤以下,调用setServersList会更新各种地表

BaseLoadBlance的allServerList 和upServerList

DynamicServerListLoadBalancer的Map<String, List<? extends Server>> upServerListZoneMap

ZoneAwareLoadBalancer的每个zone对应的BaseLoadBalancer的allServerList 和upServerList

拉取到最新的服务列表后,重写走上面④的流程

⑤ZoneAwareLoadBalancer

  • 为每一个分区创建一个BaseLoadBlance,即ConcurrentHashMap<String, BaseLoadBalancer> balancers

主要是BaseLoadBalancer类型,其内的

  • 重写chooseServer方法

如果只有一个分区,使用super.chooseServer(key);如果有多个分区,先通过LoadBalancerStats获取所有分区的状态快照,

使用ZoneAvoidanceRule.getAvailableZones计算出可用有效的分区

再使用ZoneAvoidanceRule.randomChooseZone随机选择一个分区,使用balancers 表对应BaseLoadBalancer的chooseServer选择最终的服务实例

  • 注:此分区过滤的功能最终基于ZoneAvoidanceRule进行选择的逻辑是部分重复的,那么这里为什么还需要进行重写

  • 用户这可以自定义的ZoneAwareLoadBalancer的rule,这样ZoneAwareLoadBalancer会帮我们先过滤好分区,然后选择自己设置的rule进行处理

这样就复用了分区的功能

例如可以使用WeightedResponseTimeRule以平均响应时间作为权重进行随机选择

3.7.2、BaseLoadBlance
3.7.2.1、提供整体的功能

实现了整体的功能,如下

* 在构造方法在对主要的成员属性进行初始化

  • config = clientConfig;

  • setRule(rule);

  • setPing(ping);

  • setLoadBalancerStats(stats); //一般会new LoadBalancerStats(clientConfig.getClientName());

  • 设置pingIntervalTime默认30s、maxTotalPingTime默认2

其中一个间隔pingIntervalTime的PingTask

* 维护存此服务的可用的所有的服务实例列表synchronizedList(new ArrayList<Server>()) List<Server> allServerList和upServerList,

  • 其中一个重要的方法setServersList(List<Server> lsrv)用于新增或者初始化服务实例列表,流程如下

①写锁加锁writeLock.lock

②遍历参数List<Server> lsr,添加到临时List allServers

③和原allServerList比较,如果个数不同,通知配置List<ServerListChangeListener> changeListeners监听器,触发serverListChanged(oldList, newList)方法

④是否与实例提前创建连接,在isEnablePrimingConnections为true时(默认为false),使用PrimeConnections对象

会为每一个服务实例创建执行一个异步Future<Boolean>会去访问实例服务器,如果成功值为true,否则为false

⑤复制到最新的服务列表allServerList

如果ping == null || ping.getClass().getName().equals(DummyPing.class.getName()),也直接赋值到upServerList

注:这里直接覆盖没有关系,因为server重写了equal方法: svc.getId().equals(this.getId());

⑥写锁解锁writeLock.unlock();

  • 线程安全保障

底表allServerList和upServerList是synchronizedList

获取allServerList和upServerList信息时,一般就是直接返回两个集合成员,并使用 Collections.unmodifiableList

setServersList以读写锁来处理线程安全,是为了与Pinger时获取待检查列表,进行安全控制

进一步,在DynamicServerListLoadBalancer使用的Map<String, List<? extends Server>> upServerListZoneMap在setServersList里会同步更新,获取是是直接访问upServerListZoneMap

* 通过IPing实现类定时的对服务实例列表的可用性进行检查

  • 默认始终返回true,对于Eureka的实现类为NIWSDiscoveryPing

  • 定时或者快速执行的PingTask任务为 new Pinger(pingStrategy).runPinger();,其中pingStrategy为SerialPingStrategy,流程如下

注:ping == null || ping.getClass().getName().equals(DummyPing.class.getName())时此任务不执行

①加锁的获取allServerList的快照集合

②使用pingStrategy的pingServers(快照集合),获取每一个实例的ping结果,封装为result\[\]

③用result\[\]最新的isAlive结果覆盖原allServerList的isAlive状态,其中刷选为true的还赋值到upServerList

* 通过IRule实现chooseServer方法以选择指定策略的服务实例

rule.choose(key);

* 保存对应的LoadBalancerStats

  • 此值可以是构造方法传递,如果没有传递使用IClientConfig进行默认创建。一般会new LoadBalancerStats(clientConfig.getClientName());

  • 记录了每个服务实例的状态ServerStats和每个区域的状态zoneStatsMap,包括访问次数、响应时间、熔断(时间)控制等等,

通过zone划分了服务实例

public class BaseLoadBalancer extends AbstractLoadBalancer implements PrimeConnections.PrimeConnectionListener, IClientConfigAware {

private final static IRule DEFAULT_RULE = new RoundRobinRule();
private final static SerialPingStrategy DEFAULT_PING_STRATEGY = new SerialPingStrategy();
private static final String DEFAULT_NAME = "default" ;
private static final String PREFIX = "LoadBalancer_" ;
protected IRule rule = DEFAULT_RULE ;
protected IPingStrategy pingStrategy = DEFAULT_PING_STRATEGY ;
protected IPing ping = null ;
protected String name = DEFAULT_NAME ;

protected volatile List<Server> allServerList = Collections.synchronizedList (new ArrayList<Server>());
protected volatile List<Server> upServerList = Collections.synchronizedList (new ArrayList<Server>());
protected ReadWriteLock allServerLock = new ReentrantReadWriteLock();
protected ReadWriteLock upServerLock = new ReentrantReadWriteLock();

protected Timer lbTimer = null ;
protected int pingIntervalSeconds = 10;
protected int maxTotalPingTimeSeconds = 5;
protected Comparator<Server> serverComparator = new ServerComparator();
protected AtomicBoolean pingInProgress = new AtomicBoolean(false );

protected LoadBalancerStats lbStats ;
private IClientConfig config ;

private volatile Counter counter = Monitors.newCounter("LoadBalancer_ChooseServer" );

private PrimeConnections primeConnections ;
private volatile boolean enablePrimingConnections = false ;

private List<ServerListChangeListener> changeListeners = new CopyOnWriteArrayList<ServerListChangeListener>();
private List<ServerStatusChangeListener> serverStatusListeners = new CopyOnWriteArrayList<ServerStatusChangeListener>();

// 配置默认组件,进行 ping 都是任务的启动
public BaseLoadBalancer() {
this .name = DEFAULT_NAME ;
this .ping = null ;
setRule(DEFAULT_RULE );
setupPingTask();
lbStats = new LoadBalancerStats(DEFAULT_NAME );
}
// 其他构造器,略
public BaseLoadBalancer(IClientConfig config) {
initWithNiwsConfig(config);
}
public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
}
// 根据配置 IClientConfig 类进行构造
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);
setLoadBalancerStats(stats);
rule.setLoadBalancer(this );
if (ping instanceof AbstractLoadBalancerPing) {
((AbstractLoadBalancerPing) ping).setLoadBalancer(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();

}

//setter/setter
public void addServerListChangeListener(ServerListChangeListener listener) {
changeListeners .add(listener);
}
public void removeServerListChangeListener(ServerListChangeListener listener) {
changeListeners .remove(listener);
}
public void addServerStatusChangeListener(ServerStatusChangeListener listener) {serverStatusListeners .add(listener);}
public void removeServerStatusChangeListener(ServerStatusChangeListener listener) {serverStatusListeners .remove(listener); }
public IClientConfig getClientConfig() {
return config ;
}

void setupPingTask() {
//Iping 为空或者为 DummyPing 实现类,可跳过
if (canSkipPing()) {return ;}
if (lbTimer != null ) {
lbTimer .cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name , true );
lbTimer .schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
// 手动触发一下 PingTask
forceQuickPing();
}

// 添加服务
@Override
public void addServers(List<Server> newServers) {
if (newServers != null && newServers.size() > 0) {
try {
ArrayList<Server> newList = new ArrayList<Server>();
newList.addAll(allServerList );
newList.addAll(newServers);
setServersList(newList);
} catch (Exception e) { }
}
}
// 使用写锁,锁住 allServerLock
public void setServersList(List lsrv) {
Lock writeLock = allServerLock .writeLock();
ArrayList<Server> newServers = new ArrayList<Server>();
// 添加写锁
writeLock.lock();
try {
ArrayList<Server> allServers = new ArrayList<Server>();
for (Object server : lsrv) {
if (server == null ) {continue ;}
if (server instanceof String) {server = new Server((String) server);}
if (server instanceof Server) {
allServers.add((Server) server);
}
else {throw new IllegalArgumentException();
}
}

// 触发器处理
boolean listChanged = false ;
if (!allServerList .equals(allServers)) {
listChanged = true ;
if (changeListeners != null && changeListeners .size() > 0) {
List<Server> oldList = ImmutableList.copyOf (allServerList );
List<Server> newList = ImmutableList.copyOf (allServers);
for (ServerListChangeListener l: changeListeners ) {
try {
l.serverListChanged(oldList, newList);
} catch (Exception e) {}
}
}
}
// 是否提前建立连接
if (isEnablePrimingConnections()) {
for (Server server : allServers) {
if (!allServerList .contains(server)) {
server.setReadyToServe(false );
newServers.add((Server) server);
}
}
if (primeConnections != null ) {
primeConnections .primeConnectionsAsync(newServers, this );
}
}
// 复制最新的服务列表
allServerList = allServers;
if (canSkipPing()) {
for (Server s : allServerList ) {
s.setAlive(true );
}
upServerList = allServerList ;
} else if (listChanged) {
forceQuickPing();
}
} finally {
writeLock.unlock();
}
}

//PingTask 任务
class PingTask extends TimerTask {
public void run() {
try {
new Pinger(pingStrategy ).runPinger();
} catch (Exception e) { }
}
}
//
class Pinger {
private final IPingStrategy pingerStrategy ;
public Pinger(IPingStrategy pingerStrategy) {this .pingerStrategy = pingerStrategy;}
public void runPinger() throws Exception {
// 幂等处理
if (!pingInProgress .compareAndSet(false , true )) { return ; }
// we are "in" - we get to Ping
Server\[\] allServers = null ;
boolean \[\] results = null ;
Lock allLock = null ;
Lock upLock = null ;
try {
// 加把读锁,获取当前的服务列表的数组快照 allServers ,接着就释放锁了
allLock = allServerLock .readLock();
allLock.lock();
allServers = allServerList .toArray(new Server**allServerList** .size());
allLock.unlock();

// 使用指定的 ping 策略对 allServers 里的每一个服务进行 ping
int numCandidates = allServers.length ;
// 默认为 SerialPingStrategy ,会遍历服务,调用 IPing 实现类 ping Resultsi = ping.isAlive(serversi);
// 默认的 IPing 实现类为 NoOpPing ,始终返回 true
results = pingerStrategy .pingServers(ping , allServers);

final List<Server> newUpList = new ArrayList<Server>();
final List<Server> changedServers = new ArrayList<Server>();

// 根据 ping 的结果形成新的 newUpList ,使用读写多进行 upServerList 复制
for (int i = 0; i < numCandidates; i++) {
boolean isAlive = resultsi;
Server svr = allServersi;
boolean oldIsAlive = svr.isAlive();
svr.setAlive(isAlive);
if (oldIsAlive != isAlive) {changedServers.add(svr); }
if (isAlive) {newUpList.add(svr);}
}
upLock = upServerLock .writeLock();
upLock.lock();
upServerList = newUpList;
upLock.unlock();
notifyServerStatusChangeListener(changedServers);
} finally {
pingInProgress .set(false );
}
}
}

private final Counter createCounter() {
return Monitors.newCounter("LoadBalancer_ChooseServer" );
}

// 基于 rule 实现 chooseServer
public Server chooseServer(Object key) {
if (counter == null ) {
counter = createCounter();
}
counter .increment();
if (rule == null ) {
return null ;
} else {
try {
return rule .choose(key);
} catch (Exception e) {return null ;}
}
}

public void markServerDown(Server server) {
if (server == null || !server.isAlive()) {return ;}
server.setAlive(false );
notifyServerStatusChangeListener(singleton (server));
}

}

3.7.2.2、LoadBalancerStats

负载均衡LB需要依赖这些统计信息做为判断的策略,负载均衡器的统计类主要是LoadBalancerStats,一个serverId下所有服务实例对应一个LoadBalancerStats。

其内部持有:

* LoadingCache<Server, ServerStats> serverStatsCache

①ServerStats对每个Server的运行情况做了相关统计如:平均响应时间、累计失败数、熔断(时间)控制等

②ServerStats的增删改查的方法

③基于ServerStats实现的方法,如noteResponseTime方法实现为ServerStatsnoteResponseTime(msecs),记录响应时间

* Map<String, ZoneStats> zoneStatsMap和Map<String, List<? extends Server>> upServerListZoneMap

①zoneStatsMap是在在每一个Servrt基础上又进行区域zone的划分,ZoneStats保存着每一个zone的状态(主要用于记录zone名称)

②upServerListZoneMap直接就是以zone的划分Server

注:子类DynamicServerListLoadBalancer重写setServersList会更新这个两个map,即在实例列表初始化或者定时拉取的时候会更新

* 基于ServerStats获取一些报表:

①获取一个区域快照getZoneSnapshot(List<? extends Server> servers)

  • 对一个分区中的所有服务实例进行计算后封装为new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer)

  • instanceCount:当前服务数

  • circuitBreakerTrippedCount:当前滑动窗口内断路的服务实例数

  • activeConnectionsCount:当前滑动窗口内此分区所有服务实例的有效连接总数数

  • LoadPerServer = activeConnectionsCount / (instanceCount - circuitBreakerTrippedCount)

new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer)

②略

public class LoadBalancerStats implements IClientConfigAware {

private static final String PREFIX = "LBStats_" ;

String name ;

// 区域状态表, ZoneStats 里面封装了 zone 名称和一个计数器
volatile Map<String, ZoneStats> zoneStatsMap = new ConcurrentHashMap<String, ZoneStats>();
// 区域名称和服务列表的映射
volatile Map<String, List<? extends Server>> upServerListZoneMap = new ConcurrentHashMap<String, List<? extends Server>>();

//serverStats 断路器相关
private volatile CachedDynamicIntProperty connectionFailureThreshold ;
private volatile CachedDynamicIntProperty circuitTrippedTimeoutFactor ;
private volatile CachedDynamicIntProperty maxCircuitTrippedTimeout ;

//serverStats
private static final DynamicIntProperty SERVERSTATS_EXPIRE_MINUTES =
DynamicPropertyFactory.getInstance ().getIntProperty("niws.loadbalancer.serverStats.expire.minutes" , 30);
private final LoadingCache<Server, ServerStats> serverStatsCache =
CacheBuilder.newBuilder ()
.expireAfterAccess(SERVERSTATS_EXPIRE_MINUTES .get(), TimeUnit.MINUTES )
.removalListener(new RemovalListener<Server, ServerStats>() {
@Override
public void onRemoval(RemovalNotification<Server, ServerStats> notification) {
notification.getValue().close();
}
})
.build(
new CacheLoader<Server, ServerStats>() {
public ServerStats load(Server server) {
return createServerStats(server);
}
});

protected ServerStats createServerStats(Server server) {
ServerStats ss = new ServerStats(this );
ss.setBufferSize(1000);
ss.setPublishInterval(1000);
ss.initialize(server);
return ss;
}
public LoadBalancerStats(){
zoneStatsMap = new ConcurrentHashMap<String, ZoneStats>();
upServerListZoneMap = new ConcurrentHashMap<String, List<? extends Server>>();
}
public LoadBalancerStats(String name){
this ();
this .name = name;
Monitors.registerObject(name, this );
}

//getter/setter

@Override
public void initWithNiwsConfig(IClientConfig clientConfig)
{
this .name = clientConfig.getClientName();
Monitors.registerObject(name , this );
}

// 更新 serverStats
public void updateServerList(List<Server> servers){
for (Server s: servers){
addServer(s);
}
}
public void addServer(Server server) {
try {
serverStatsCache .get(server);
} catch (ExecutionException e) {
ServerStats stats = createServerStats(server);
serverStatsCache .asMap().putIfAbsent(server, stats);
}
}
protected ServerStats getServerStats(Server server) {
try {
return serverStatsCache .get(server);
} catch (ExecutionException e) {
ServerStats stats = createServerStats(server);
serverStatsCache .asMap().putIfAbsent(server, stats);
return serverStatsCache .asMap().get(server);
}
}

// 基于 ServerStats 内暴露的方法
public void noteResponseTime(Server server, double msecs){
ServerStats ss = getServerStats(server);
ss.noteResponseTime(msecs);
}
public void incrementActiveRequestsCount(Server server) {
ServerStats ss = getServerStats(server);
ss.incrementActiveRequestsCount();
}
public boolean isCircuitBreakerTripped(Server server) {
ServerStats ss = getServerStats(server);
return ss.isCircuitBreakerTripped();
}
//

//zoneStatsMap 表相关方法
private ZoneStats getZoneStats(String zone) {
zone = zone.toLowerCase();
ZoneStats zs = zoneStatsMap .get(zone);
if (zs == null ){
zoneStatsMap .put(zone, new ZoneStats(this .getName(), zone, this ));
zs = zoneStatsMap .get(zone);
}
return zs;
}
public void updateZoneServerMapping(Map<String, List<Server>> map) {
upServerListZoneMap = new ConcurrentHashMap<String, List<? extends Server>>(map);
for (String zone: map.keySet()) {
getZoneStats(zone);
}
}
public int getActiveRequestsCount(String zone) {
return getZoneSnapshot(zone).getActiveRequestsCount();
}
public double getActiveRequestsPerServer(String zone) {
return getZoneSnapshot(zone).getLoadPerServer();
}
// 获取某个地区的 zoneStatsMap 快照
public ZoneSnapshot getZoneSnapshot(String zone) {
if (zone == null ) {return new ZoneSnapshot();}
zone = zone.toLowerCase();
List<? extends Server> currentList = upServerListZoneMap .get(zone);
return getZoneSnapshot(currentList);
}
// 根据每个服务的断路、可用状态,计算了 loadPerServer = 当前滑动窗口内有效连接数(包括了当前时刻状态为断路的) / ( 当前服务数 - 当前滑动窗口内有效连接数且服务没有断路 )
public ZoneSnapshot getZoneSnapshot(List<? extends Server> servers) {
if (servers == null || servers.size() == 0) {
return new ZoneSnapshot();
}
int instanceCount = servers.size();
int activeConnectionsCount = 0;
int activeConnectionsCountOnAvailableServer = 0;
int circuitBreakerTrippedCount = 0;
double loadPerServer = 0;
long currentTime = System.currentTimeMillis ();
for (Server server: servers) {
ServerStats stat = getSingleServerStat(server);
if (stat.isCircuitBreakerTripped(currentTime)) {
circuitBreakerTrippedCount++;
} else {
activeConnectionsCountOnAvailableServer += stat.getActiveRequestsCount(currentTime);
}
activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
}
if (circuitBreakerTrippedCount == instanceCount) {
if (instanceCount > 0) {
// should be NaN, but may not be displayable on Epic
loadPerServer = -1;
}
} else {
loadPerServer = ((double ) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
}
return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
}
// 获取指定的 zone 内的累计请求数
public long getMeasuredZoneHits(String zone) {
if (zone == null ) {
return 0;
}
zone = zone.toLowerCase();
long count = 0;
List<? extends Server> currentList = upServerListZoneMap .get(zone);
if (currentList == null ) {
return 0;
}
for (Server server: currentList) {
ServerStats stat = getSingleServerStat(server);
count += stat.getMeasuredRequestsCount();
}
return count;
}
// 计算指定 zone ( 活跃请求数量(周期) + 断路数 ) / 服务数
public int getCongestionRatePercentage(String zone) {
if (zone == null ) {
return 0;
}
zone = zone.toLowerCase();
List<? extends Server> currentList = upServerListZoneMap .get(zone);
if (currentList == null || currentList.size() == 0) {
return 0;
}
int serverCount = currentList.size();
int activeConnectionsCount = 0;
int circuitBreakerTrippedCount = 0;
for (Server server: currentList) {
ServerStats stat = getSingleServerStat(server);
activeConnectionsCount += stat.getActiveRequestsCount();
if (stat.isCircuitBreakerTripped()) {
circuitBreakerTrippedCount++;
}
}
return (int ) ((activeConnectionsCount + circuitBreakerTrippedCount) * 100L / serverCount);
}

}

3.7.2.3、ServerStats
  1. 用于记录一个Server实例的统计数据/属性

CircuitBreaker断路器相关属性

*connectionFailureThreshold:连接失败阈值,默认值3(超过就熔断)

默认值配置:niws.loadbalancer.default.connectionFailureCountThreshold此key指定;个性化配置:"niws.loadbalancer." + name + ".connectionFailureCountThreshold"

* circuitTrippedTimeoutFactor:断路器超时因子,默认值10s。

默认值配置: niws.loadbalancer.default.circuitTripTimeoutFactorSeconds;个性化配置:"niws.loadbalancer." + name + ".circuitTripTimeoutFactorSeconds"

* maxCircuitTrippedTimeout:断路器最大超时秒数(默认使用超时因子计算出来),默认值是30s。

默认值配置:niws.loadbalancer.default.circuitTripMaxTimeoutSeconds;个性化配置:"niws.loadbalancer." + name + ".circuitTripMaxTimeoutSeconds"

* successiveConnectionFailureCount:连续(successive)请求异常数量(这个连续发生在Retry重试期间)。

在重试期间,但凡有一次成功了,就会把此参数置为0(失败的话此参数就一直加)

说明:只有通常callErrorHandler.isCircuitTrippingException(e)方法判定才会算作失败,才会+1,

默认情况下只有SocketException/SocketTimeoutException这两种异常才算失败哦~

* totalCircuitBreakerBlackOutPeriod:断路器断电总时长(连续失败>=3次,增加20~30秒。具体增加多少秒,后面有计算逻辑)。

* totalRequests:总请求数量。每次请求结束/错误时就会+1。

* activeRequestsCount:活跃请求数量(正在请求的数量,它能反应该Server的负载、压力)。

但凡只要开始执行Sever了,就+1;但凡只要请求完成了/出错了,就-1;注意:它有时间窗口的概念,后面讲具体逻辑

* lastActiveRequestsCountChangeTimestamp:简单的说就是activeRequestsCount的值最后变化的时间戳

* activeRequestsCountTimeout:活跃请求数量的统计周期,如果lastActiveRequestsCountChangeTimestamp距离now超过了此周期,activeRequestsCount会重新计算

* openConnectionsCount:暂无任何使用处,可忽略。

* lastConnectionFailedTimestamp:最后一次失败的时间戳。至于什么叫失败,参考successiveConnectionFailureCount对失败的判断逻辑

* lastAccessedTimestamp:最后访问时间戳。和lastActiveRequestsCountChangeTimestamp的区别是,它增/减都update一下,

而lastAccessedTimestamp只有在增的时候才会update一下。

* firstConnectionTimestamp:首次连接时间戳,只会记录首次请求进来时的时间。

* failureCountSlidingWindowInterval:失败次数统计时间窗。默认值1000ms

* serverFailureCounts:上一秒失败次数(上一秒是因为failureCountSlidingWindowInterval默认1000ms),

successiveConnectionFailureCount增它就增,只不过它有时间窗口(1s)

* requestCountInWindow:一个窗口期内的请求总数,窗口期默认为5分钟(300秒),activeRequestsCount增它就增,只不过它有时间窗口(300s)

响应时间的累计预计

* dataDist:它是一个DataAccumulator,数据累加器。内部维护这一个样本区,是一个数值类型的数组,每次调用noteValue都会在数组依次填充记录,数组下标++,

如果满了会从头填充,之前的数据不会清空

* publisher:定时publish发布数据,默认1分钟发布一次,会计算数组中数据10,20...,90...,99.5百分比数据的统计值。统计之后会清空数据

比如数组buf大小为10,数为 1,2,3,4,5, null,null,null,null,null,开始统计,表示在最近的1分钟内,只记录了5个数据 , 90%的统计值为:5 * 90% = 4.5 ,取low = 4 , high = 5

返回 bufiLow + (index - iLow) * (bufiHigh - bufiLow) = 2.5

* responseTimeDist:它是个Distribution类型,因为它仅仅只需要持续累加数据,然后提供最大最小值、平均值的访问而已

dataDist和responseTimeDist统一通过noteResponseTime(double msecs)来记录每个请求的响应时间,dataDist按照时间窗口统计,responseTimeDist一直累加。

(2)CircuitBreaker断路器的原理

* 本处的断路器解释

①当有某个服务存在多个实例时,在请求的过程中,负载均衡器会统计每次请求的情况(请求响应时间,是否发生网络异常等),当出现了请求出现累计重试时,负载均衡器会标识当前服务实例,

设置当前服务实例的断 路的时间区间,在此区间内,当请求过来时,负载均衡器会将此服务实例从可用服务实例列表中暂时剔除(其实就是暂时忽略此Server),优先选择其他服务实例。

②该断路器和Hystrix无任何关系,无任何关系,无任何关系。它是ServerStats内部维护的一套熔断机制,体现在如下getCircuitBreakerTimeout方法上

该断路器规则非常简单,开启与否完全由连续失败来决定,而是否算失败由RetryHandler#isCircuitTrippingException来决定,默认它只认为SocketException/SocketTimeoutException(或者其子类异常)属于该种类型的异常

任何业务异常(如NPE)和此断路器没有半毛钱关系。本断路器断的是Server,也就是远程服务器, Hystrix断路器断的是Client,也就是客户端的调用

* 熔断结束时间的计算

目前断路器统计失败是靠连续失败次数去判断断路逻辑的。此方法逻辑可总结如下:

①若连续失败次数还小于阈值(默认3次),那么就不用断路。返回0即可,否则执行计算要断开多久的逻辑

连续(successive)请求异常数量(这个连续发生在Retry重试期间),在重试期间,但凡有一次成功了,就会把此参数置为0(失败的话此参数就一直加)

说明:只有通常callErrorHandler.isCircuitTrippingException(e)方法判定才会算作失败,才会+1,默认情况下只有SocketException/SocketTimeoutException这两种异常

②计算失败基数,最大不能超过16(就算你连续失败100次,此基数也是16)

③根据超时因子circuitTrippedTimeoutFactor(默认是10)计算出时间值blackOutSeconds,该值不能大于上限connectionFailureCircuitTimeout(默认30s),

也就是说保证了断路器最长不能打开超过30s

④断路结束计算公式:(连续失败次数 - 失败基数) * 2 * 超时因子

// 看看该断路器到哪个时间点(关闭)的时刻时间戳 在这个时间段内,该服务是断路的,在Server被熔断期间,负载均衡器都将忽略此Server。
// 比如断路器要从 0 点开 30s ,那么返回值就是 00:00:30s 这个时间戳呗 = 最后一次连接失败时间 + 断路周期
private long getCircuitBreakerTimeout() {
long blackOutPeriod = getCircuitBreakerBlackoutPeriod();
if (blackOutPeriod <= 0) {
return 0;
}
return lastConnectionFailedTimestamp + blackOutPeriod;
}

// 返回需要中断的持续时间(毫秒值) 如果大于0表示需要断路
private long getCircuitBreakerBlackoutPeriod() {
int failureCount = successiveConnectionFailureCount.get();
int threshold = connectionFailureThreshold.get();
if (failureCount < threshold) {
return 0;
}
int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
blackOutSeconds = maxCircuitTrippedTimeout.get();
}
return blackOutSeconds * 1000L;
}

* 断路器如何闭合?

倘若断路器打开了,它如何恢复呢?有如下3种情形它会恢复到正常状态:

①不是连续失败了,也就是成功了一次,那么successiveConnectionFailureCount就会立马归0,所以熔断器就闭合了

②即使请求失败了,但是并非是断路器类异常,即不是RetryHandler#isCircuitTrippingException这种类型的异常时(比如RuntimeException就不是这种类型的异常),那就也不算连续失败,所以也就闭合了

③到时间了,断路器自然就自动闭合了

(3)有窗口性质的统计

* serverFailureCounts:上一秒失败次数(上一秒是因为failureCountSlidingWindowInterval默认1000ms),

successiveConnectionFailureCount增它就增,只不过它有时间窗口(1s)

* requestCountInWindow:一个窗口期内的请求总数,窗口期默认为5分钟(300秒),activeRequestsCount增它就增,只不过它有时间窗口(300s)

* 他们都是MeasuredRate类型的

内部使用一个_threshold表示当前窗口的结尾边界,= System.currentTimeMillis() + sampleInterval;

在操作统计值时,如果此时还处于窗口内就累加,否就重置

public class MeasuredRate {
private final AtomicLong _lastBucket = new AtomicLong(0);
private final AtomicLong _currentBucket = new AtomicLong(0);
private final long _sampleInterval ;
private volatile long _threshold ;

public MeasuredRate(long sampleInterval){
_sampleInterval = sampleInterval;
_threshold = System.currentTimeMillis () + sampleInterval;
}

public long getCount() {
checkAndResetWindow();
return _lastBucket .get();
}

public long getCurrentCount() {
checkAndResetWindow();
return _currentBucket .get();
}

public void increment() {
checkAndResetWindow();
_currentBucket .incrementAndGet();
}

private void checkAndResetWindow() {
long now = System.currentTimeMillis ();
if (_threshold < now) {
_lastBucket .set(_currentBucket .get());
_currentBucket .set(0);
_threshold = now + _sampleInterval ;
}
}

}

3.7.3、DynamicServerListLoadBalancer

主要是对服务列表的处理,基于几个组件,重写父类的方法

* 在构造器后新增restOfInit处理,以初始化当前ServerId的服务实例

①调用serverListUpdater.start,为服务列表的更新器,默认为PollingServerListUpdater会定时(默认30s)调用updateListOfServers方法,

而Eureka对应的为EurekaNotificationServerListUpdater,为在eurekaClient.registerEventListener(updateListener),

只有监听到CacheRefreshedEvent事件才会调用updateListOfServers方法

②调用serverListImpl.getUpdatedListOfServers(),再使用filter过滤下,

③再调用setServersList方法,更新好父类的服务列表allServerList,再处理LoadBalancerStats的serverStatsCache和upServerListZoneMap

  • 每一个Server都封装为ServerStats在存于父类BaseLoadBlance中的LoadingCache<Server, ServerStats> serverStatsCache

  • 把所有Server按照zone进行划分存于父类BaseLoadBlance中的 Map<String, List<? extends Server> upServerListZoneMap

  • 把每一个zone封装为ZoneStats在存于父类BaseLoadBlance中的Map<String, ZoneStats> zoneStatsMap

ZoneStats只是对zone的简单封装,存有LoadBalancerStats loadBalancerStats和 String zone;

public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
private static final Logger LOGGER = LoggerFactory.getLogger (DynamicServerListLoadBalancer.class );

boolean isSecure = false ;
boolean useTunnel = false ;
protected AtomicBoolean serverListUpdateInProgress = new AtomicBoolean(false );

// 基于 serverListImpl 获取服务列表
volatile ServerList<T> serverListImpl ;
volatile ServerListFilter<T> filter ;
// 处理 ServerList 的个更新器,默认为 PollingServerListUpdater 会都是调用 updateListOfServers 方法,
// Eureka 对应的为 EurekaNotificationServerListUpdater ,为在 eurekaClient.registerEventListener(updateListener)
// 只有监听到 CacheRefreshedEvent 事件才会调用 updateListOfServers 方法
protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
@Override
public void doUpdate() {
updateListOfServers();
}
};
protected volatile ServerListUpdater serverListUpdater ;

public DynamicServerListLoadBalancer() {
super ();
}

// 构造器,初始化组件之后,调用 restOfInit
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 DynamicServerListLoadBalancer(IClientConfig clientConfig) {
initWithNiwsConfig(clientConfig);
}
// 基于配置类初始化默认的组件实现
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
try {
super .initWithNiwsConfig(clientConfig);
ServerList<T> niwsServerListImpl = (ServerList<T>) ClientFactory.instantiateInstanceWithClientConfig (niwsServerListClassName, clientConfig);
this .serverListImpl = niwsServerListImpl;
//
restOfInit(clientConfig);
} catch (Exception e) {throw new RuntimeException();}
}

void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this .isEnablePrimingConnections();
this .setEnablePrimingConnections(false );
// 调用 serverListUpdater.start
enableAndInitLearnNewServersFeature();
// 调用 serverListImpl.getUpdatedListOfServers() ,再使用 filter 过滤下,再调用 setServersList
updateListOfServers();
if (primeConnection && this .getPrimeConnections() != null ) {
this .getPrimeConnections().primeConnections(getReachableServers());
}
this .setEnablePrimingConnections(primeConnection);
}

@Override
public void setServersList(List lsrv) {
// 更新好父类的服务列表 allServerList ,再处理 LoadBalancerStats serverStatsCache upServerListZoneMap
super .setServersList(lsrv);
List<T> serverList = (List<T>) lsrv;
Map<String, List<Server>> serversInZones = new HashMap<String, List<Server>>();
for (Server server : serverList) {
// make sure ServerStats is created to avoid creating them on hot
// path
getLoadBalancerStats().getSingleServerStat(server);
String zone = server.getZone();
if (zone != null ) {
zone = zone.toLowerCase();
List<Server> servers = serversInZones.get(zone);
if (servers == null ) {
servers = new ArrayList<Server>();
serversInZones.put(zone, servers);
}
servers.add(server);
}
}
//getLoadBalancerStats().updateZoneServerMapping(zoneServersMap)
setServerListForZones(serversInZones);
}

protected void setServerListForZones(Map<String, List<Server>> zoneServersMap) {
getLoadBalancerStats().updateZoneServerMapping(zoneServersMap);
}

public void stopServerListRefreshing() {
if (serverListUpdater != null ) {
serverListUpdater .stop();
}
}

@Override
public void shutdown() {
super .shutdown();
stopServerListRefreshing();
}

//
}

3.7.4、ZoneAwareLoadBalancer重写chooseServer

* 为每一个分区创建一个BaseLoadBlance,即ConcurrentHashMap<String, BaseLoadBalancer> balancers

* 重写chooseServer,如果只有一个分区,使用super.chooseServer(key);如果有多个分区,先通过LoadBalancerStats获取所有分区的状态快照,

使用ZoneAvoidanceRule.getAvailableZones计算出可用有效的分区集合availableZones(具体逻辑见上ZoneAvoidanceRule实现类)

* 再使用ZoneAvoidanceRule.randomChooseZone随机选择一个分区,使用其ZoneAvoidanceRule的chooseServer选择最终的服务实例

即最终使用ZoneAwareLoadBalancer配置的rule的choose方法进行选定,默认就是ZoneAvoidanceRule的choose方法

注:默认ZoneAvoidanceRule也会进行分区处理,当时一个BaseLoadBlance里没有upServerListZoneMap分区概念,其内的upServerList都是同一个分区的

所以会第二个AvailabilityPredicate校验,会筛选不处于断路状态且活跃请求数小于niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit配置,默认为int最大值

对最终结果List<Server> eligible进行轮询即可

* 为什么给每一个每一个分区创建一个BaseLoadBlance

  • 用户这可以自定义的ZoneAwareLoadBalancer的rule,这样ZoneAwareLoadBalancer会帮我们先过滤好分区,然后选择自己设置的rule进行处理

这样就复用了分区的功能

例如可以使用WeightedResponseTimeRule以平均响应时间作为权重进行随机选择

public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {

private ConcurrentHashMap<String, com.netflix.loadbalancer.BaseLoadBalancer> balancers = new ConcurrentHashMap<String, com.netflix.loadbalancer.BaseLoadBalancer>();

private static final Logger logger = LoggerFactory.getLogger (ZoneAwareLoadBalancer.class );

private volatile DynamicDoubleProperty triggeringLoad ;

private volatile DynamicDoubleProperty triggeringBlackoutPercentage ;

private static final DynamicBooleanProperty ENABLED = DynamicPropertyFactory.getInstance ().getBooleanProperty("ZoneAwareNIWSDiscoveryLoadBalancer.enabled" , true );

void setUpServerList(List<Server> upServerList) {
this .upServerList = upServerList;
}

public ZoneAwareLoadBalancer() {
super ();
}

@Deprecated
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter) {
super (clientConfig, rule, ping, serverList, filter);
}

@Override
public Server chooseServer(Object key) {
if (!ENABLED .get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
return super .chooseServer(key);
}
Server server = null ;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
// 调用 LoadBalancerStats 每一个分区的状态快照 ZoneSnapshot ZoneSnapshot 包括了此分区的可用连接,断路数的相关数据
//loadPerServer = ((double) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
// = 当前滑动窗口内有效连接数(包括了当前时刻状态为断路的) / ( 当前服务数 - 当前滑动窗口内有效连接数且服务没有断路 )
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot (lbStats);
if (triggeringLoad == null ) {
triggeringLoad = DynamicPropertyFactory.getInstance ().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this .getName() + ".triggeringLoadPerServerThreshold" , 0.2d);
}
if (triggeringBlackoutPercentage == null ) {
triggeringBlackoutPercentage = DynamicPropertyFactory.getInstance ().getDoubleProperty(
"ZoneAwareNIWSDiscoveryLoadBalancer." + this .getName() + ".avoidZoneWithBlackoutPercetage" , 0.99999d);
}
// 选择有效的分区,再随机选择一个分区 zone ,使用 zoneLoadBalancer.chooseServer(key) 选择一个服务实例
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones (zoneSnapshot, triggeringLoad .get(), triggeringBlackoutPercentage .get());
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
String zone = ZoneAvoidanceRule.randomChooseZone (zoneSnapshot, availableZones);
if (zone != null ) {
// balancers 找到分区对应的 BaseLoadBalancer ,没有会创建,在基于默认的 ZoneAvoidanceRule 寻找实例
com.netflix.loadbalancer.BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
server = zoneLoadBalancer.chooseServer(key);
}
}
} catch (Exception e) {
logger .error("Error choosing server using zone aware logic for load balancer={}" , name , e);
}
if (server != null ) {
return server;
} else {
logger .debug("Zone avoidance logic is not invoked." );
return super .chooseServer(key);
}
}

@VisibleForTesting
com.netflix.loadbalancer.BaseLoadBalancer getLoadBalancer(String zone) {
zone = zone.toLowerCase();
com.netflix.loadbalancer.BaseLoadBalancer loadBalancer = balancers .get(zone);
if (loadBalancer == null ) {
IRule rule = cloneRule(this .getRule());
loadBalancer = new com.netflix.loadbalancer.BaseLoadBalancer(this .getName() + "_" + zone, rule, this .getLoadBalancerStats());
BaseLoadBalancer prev = balancers .putIfAbsent(zone, loadBalancer);
if (prev != null ) {
loadBalancer = prev;
}
}
return loadBalancer;
}

private IRule cloneRule(IRule toClone) {
IRule rule;
if (toClone == null ) {
rule = new AvailabilityFilteringRule();
} else {
String ruleClass = toClone.getClass().getName();
try {
rule = (IRule) ClientFactory.instantiateInstanceWithClientConfig (ruleClass, this .getClientConfig());
} catch (Exception e) {
throw new RuntimeException("Unexpected exception creating rule for ZoneAwareLoadBalancer" , e);
}
}
return rule;
}

@Override
public void setRule(IRule rule) {
super .setRule(rule);
if (balancers != null ) {
for (String zone: balancers .keySet()) {
balancers .get(zone).setRule(cloneRule(rule));
}
}
}
}

/ / ZoneAvoidanceRule
public class ZoneAvoidanceRule extends PredicateBasedRule {

private static final Random random = new Random();

private CompositePredicate compositePredicate ;

public ZoneAvoidanceRule() {
super ();
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this );
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this );
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}

private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
return CompositePredicate.withPredicates (p1, p2)
.addFallbackPredicate(p2)
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue ())
.build();

}

@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this , clientConfig);
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this , clientConfig);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}

static Map<String, ZoneSnapshot> createSnapshot(LoadBalancerStats lbStats) {
Map<String, ZoneSnapshot> map = new HashMap<String, ZoneSnapshot>();
for (String zone : lbStats.getAvailableZones()) {
ZoneSnapshot snapshot = lbStats.getZoneSnapshot(zone);
map.put(zone, snapshot);
}
return map;
}

static String randomChooseZone(Map<String, ZoneSnapshot> snapshot,
Set<String> chooseFrom) {
if (chooseFrom == null || chooseFrom.size() == 0) {
return null ;
}
String selectedZone = chooseFrom.iterator().next();
if (chooseFrom.size() == 1) {
return selectedZone;
}
int totalServerCount = 0;
for (String zone : chooseFrom) {
totalServerCount += snapshot.get(zone).getInstanceCount();
}
int index = random .nextInt(totalServerCount) + 1;
int sum = 0;
for (String zone : chooseFrom) {
sum += snapshot.get(zone).getInstanceCount();
if (index <= sum) {
selectedZone = zone;
break ;
}
}
return selectedZone;
}

public static Set<String> getAvailableZones(
Map<String, ZoneSnapshot> snapshot, double triggeringLoad,
double triggeringBlackoutPercentage) {
if (snapshot.isEmpty()) {
return null ;
}
Set<String> availableZones = new HashSet<String>(snapshot.keySet());
if (availableZones.size() == 1) {
return availableZones;
}
Set<String> worstZones = new HashSet<String>();
double maxLoadPerServer = 0;
boolean limitedZoneAvailability = false ;

for (Map.Entry<String, ZoneSnapshot> zoneEntry : snapshot.entrySet()) {
String zone = zoneEntry.getKey();
ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
int instanceCount = zoneSnapshot.getInstanceCount();
if (instanceCount == 0) {
availableZones.remove(zone);
limitedZoneAvailability = true ;
} else {
double loadPerServer = zoneSnapshot.getLoadPerServer();
if (((double ) zoneSnapshot.getCircuitTrippedCount())
/ instanceCount >= triggeringBlackoutPercentage
|| loadPerServer < 0) {
availableZones.remove(zone);
limitedZoneAvailability = true ;
} else {
if (Math.abs (loadPerServer - maxLoadPerServer) < 0.000001d) {
// they are the same considering double calculation
// round error
worstZones.add(zone);
} else if (loadPerServer > maxLoadPerServer) {
maxLoadPerServer = loadPerServer;
worstZones.clear();
worstZones.add(zone);
}
}
}
}

if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
// zone override is not needed here
return availableZones;
}
String zoneToAvoid = randomChooseZone (snapshot, worstZones);
if (zoneToAvoid != null ) {
availableZones.remove(zoneToAvoid);
}
return availableZones;

}

public static Set<String> getAvailableZones(LoadBalancerStats lbStats,
double triggeringLoad, double triggeringBlackoutPercentage) {
if (lbStats == null ) {
return null ;
}
Map<String, ZoneSnapshot> snapshot = createSnapshot (lbStats);
return getAvailableZones (snapshot, triggeringLoad,
triggeringBlackoutPercentage);
}

@Override
public AbstractServerPredicate getPredicate() {
return compositePredicate ;
}
}

3.8、RibbonLoadBalancerContext统计根据

* RibbonLoadBalancerContext和LoadBalancerContext一致

* 此类的作用类似一个记录工具类,所有方法都需要指定LoadBalancerStats的ServerStats参数

穿插在请调用的整个流程,提供记录和更新服务状态ServerStats的功能,由cliect客户端自己决定何时调用

比如在打开请求时,会调用noteOpenConnectio(ServerStats serverStats)n方法,此方法会把对应的ServerStats的活跃请求指标activeRequestsCount + 1

* 核心方法:

① noteRequestCompletion:请求正常响应或者异常时都会调用

  • 活跃请求-1 ; 总请求数+1 ;记录响应时间

stats.decrementActiveRequestsCount();

stats.incrementNumRequests();

stats.noteResponseTime(responseTime);

  • 正常响应,清空连续失败记录:stats.clearSuccessiveConnectionFailureCount();

  • 如果时符合断路器异常(由RetryHandler默认SocketException.class, SocketTimeoutException.class):

连续失败次数+1 ;stats.incrementSuccessiveConnectionFailureCount();

服务总失败次数+1:stats.addToFailureCount();

  • 其他不符合的异常清空连续失败记录:stats.clearSuccessiveConnectionFailureCount();

②noteError:抛出error的时候调用(同noteRequestCompletion)

protected void

③noteResponse:正常响应的时候调用(同noteRequestCompletion)

protected void

④noteOpenConnection:client开始执行请求的是调用:活跃请求+1

public class LoadBalancerContext implements IClientConfigAware {
private static final Logger logger = LoggerFactory.getLogger (LoadBalancerContext.class );

protected String clientName = "default" ;
protected String vipAddresses ;

// 用于判断一个异常类型可否重试或断路
protected int maxAutoRetriesNextServer = DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER ;
protected int maxAutoRetries = DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES ;
protected RetryHandler defaultRetryHandler = new DefaultLoadBalancerRetryHandler();

protected boolean okToRetryOnAllOperations = DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS .booleanValue();
private ILoadBalancer lb ;
private volatile Timer tracer ;

public LoadBalancerContext(ILoadBalancer lb) {
this .lb = lb;
}

// 活跃请求 -1 ; 总请求数 +1 ; 记录响应时间
private void recordStats(ServerStats stats, long responseTime) {
if (stats == null ) {
return ;
}
stats.decrementActiveRequestsCount();
stats.incrementNumRequests();
stats.noteResponseTime(responseTime);
}

// 请求正常响应或者异常时都会调用
protected void noteRequestCompletion(ServerStats stats, Object response, Throwable e, long responseTime) {
if (stats == null ) {
return ;
}
noteRequestCompletion(stats, response, e, responseTime, null );
}
public void noteRequestCompletion(ServerStats stats, Object response, Throwable e, long responseTime, RetryHandler errorHandler) {
if (stats == null ) {
return ;
}
try {
// 活跃请求 -1 ; 总请求数 +1 ; 记录响应时间
recordStats(stats, responseTime);
RetryHandler callErrorHandler = errorHandler == null ? getRetryHandler() : errorHandler;
if (callErrorHandler != null && response != null ) {
// 正常响应,清空连续失败记录
stats.clearSuccessiveConnectionFailureCount();
} else if (callErrorHandler != null && e != null ) {
if (callErrorHandler.isCircuitTrippingException(e)) {
// 如果时符合断路器异常 ( 默认 SocketException.class, SocketTimeoutException.class) :连续失败次数 +1 ; 服务总失败次数 +1
stats.incrementSuccessiveConnectionFailureCount();
stats.addToFailureCount();
} else {
// 其他不符合的异常清空连续失败记录
stats.clearSuccessiveConnectionFailureCount();
}
}
} catch (Exception ex) {}
}

// 抛出 error 的时候调用(同 noteRequestCompletion
protected void noteError(ServerStats stats, ClientRequest request, Throwable e, long responseTime) {
if (stats == null ) {
return ;
}
try {
// 活跃请求 -1 ; 总请求数 +1 ; 记录响应时间
recordStats(stats, responseTime);
RetryHandler errorHandler = getRetryHandler();
if (errorHandler != null && e != null ) {
if (errorHandler.isCircuitTrippingException(e)) {
stats.incrementSuccessiveConnectionFailureCount();
stats.addToFailureCount();
} else {
stats.clearSuccessiveConnectionFailureCount();
}
}
} catch (Exception ex) {}
}

// 正常响应的时候调用(同 noteRequestCompletion
protected void noteResponse(ServerStats stats, ClientRequest request, Object response, long responseTime) {
if (stats == null ) {
return ;
}
try {
recordStats(stats, responseTime);
RetryHandler errorHandler = getRetryHandler();
if (errorHandler != null && response != null ) {
stats.clearSuccessiveConnectionFailureCount();
}
} catch (Exception ex) {
logger .error("Error noting stats for client {}" , clientName , ex);
}
}

//client 开始执行请求的是调用:活跃请求 +1
public void noteOpenConnection(ServerStats serverStats) {
if (serverStats == null ) {
return ;
}
try {
serverStats.incrementActiveRequestsCount();
} catch (Exception ex) {
logger .error("Error noting stats for client {}" , clientName , ex);
}
}

//....
}

4、通过SpringClientFactory获取bean(以serviceId容器隔离)

* 在RibbonAutoConfiguration 自动配置类中会创建SpringClientFactory 、configurations为配置表

@Bean

public SpringClientFactory springClientFactory() {

SpringClientFactory factory = new SpringClientFactory();

factory.setConfigurations(this .configurations );

return factory;

}

* SpringClientFactory继承NamedContextFactory,在创建时,构造方法指定了默认的配置信息和名称

public SpringClientFactory() {
super (RibbonClientConfiguration.class , NAMESPACE , "ribbon.client.name" );
}

* NamedContextFactory提供以name为key的上下文表(父容器为顶层的Spring容器)

  • 可以为每一个服务创建一个容器,内部为各个服务指定配置的实例和默认的配置实例

* 核心作用:获取bean

  • SpringClientFactory.getInstance(String name, Class<C> type)

  • 通过NamedContextFactory获取指定容器内的某个type实例

获取不到时,会通过IClientConfig进行创建,调用容器的autowireBean进属性注入,但是最终实例不会放入容器中

// SpringClientFactory

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

// 通过 NamedContextFactory 获取指定容器内的某个 type 实例,获取不到时,会通过 IClientConfig 进行创建,
// 调用容器的 autowireBean 进属性注入,但是最终实例不会放入容器中
@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);
}

//
}

// NamedContextFactory
public abstract class NamedContextFactory<C extends org.springframework.cloud.context.named.NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {

// 默认的配置类信息
private final String propertySourceName ;
private final String propertyName ;
private Class<?> defaultConfigType ;
// 容器表
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
//@RibbonClients RibbonClient 注解, name 和指定的配置类
private Map<String, C> configurations = new ConcurrentHashMap<>();
private ApplicationContext parent ;

public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this .defaultConfigType = defaultConfigType;
this .propertySourceName = propertySourceName;
this .propertyName = propertyName;
}

// 获取指定 name 上下文中,指定类型的实例
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) {
if (!this .contexts .containsKey(name)) {
synchronized (this .contexts ) {
if (!this .contexts .containsKey(name)) {
this .contexts .put(name, createContext(name));
}
}
}
return this .contexts .get(name);
}

// 创建指定 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);
}
}
// 在配置表中找到默认的全局配置类( defaultConfiguratiuon 指定的),进行解析注入
for (Map.Entry<String, C> entry : this .configurations .entrySet()) {
if (entry.getKey().startsWith("default." )) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 在配置表中找到默认的全局配置类(构造器 defaultConfigType 指定的),进行解析注入
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 ) {
context.setParent(this .parent );
context.setClassLoader(this .parent .getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}

//

}

5、@LoadBalanced + RestTemplate 的主流程

5.1、实例

(1)使用 @LoadBalanced 注解修饰的 RestTemplate:

@Configuration
public class RestConfig {
// 创建 restTemplate 对象。 LoadBalanced 注解表示赋予 restTemplate 使用 Ribbon 的负载均衡的能力(一定要加上注解,否则无法远程调用)
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

(2)通过 RestTemplate 请求远程服务地址并接收返回值

@RestController
@RequestMapping(value = "api/invoke" )
public class InvokeController{

@Autowired
private RestTemplate restTemplate ;

public String getByRestTemplate(Integer num1, Integer num2){
// 第一个 cloud-producer-server 代表在 nacos 注册中心中的服务名,第二个 cloud-producer-server 代表 contextPath 配置的项目路径
String url = "http://cloud-producer-server/cloud-producer-server/getSum" ;
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("num1" , num1);
params.add("num2" , num2);
return restTemplate .postForObject(url, params, String.class );
}
}

5.2、RestTemplate到LoadBalancerClient(RibbonLoadBalancerClient)的流程

* 这里的RestTemplate使用的是原生的RestTemplate,即没有进过任何定制的(可支持自定义超时属性、底层HttpClient 工具)

默认使用JDK自带的HttpURLConnection进行通信的

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}

* 标志了@LoadBalanced的restTemplate.postForObject(url, params, String.class) ->doExecute的流程

  • 通过restTemplate实例中设置的ClientHttpRequestFactory返回,由于存在拦截器则为InterceptingClientHttpRequestFactory

会创建InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);

  • 执行InterceptingClientHttpRequest.execute()

就会执行拦截器链的,最后会调用LoadBalancerClient.execute(serviceName,this.requestFactory.createRequest(request, body, execution))

①在LoadBalancerAutoConfiguration自动配置类中可以指定

  • LoadBalancerClient指RibbonLoadBalancerClient类型对象

  • requestFactory指LoadBalancerRequestFactory类型对象

②参数serviceName为url路径的域名

③参数request是InterceptingClientHttpRequest:封装了请求对象和通信工具

//RestTemplate
//postForObjec->doExecute
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
ClientHttpResponse response = null ;
try {
//InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null ) {
requestCallback.doWithRequest(request);
}
//InterceptingClientHttpRequest.executeInternal->InterceptingRequestExecution 类的 .execute
// 执行拦截器链的
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null );
}
catch (IOException ex) {throw new ResourceAccessException();
}
finally {
if (response != null ) {response.close(); }
}
}
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
initialize(request);
return request;
}

/ / RestTemplate 的父类, InterceptingHttpAccessor

//InterceptingClientHttpRequestFactory底层封装了原始SimpleClientHttpRequestFactory和拦截器,装饰了拦截功能
@Override
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty (interceptors)) {
ClientHttpRequestFactory factory = this .interceptingRequestFactory ;
if (factory == null ) {
factory = new InterceptingClientHttpRequestFactory(super .getRequestFactory(), interceptors);
this .interceptingRequestFactory = factory;
}
return factory;
}
else {
return super .getRequestFactory();
}
}

/ / InterceptingRequestExecution
private class InterceptingRequestExecution implements ClientHttpRequestExecution {

private final Iterator<ClientHttpRequestInterceptor> iterator ;

public InterceptingRequestExecution() {
this .iterator = interceptors .iterator();
}

// 存在拦截器的:走每一个拦截器的intercept方法

//不存在拦截器:走 原始SimpleClientHttpRequestFactory 创建的SimpleBufferingClientHttpRequest会设置好超时配置

// setConnectTimeout/setReadTimeout,值是在restTemplate中设置的

// 并且是使用JDK自带的HttpURLConnection进行通信的
@Override
public ClientHttpResponse execute(HttpRequest request, byte \[\] body) throws IOException {
if (this .iterator .hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this .iterator .next();
return nextInterceptor.intercept(request, body, this );
}
else {
HttpMethod method = request.getMethod();
ClientHttpRequest delegate = requestFactory .createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy (body, outputStream));
}
else {
StreamUtils.copy (body, delegate.getBody());
}
}
return delegate.execute();
}
}
}

//最终调用的LoadBalancerClient.execute(serviceName,this.requestFactory.createRequest(request, body, execution))

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();
String serviceName = originalUri.getHost();
return this .loadBalancer .execute(serviceName,this .requestFactory .createRequest(request, body, execution));
}

}

5.3、RibbonLoadBalancerClient.execute方法(LoadBalancerClient接口方法)

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
//..
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte \[\] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this .loadBalancer .execute(serviceName,this .requestFactory .createRequest(request, body, execution));
}

}

* 核心方法RibbonLoadBalancerClient.execute的实现

  • 此方法是LoadBalancerClient接口的核心方法,用于选中一个服务实例后,执行

  • 基于SpringClientFactory在serviceId对应的上下文中获取ILoadBalancer实例,默认为ZoneAwareLoadBalancer

SpringClientFactory的作用和LoadBalancerClientFactory一模一样,都是Name容器,以serviceId为一个容器的维度

里面包含了ribbon的所有必要的组件bean。每一个serverId是相互隔离的

  • 核心调用其ILoadBalancer的chooseServer方法获取对应目标服务实例

  • 最后调用LoadBalancerRequest.apply(serviceInstance)执行请求

执行 LoadBalancerRequestFactory.createRequest(request, body, execution)创建一个函数指针,进行url通信

* LoadBalancerRequestFactory.createRequest(request, body, execution)

  • 使用LoadBalancerRequestFactory创建一个函数指针,把选择的服务实例,使用InterceptingRequestExecution.execute(serviceRequest, body);完成通信,

  • InterceptingRequestExecution的execute流程见上,由于此时没有拦截器了,就会走默认的execute步骤

走原始SimpleClientHttpRequestFactory创建的SimpleBufferingClientHttpRequest会设置好超时配置,setConnectTimeout/setReadTimeout,值是在restTemplate中设置的

最终使用使用JDK自带的HttpURLConnection进行通信的

public class RibbonLoadBalancerClient implements LoadBalancerClient {

private SpringClientFactory clientFactory ;

public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this .clientFactory = clientFactory;
}

@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
//
return context.reconstructURIWithServer(server, uri);
}

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 基于 SpringClientFactory serviceId 对应的上下文中获取 ILoadBalancer 实例,默认为 ZoneAwareLoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 使用 ILoadBalancer chooseServer 方法获取最终选定的服务
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.apply(serviceInstance) 执行请求
//LoadBalancerRequest<T> request RestTemplate 执行拦截器时调用提供放入
return execute(serviceId, ribbonServer, request);
}

protected ILoadBalancer getLoadBalancer(String serviceId) {
return this .clientFactory .getLoadBalancer(serviceId);
}

// 核心是调用loadBalancer.chooseServer 方法

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null ) {
return null ;
}
return loadBalancer.chooseServer(hint != null ? hint : "default" );
}
//

}

public class LoadBalancerRequestFactory {

private LoadBalancerClient loadBalancer ;

private List<LoadBalancerRequestTransformer> transformers ;

public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer,
List<LoadBalancerRequestTransformer> transformers) {
this .loadBalancer = loadBalancer;
this .transformers = transformers;
}

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

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

}

相关推荐
计算机安禾1 小时前
【算法分析与设计】第48篇:流算法与数据概要技术
java·服务器·网络·数据库·算法
hunterkkk(c++)1 小时前
SPFA最短路径算法(c++)
java·c++·算法
Java_2017_csdn1 小时前
在 Java 中,MessageFormat.format() 和 String.format() 函数对比?
java·开发语言·前端·数据库
噢,我明白了2 小时前
MyBatis-Plus 中IPage的分页查询
java·mybatis
剑挑星河月2 小时前
98.验证二叉搜索树
java·算法·leetcode
我登哥MVP2 小时前
Spring Boot 从“会用”到“精通”:请求映射原理
java·spring boot·后端·spring·servlet·maven·intellij-idea
阿文的代码库2 小时前
干货分享|C++运算符重载知识点
java·c++·算法
码不停蹄的玄黓2 小时前
Java 实现阻塞队列
java·开发语言
SunnyDays10112 小时前
Java 实现 PDF 转 PDF/A 和 PDF/A 转 PDF(超详细教程)
java·开发语言·pdf