通讯组件主要包括编码&解码器,列表过滤,调用代理客户端,本地优先策略,容错,重试以及超时等相关内容。
LoadBalancerClient接口的所属包org.springframework.cloud.client.loadbalancer,对其内容进行整理,可以得出如下图的关系:
IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特性的需求,可以自己实现。
- BestAvailableRule 选择最小请求数
- ClientConfigEnabledRoundRobinRule 轮询
- RandomRule 随机选择一个server
- RoundRobinRule 轮询选择server
- RetryRule 根据轮询的方式重试
- WeightedResponseTimeRule 根据响应时间去分配一个weight ,weight越低,被选择的可能性就越低
- ZoneAvoidanceRule 根据server的zone区域和可用性来轮询选择
过滤器ZoneAvoidanceRule源码解读
ZoneAvoidanceRule源码解读
public class ZoneAvoidanceRule extends PredicateBasedRule {
private static final Random random = new Random();
//使用CompositePredicate来进行服务实例清单过滤。
//组合过滤条件
private CompositePredicate compositePredicate;
public ZoneAvoidanceRule() {
super();
//主过滤条件
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
//次过滤条件
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
......
}
CompositePredicate源码解读
public class ZoneAvoidanceRule extends PredicateBasedRule {
private static final Random random = new Random();
//使用CompositePredicate来进行服务实例清单过滤。
//组合过滤条件
private CompositePredicate compositePredicate;
public ZoneAvoidanceRule() {
super();
//主过滤条件
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
//次过滤条件
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
......
}
ZoneAvoidancePredicate源码解读
//服务实例所在的Zone必须可用
public class ZoneAvoidancePredicate extends AbstractServerPredicate {
......
@Override
public boolean apply(@Nullable PredicateKey input) {
if (!ENABLED.get()) {
return true;
}
String serverZone = input.getServer().getZone();
if (serverZone == null) {
// there is no zone information from the server, we do not want to filter
// out this server
return true;
}
LoadBalancerStats lbStats = getLBStats();
if (lbStats == null) {
// no stats available, do not filter
return true;
}
if (lbStats.getAvailableZones().size() <= 1) {
// only one zone is available, do not filter
return true;
}
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
if (!zoneSnapshot.keySet().contains(serverZone)) {
// The server zone is unknown to the load balancer, do not filter it out
return true;
}
logger.debug("Zone snapshots: {}", zoneSnapshot);
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
//服务实例所在的Zone必须可用
if (availableZones != null) {
return availableZones.contains(input.getServer().getZone());
} else {
return false;
}
}
}
列表过滤
大概的实现类流程如下:
ServerList就是server的列表,IRule做负载均衡的关键参数就是server列表,就是从这里获取的。
LoadBalancerStats内部有两个成员变量,一是upServerListZoneMap,二是serverStatsCache。
upServerListZoneMap定义为volatile
Map<String, List<? extends Server>>,其中存储了zone和server列表的对应关系。
serverStatsCache定义为LoadingCache<Server,
ServerStats>,存储了server和ServerStats对象的对应关系,其使用了guaua的cache。
根据以上两种结构,能够从每个server的状态统计到整个zone的状态。
Affinity即密切关系,这个filter是ribbon的区域亲和性过滤。默认情况下,单独使用ribbon,区域亲和的过滤器默认是关闭的,可以通过修改配置打开。
ZoneAffinityServerListFilter先进行亲和性过滤,之后再进行zone健康检查,如果zone认为不健康,则不再执行过滤,认为zone不健康的条件如下:
- zone中断路器打开的server比例达到了整个集群中的80%
- server平均负载(即存活的请求数)达到60%
- server存活数小于2,即0或1
DefaultNIWSServerListFilter继承自ZoneAffinityServerListFilter,默认没有任何实现。
ZonePreferenceServerListFilter,spring-cloud自己实现的过滤器,即可以通过配置,强制使用配置的zone来过滤。
其实现也比较简单,即先用ZoneAffinityServerListFilter来过滤,如果父类没有启用过滤则再用配置的zone来强制过滤。
如果没有配置zone,则该filter退化为ZoneAffinityServerListFilter。
ServerListSubsetFilter这个类用于限制返回server列表的数量,特别适用于集群中有成百上千的server。因为让客户端http连接池跟每个server保持链接其实用处不大,它同样会剔除不健康的server。
调用代理客户端
WeightedResponseTimeRule,获取平均响应时间作为权重计算的策略。
AvailabilityFilteringRule,判断当前断路器是否打开作为是否可用的策略。使用活跃请求数量选择最小的server。
ZoneAvoidanceRule,获取可用的所有zone,获取zone状态信息。
ZoneAwareLoadBalancer,它有如下功能
- 当选择server时能够避免某个不健康的zone。
-
衡量zone健康状况的关键指标是平均活跃请求,即某个zone的平均活跃请求=这个zone中,还未完成的请求/存活的server
当某个状况不好的zone慢慢的发生超时的时候(即大量的请求被卡住),这个指标是非常有效的。
- 此均衡器将会计算所有的存活的zone的状况:当任何某个zone平均活跃请求达到配置的阈值时,这个zone将会从被从存活列表中移除;如果多个zone达到阈值,那么具有平均server请求量最大的zone将会被移除;
- 一旦状况最坏的zone被移除,那么将会按照zone具有的实例数量作为概率来选择某个zone。
- 选择完zone,将会使用IRule来选择一个server,默认的实现为ZoneAvoidanceRule(由于此时只有一个zone,故ZoneAvoidanceRule退化为AvailabilityFilteringRule)。
来看一下核心方法chooseServer是如何实现的,我们已经知道了最终负载均衡的实现是ZoneAwareLoadBalancer,而它也重写了chooseServer,它的choose过程如下:
单个zone的choose过程说明:
- 从LoadBalancerStats查询存活的zone数量。
- 调用BaseLoadBalancer的chooseServer
- BaseLoadBalancer的实现很简单,直接委托给具体的Rule来选择
- 最终会使用ZoneAvoidanceRule,而此时zone的数量为1,ZoneAvoidanceRule的zone过滤选择功能不起作用,退化为AvailabilityFilteringRule,即按照server断路器打开或者并发量过大过滤掉一些server,再轮询选择一个。
多个zone的choose过程说明
- 从LoadBalancerStats查询存活的zone数量。
- 发现>1,则调用ZoneAvoidanceRule进行zone的过滤和挑选。
- 挑选好zone之后,跟 单个zone的choose过程一样了,委托给BaseLoadBalancer来进行具体的server挑选。
本中心优先策略
利用Ribbon的Zone-Affinity(区域亲和性)来解决。Ribbon的Zone-Affinity的原理是:服务消费者在获取注册服务的列表时,添加一个filter来过滤注册服务,根据配置选择跟服务消费者在同一个区的服务,这样一般可以降低延迟;如果过滤后的服务正忙或者异常,则停止区域感知。从而可以做到基于实时的服务运行状况,决定是否只选择本区域的服务,当本区域的服务发生故障时,可以快速的切换。
扩展IRule
可以通过以下配置项识别实例所在的区域:
eureka.instance.metadata-map.zone=xxx
深圳的服务全部加上:
eureka.instance.metadata-map.zone=sz
成都的服务全部加上:
eureka.instance.metadata-map.zone=cd
以达到本地优先效果。
重试
负载均衡的重试机制
ribbon:
OkToRetryOnAllOperations: true # 是否对所有请求都进行重试
MaxAutoRetries: 0 #重试次数
MaxAutoRetriesNextServer: 2 #重试切换实例次数
ConnectTimeout: 250
ReadTimeout: 300
retryableStatusCodes: 503,500 # 这里不配置其实也可以,不配置的时候,仅仅当请求服务实例报错的时候重试,配置了的时候,当请求服务实例出现这里指定的状态,也会重试
超时
#ribbon请求连接的超时时间,限制3秒内必须请求到服务,并不限制服务处理的返回时间
ribbon.ConnectTimeout=3000
ribbon.SocketTimeout=5000
#请求处理的超时时间 下级服务响应最大时间,超出时间消费方(路由也是消费方)返回timeout
ribbon.ReadTimeout=5000
# 单独设置某个服务的超时时间,会覆盖其他的超时时间限制,服务的名称以注册中心页面显示的名称为准,超时时间不可大于断路器的超时时间
#service-a.ribbon.ReadTimeout=50000
#service-a.ribbon.ConnectTimeout=50000
关注公众号 soft张三丰