Ribbon 负载均衡使用详解

本贴最后更新于 2636 天前,其中的信息可能已经时移世异

基本使用及配置

以下配置及使用均基于Spring Boot工程,默认POM父类为spring-boot-starter-parent,dependecyManagement里包含spring-cloud-dependencies的配置。

根据配置文件为RestTemplate负载均衡

配置及使用

引入以下JAR包

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;">&lt;<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">dependency</span>&gt;</span>
	<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;">&lt;<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;">&lt;/<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">groupId</span>&gt;</span>
	<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;">&lt;<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">artifactId</span>&gt;</span>spring-cloud-starter-ribbon<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;">&lt;/<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">artifactId</span>&gt;</span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;">&lt;/<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">dependency</span>&gt;</span></code></pre>
  • spring-boot-starter-web提供RestTemplate实现;
  • spring-cloud-starter-ribbon提供Ribbon实现。

在配置文件中配置应用及其对应实例

service-a.ribbon.listOfServers=localhost:80,localhost:81
service-b.ribbon.listOfServers=localhost:83,localhost:82
  • service-a,service-b代表应用名
  • ribbon这个字段固定不变
  • ribbon后面代表某个特定某个特定应用名后的参数
  • 其他更多的参数可参见DefaultClientConfigImpl这个类

创建RestTemplate及使用

创建RestTemplate这个Bean

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

加上@LoadBalanced表示创建的这个RestTemplate需要使用Ribbon进行负载均衡。

通过注入获得restTemplate:

@Resource
private RestTemplate restTemplate;

使用RestTemplate

restTemplate.getForObject("http://service-a/hello-world?parameter={params}", String.class,paramObject);

如上直接使用service-a即可自动负载均衡到两个实例:localhost:80,localhost:81中

默认负载均衡策略

  • IClientConfig:客户端配置读取形式,默认使用DefaultClientConfigImpl
  • IRule:具体服务选择策略,默认使用ZoneAvoidanceRule,其会根据ZONE的健康情况过滤特定ZONE的所有实例,并根据实例自身的并发请求数量及断路状态进行过滤。过滤完后进行线性轮询
  • IPing:服务实例存活检测策略,默认NoOpPing,即不主动进行服务器存活检测
  • ServerList:获取应用对应的服务实例列表。默认ConfigurationBasedServerList,其根据客户端的配置获取特定应用的服务器列表
  • ServerListFilter:服务实例过滤策略,默认ZonePreferenceServerListFilter,其优先筛选出相同ZONE的Server列表
  • ILoadBalancer:ZoneAwareLoadBalancer,该实现具有区域感知能力,能对区域层次的调用信息进行统计,供具体负载均衡实例使用
  • 调用重试:默认不会调用重试,除非引入了了Spring-retry的包
    • 若不需重试,请注意别不小心引入该包,或者显式配置关闭重试:spring.cloud.loadbalancer.retry.enabled=false
    • 此处的重试跟IRule的重试不是一回事
    • 建议不启用重试
  • ServerStat:其中包含默认访问断路逻辑,当某一个Server连续失败N次时(默认是3次)则将其断路若干时间,断路时间随着连续失败次数的增长而按2为底的指数级增长,断路时间存在一个上限,默认为30秒

关于以上各个类及名词的详细作用及解析请参考后续章节——《原理、主要类、配置详解》

注意事项

  • 配置了@LoadBalanced注解的RestTemplate默认不能再通过 原生的域名或者IP 完成访问。
    • 需要访问原生域名/IP时,最简单的方法就是重新创建一个RestTemplate
    • 若不想创建多个RestTemplate请参考后续章节,请顺序往后阅读

使用Eureka的配置

配置及使用

配置

在上述配置的基础上添加Eureka的POM依赖及填写对应的Eureka服务器地址即可。具体请参考注册中心配置部分。

如果有多个区域的服务,若希望优先使用本区的服务则需要配置区域信息

eureka.instance.metadataMap.zone=shanghai

使用

其使用方法与依赖于配置文件的负载均衡使用形式一致。

默认负载均衡策略

