基本使用及配置
以下配置及使用均基于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;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">dependency</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">groupId</span>></span>org.springframework.cloud<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">groupId</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">artifactId</span>></span>spring-cloud-starter-ribbon<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">artifactId</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">dependency</span>></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><ServerList<span class="hljs-preprocessor" style="box-sizing: border-box; color: #999999; font-weight: bold;"><?</span> extends Server>> 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><Server> 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;"><?</span> extends Server> serverList:listServerList){
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span><Server> 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><Server>) 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<>();
}
@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><Server> 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;"><?</span> extends Server> serverList:listServerList){
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">List</span><Server> 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><Server>) 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<>();
}
}
创建一个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<Server> <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<EurekaClient> 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<Server> <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<Server> result = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> ArrayList<>();
<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<Server> <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<Server> <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的实现
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于