其主要特性与基于配置的一致,以下描述不同点 IPing:实现将替换为NIWSDiscoveryPing。其判断Server存活的依据就是之前从Eureka服务器拉下来的Server的状态,也就是说客户端不作实质的存活检测,交由Eureka进行。 ServerList:实现替换为DomainExtractingServerList,其主要操作实质委托给DiscoveryEnabledNIWSServerList,也就是委托给Eureka处理

注意事项

  • 引入Eureka后,原来基于配置的应用访问列表随即失效,但其他配置依然有效。
  • 同样,添加了@LoadBalanced注解的RestTemplate访问原生域名/IP也会失败。
  • 后续会介绍兼容 基于配置服务器负载均衡、基于Eureka服务器负载均衡以及基于DNS服务器列表负载均衡的 @LoadBalanced RestTemplate的配置形式

回退到配置形式

如果遇到某些特殊情况,如Eureka服务器不可用,可以手工回退到配置模式

ribbon.eureka.enabled=false

因此建议前期上线的基于Ribbon及Eureka做客户端负载均衡的最好也配上listOfServers这个参数。以便出现Eureka服务器异常时,也能降级使用

整合Eureka、配置及DNS访问(Beta)

使用本实现前请先阅读《原理、主要类、配置详解》一节,理解相关背景知识,本案例仅供参。优先推荐使用多个RestTemplte实例来解决同时存在域名访问及RIBBON访问的问题

有的时候我们希望使用一个@LoadBalanced RestTemplate解决所有的访问类型,包括域名访问、基于配置的RIBBON访问、基于Eureka的RIBBON访问,这样注入的时候不需要刻意选择特定的RestTemplate。

实现这个需求需要我们自行定制一个ServerList以替换原生的ServerList。以下扩展基于Eureka的Ribbon访问配置。

定义一个CompositeServerList类,其使用Composite模式组装多个ServerList实现

public class CompositeServerList implements ServerList<Server> {
@Autowired
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;ServerList<span class="hljs-preprocessor" style="box-sizing: border-box; color: #999999; font-weight: bold;">&lt;?</span> extends Server&gt;&gt; listServerList;

<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">static</span> Logger LOG = LoggerFactory.getLogger(CompositeServerList.class);	


@SuppressWarnings(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"unchecked"</span>)
@Override
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;Server&gt; getInitialListOfServers() {
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">for</span>(ServerList<span class="hljs-preprocessor" style="box-sizing: border-box; color: #999999; font-weight: bold;">&lt;?</span> extends Server&gt; serverList:listServerList){
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;Server&gt; initialListOfServers = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">null</span>;
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">try</span> {
			initialListOfServers = (<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;Server&gt;) serverList.getInitialListOfServers();
		} <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">catch</span> (<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">Exception</span> e) {
			LOG.error(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"server list resolved failed"</span>,e);
		}
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">if</span>(initialListOfServers == <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">null</span> || initialListOfServers.size() == <span class="hljs-number" style="box-sizing: border-box; color: teal;">0</span>){
			<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">continue</span>;
		}
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> initialListOfServers;
	}
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> ArrayList&lt;&gt;();
}

@SuppressWarnings(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"unchecked"</span>)
@Override
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;Server&gt; getUpdatedListOfServers() {
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">for</span>(ServerList<span class="hljs-preprocessor" style="box-sizing: border-box; color: #999999; font-weight: bold;">&lt;?</span> extends Server&gt; serverList:listServerList){
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;Server&gt; updatedListOfServers = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">null</span>;
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">try</span> {
			updatedListOfServers = (<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span>&lt;Server&gt;) serverList.getUpdatedListOfServers();
		} <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">catch</span> (<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">Exception</span> e) {
			LOG.error(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"server list resolved failed"</span>,e);
		}
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">if</span>(updatedListOfServers == <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">null</span> || updatedListOfServers.size() == <span class="hljs-number" style="box-sizing: border-box; color: teal;">0</span>){
			<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">continue</span>;
		}
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> updatedListOfServers;
	}
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> ArrayList&lt;&gt;();
}

}

创建一个Spring Configuration类,但不必带上@Configuartion标签(避免在主ApplicationContext加载了这些Configuration,这些配置是需要在application/host对应的子ApplicationContext加载的,具体请参考后续章节)

public class OverrideRibbonConfiguration {
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">static</span> Logger LOG = LoggerFactory.getLogger(OverrideRibbonConfiguration.class);


<span class="hljs-annotation" style="box-sizing: border-box;">@Value</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"${ribbon.eureka.approximateZoneFromHostname:false}"</span>)
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">boolean</span> approximateZoneFromHostname = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">false</span>;

<span class="hljs-annotation" style="box-sizing: border-box;">@Value</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"${ribbon.client.name}"</span>)
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> String ribbonClientName;

<span class="hljs-annotation" style="box-sizing: border-box;">@Value</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"${ribbon.dns.resolvedServerPort:80}"</span>)
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">int</span> dnsResolvedServerPort;

<span class="hljs-annotation" style="box-sizing: border-box;">@Bean</span>
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> IClientConfig <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">ribbonClientConfig</span><span class="hljs-params" style="box-sizing: border-box;">()</span> </span>{
	DefaultClientConfigImpl config = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> DefaultClientConfigImpl();
	config.loadProperties(<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.ribbonClientName);
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> config;
}

<span class="hljs-annotation" style="box-sizing: border-box;">@Bean</span>
<span class="hljs-annotation" style="box-sizing: border-box;">@Primary</span>
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> ServerList&lt;Server&gt; <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">serverList</span><span class="hljs-params" style="box-sizing: border-box;">()</span></span>{
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> CompositeServerList();
}

<span class="hljs-annotation" style="box-sizing: border-box;">@Bean</span>
<span class="hljs-annotation" style="box-sizing: border-box;">@Order</span>(<span class="hljs-number" style="box-sizing: border-box; color: teal;">0</span>)
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> DomainExtractingServerList <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">eurekaServerList</span><span class="hljs-params" style="box-sizing: border-box;">(IClientConfig config,Provider&lt;EurekaClient&gt; eurekaClientProvider)</span></span>{
	DiscoveryEnabledNIWSServerList discoveryServerList = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> DiscoveryEnabledNIWSServerList(config, eurekaClientProvider);
	DomainExtractingServerList serverList = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> DomainExtractingServerList(discoveryServerList, config, <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.approximateZoneFromHostname);
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> serverList;
}

<span class="hljs-annotation" style="box-sizing: border-box;">@Bean</span>
<span class="hljs-annotation" style="box-sizing: border-box;">@Order</span>(<span class="hljs-number" style="box-sizing: border-box; color: teal;">1</span>)
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> ConfigurationBasedServerList <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">cfgServerList</span><span class="hljs-params" style="box-sizing: border-box;">(IClientConfig config)</span></span>{
	ConfigurationBasedServerList cfgServerList = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> ConfigurationBasedServerList();
	cfgServerList.initWithNiwsConfig(config);
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> cfgServerList;
}

<span class="hljs-annotation" style="box-sizing: border-box;">@Bean</span>
<span class="hljs-annotation" style="box-sizing: border-box;">@Order</span>(<span class="hljs-number" style="box-sizing: border-box; color: teal;">2</span>)
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> DnsResolvedServerList <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">dnsServerList</span><span class="hljs-params" style="box-sizing: border-box;">()</span></span>{
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> DnsResolvedServerList(ribbonClientName, dnsResolvedServerPort);
}

public static class DnsResolvedServerList implements ServerList<Server>{

	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> String ribbonClientName;
	<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">int</span> dnsResolvedServerPort;
	
	
	<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">DnsResolvedServerList</span><span class="hljs-params" style="box-sizing: border-box;">(String ribbonClientName, <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">int</span> dnsResolvedServerPort)</span> </span>{
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">super</span>();
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.ribbonClientName = ribbonClientName;
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.dnsResolvedServerPort = dnsResolvedServerPort;
	}

	<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">private</span> List&lt;Server&gt; <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">getHostAddress</span><span class="hljs-params" style="box-sizing: border-box;">()</span></span>{
		ArrayList&lt;Server&gt; result = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> ArrayList&lt;&gt;();			
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">try</span> {
			InetAddress[] address = InetAddress.getAllByName(ribbonClientName);
			<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">for</span>(InetAddress addr :address){
				result.add(<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> Server(addr.getHostAddress(),dnsResolvedServerPort));
			}
		} <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">catch</span> (UnknownHostException e) {
			LOG.warn(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"can not resolve by DNS:"</span> + ribbonClientName);
		}
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> result;
	}

	<span class="hljs-annotation" style="box-sizing: border-box;">@Override</span>
	<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> List&lt;Server&gt; <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">getInitialListOfServers</span><span class="hljs-params" style="box-sizing: border-box;">()</span> </span>{
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> getHostAddress();
	}

	<span class="hljs-annotation" style="box-sizing: border-box;">@Override</span>
	<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> List&lt;Server&gt; <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">getUpdatedListOfServers</span><span class="hljs-params" style="box-sizing: border-box;">()</span> </span>{
		<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> getHostAddress();
	}
}

}

以上类主要的功能是,创建了3个ServerList,然后再注入到CompositeServerList。 但本实现存在缺陷,就是DNS的解析实现时,并没有Server的端口信息,只能解析到域名对应的IP列表,因此若要使用本实现,需要在本地配置好不同 域名对应的服务端口。若一个域名有多个端口提供服务,则无解,只能另起途径解决本问题。

原理、主要类、配置详解

获取所有加了@LoadBalanced注解的RestTemplate,并为其应用LoadBalancerInterceptor这个Inteceptor。

其对于http请求的传入要访问的HostName做解析,解析到对应IP及端口列表,并根据相关负载均衡的逻辑选择一个IP及端口作为host替换掉原来的hostName

LoadBalancerInterceptor

  • 本类在RestTemplate与Ribbon整合时嵌入到RestTemplate的Inteceptor中,根据hostName获取对应的服务器列表,并替换hostName再执行发送请求后返回结果。
  • 后续的inteceptor会被忽略掉。
  • 对于每个hostName都会以当前ApplicationContext为父Context创建一个新的ApplicationContext,并在其中初始化对应的ILoadBalancer相关的一系列Bean,其目的主要是为了简化不同hostName都有相同类型的Bean的情况,该ApplicationContext初始化的设置由SpringClientFactory.configurations、defaultConfigType及PropertyPlaceholderAutoConfiguration.Class确定
    • 若要调整默认或者特定hostName对应的ILoadBalancer的生成,可通过定义RibbonClientSpecification这个Bean或者更改defaultConfigType实现

ILoadBalancer 负载均衡器

  • 初始化/新增 Server列表
  • 根据一个KEY选择一个Server(Key通常是应用名,但LoadBalancer不负责选择应用,只选择应用下的Server。一个Application对应一个ILoadBalancer,由外层控制)
  • 标志某一个Server已经DOWN了,供本地客户程序使用,可动态去掉不可用服务器
    • 还有其他很多层次标志服务器已经DOWN的实现,如通过IPING接口检测到已经DOWN的,Eureka检测到已经DOWN的,不过ILoadBalancer这个层次的抽象不Care
  • 获取所有Server列表
  • 获取所有可用Server

AbstractLoadBalancer

  • 定义方法可获取已经DOWN了的服务器的列表
  • 无参选择一个服务器的方法(比较少用)
  • Server统计数据对象的获取
    • 该对象负责统计、更新、对外提供 server层次/ZONE层次的 响应时间(平均,最大)、活跃调用数、保持的连接数、是否处于断路状态、断路时间配置、

BaseLoadBalancer

  • 引入IRule用于定制选择可用Server的方法
    • 其包含一个LoadBalancer实例,设计有点挫,主要是为了供具体的IRule逻辑获取各种类型服务器列表
  • 引入IPing用于检测特定Server是否健康运作
  • 引入IPingStrategy用于指定执行IPing的策略
  • 如果IPing及IPingStrategy都存在则默认10秒执行一遍PING
    • 上次PING正在执行则跳过
  • 引入发布订阅模式,对外发布 Server列表变更事件、Server状态变更事件
  • 引入统一的配置读取接口

DynamicServerListLoadBalancer

  • 引入ServerList类,用于获取初始服务器列表,以及获取更新的服务器列表
    • “获取更新服务器列表”这个功能将会被ServerListUpdater视情况调用,其有两个实现
      • 定时(默认), 默认是按时30秒调用一次
      • Eureka相关事件触发
    • ServerList在Eureka中的实现,并不一定是全量远程获取所有的?????TBD
  • 引入ServerListFilter,用于按需过滤ServerList类获取到的服务器列表。
    • 该过滤功能与ServerList类的获取更新服务器列表问题 一样,被ServerListUpdater调用
    • 过滤后的ServerList会被全量应用到Loadbalancer中
    • 主要实现的ServerListFilter都与Zone相关,优先筛选出本ZONE对应的Server,若本ZONE出现问题,才会返回其他ZONE的Server

ZoneAwareLoadBalancer

  • 这个类改写了选择父类的ChooseServer方法,父类方法会直接将经过ServerListFilter过滤后的服务器列表直接传递给IRule进行一个特定服务器的选择
  • 而这个类对ZONE数量大于1时,将会对Server按照Zone进行分组并创建一个对应的BaseLoadBalancer,同时根据Zone的一些统计数据,剔除一些不健康的ZONE,对于剩下的ZONE,将随机选择一个,调用BaseLoadBalancer,将请求委托给IRule.

IRule 服务器选择规则

  • 其包含一个选择服务器的方法 Server Choose(Object)
  • 其可以获取或者设置IRule归属的ILoadBalancer,用于获取服务列表等信息

AbstractLoadBalancerRole

  • 实现设置、获取 LoadBalancer的方法

RandomRule

  • 当服务器列表中存在服务器时,随机选择一个状态为UP状态的返回,若没有选择成功则循环返回(不推荐使用本方法,个人觉得这个实现有BUG)

RetryRule

  • Decorator模式,其可修饰一个特定的其他IRule,使其增加在发生获取Server失败的情况下,在一定超时范围内尝试重试获取Server的逻辑。

RoundRobin

  • 线性地从ServerList中获取Server,如果获取的服务器 不可用 或者 没有 准备好提供服务,则尝试获取下一个,最多重试10次

WeightedResponseTimeRule

  • 根据统计数据计算出每个Server的权重,平均响应时间越短的Sever其权重越大,权重越大则在随机选择的过程中命中几率更大

ClientConfigEnabledRoundRobinRule

  • 使用组合模式,组合了RoundRobinRule的功能,这便于本类的继承类自由的实现其他Rule形式,而无需考虑父类实现,当其他形式失败时,则回退到roundRobin形式

BestAvailableRule

  • 继承自 ClientConfigEnabledRoundRobinRule,根据统计信息,选择可用且请求数量最少的一个Server返回,若统计信息尚未生成完善,则使用线性轮训机制

PredicateBaseRule

继承自 ClientConfigEnabledRoundRobinRule,可自定义Server过滤规则,过滤完后再线性轮训(可通过继承修改)

AvailabilityFilteringRule

继承自PredicateBaseRule,线性轮询Sever,过滤掉处于断路状态或者并发请求数超过某个限定值的请求

ZoneAvoidanceRule

其继承自PredicateBaseRule,该Rule集成了2个Predict的筛选条件,其中有一个是主筛选条件:ZoneAvoidancePredict主要根据ZONE的健康情况,过滤不健康的ZONE的实例,当主条件过滤完一遍后,使用从过滤条件过滤:AvailablitilyPredicate,其过滤结束条件是——全部都过滤了一遍或者过滤剩下的Server小于要求的个数或者小于特定的比例

改写默认策略

配置形式

  • hello-service.ribbon.listOfServers=localhost:8080,localhost:8081
  • ribbon.ConnectionTimeout=250

第一条表示改写hostName/applicationId 为 hello-service的配置 第二条表示改写全局默认配置

ribbon后面的配置项,可参见DefaultClientConfigImpl,常见的有:

  • NFLoadBalancerClassName
  • NFLoadBalancerRuleClassName
  • NFLoadBalancerPingClassName
  • NIWSServerListClassName
  • NIWSServerListFilterClassName

注解形式

使用@RibbonClient实现,只能覆盖特定HostName的配置 若要覆盖默认实现,则直接创建默认实现对应的Bean即可

RibbonClientSpecification形式

创建该Bean的对应实例,可以调整具体的ILoadBalancer的实现

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • someone

    创建一个Spring Configuration类,但不必带上@Configuartion标签(避免在主ApplicationContext加载了这些Configuration,这些配置是需要在application/host对应的子ApplicationContext加载的,具体请参考后续章节)

    这个不加@Configuration类都不会加载怎么办?