每日一句
对自己狠一点,逼自己努力,再过几年你将会感谢今天发狠的自己、恨透今天懒惰自卑的自己
目录
前言
上一篇我们介绍了IRule的比较简单的线性轮询的规则,本文继续来探讨其他的规则实现。ClientConfigEnabledRoundRobinRule和他的子类们。
ClientConfigEnabledRoundRobinRule
ClientConfigEnabledRoundRobinRule: 只是包装了RoundRobinRule 并没有实际的价值,并不会直接使用。
@Override
public Server choose(Object key) {
if (roundRobinRule != null) {
return roundRobinRule.choose(key);
} else {
throw new IllegalArgumentException(
"This class has not been initialized with the RoundRobinRule class");
}
}
BestAvailableRule
最小负载规则,那就一定会从LoadBalancerStats中获取服务器的指标 然后动态选取负载最低的服务器。
@Override
public Server choose(Object key) {
//如果负载均衡状态机为空 那么直接用父类的线性轮询规则 来选取。
if (loadBalancerStats == null) {
return super.choose(key);
}
List<Server> serverList = getLoadBalancer().getAllServers();
int minimalConcurrentConnections = Integer.MAX_VALUE;
long currentTime = System.currentTimeMillis();
Server chosen = null;
for (Server server: serverList) {
//获取每个服务的状态
ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
//如果服务没有熔断
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
//那就获取当前的负载 下面的逻辑就是找出一个最小负载的服务器
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
//注意它这里选出来的server并没有 做可用性的判断 我们回顾一下 线性轮询规则 选出来的server 要进行
//(server.isAlive() && (server.isReadyToServe())) 的判断。
}
}
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
我们看到上面的逻辑很简单 就是找到一个非熔断的 最小负载的机器。我们之前说的线性轮询规则 对选出来的服务器 做了可用性判断server.isAlive() && server.isReadyToServe() 这里并没有这么判断。如果你能接受选出的服务器可能是不可用的。毕竟负载为0的服务器有可能是不能用的,负载大于0的也不能说一定是可以用的
PredicateBasedRule
将服务器选取规则委托给AbstractServerPredicate 具体委托给哪一个AbstractServerPredicate的实现类 就需要PredicateBasedRule的子类来决定。
//该类定义了一个抽象方法 让子类返回一个具体的AbstractServerPredicate
public abstract AbstractServerPredicate getPredicate();
//选择这里并没有让父类兜底 如果选择不成功直接返回null
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//这里是关键 使用子类返回的断言来选择一个服务器 如果选择不到 返回null
//注意调用的是断言的chooseRoundRobinAfterFiltering方法
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
AbstractServerPredicate
这个断言其实我们在前面已经介绍过他的一个子类ZoneAffinityPredicate。这里我们再详细的对这个断言做一个介绍:
AbstractServerPredicate:服务器过滤逻辑的基本构建块,可在规则和服务器列表过滤器中使用。 断言的输入对象是PredicateKey,它具有服务器和负载均衡器键信息。因此,可以开发逻辑来通过服务器和负载平衡器键或其中之一来过滤服务器。
仔细想一下 服务器过滤组件和规则组件并无太大的区别。甚至可以说 规则的操作是服务器过滤组件的一个特殊。所以不管是服务器过滤组件还是规则组件都是可以使用 AbstractServerPredicate 断言逻辑的。
AvailabilityPredicate
该断言具有过滤掉 熔断了的服务器***和***负载过高的服务器。我们不妨猜想一下这个类的断言逻辑:先看第一个条件 过滤熔断的服务器,那么如果灵活一点 肯定是需要一个可配置的开关的,所以这里会有一个配置 是否过滤掉熔断了的服务器。再看第二个条件: 过滤掉负载过高的服务器,负载过高是一个很抽象的概念,到底什么样的负载叫做过高。所以这里肯定会有一个配置的阈值。然后从指标统计器中拿出当前的指标来和当前两个条件对比就ok了。
阈值配置:
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);
- niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped 来配置是否过滤熔断服务器 注意默认值是true,也就是默认是会过滤掉熔断了的服务器的
- niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit 来配置负载的阈值,大于该阈值将会被过滤。请注意 默认值是Integer.MAX_VALUE 也就意味着 默认不会过滤负载过高的服务器
ZoneAvoidancePredicate
如果说AvailabilityPredicate是针对服务器做得断言 那么ZoneAvoidancePredicate就是对区域做得断言,我们知道区域会有一个或多个服务器。所以这个断言就是判断一个区域是否是一个可用的区域。那什么样的区域时不可用的区域?区域中服务器的平均负载 服务器的熔断数量 这些指标都是可以从负载均衡指标统计器中获取到的。所以应该逻辑就是 区域中的服务器平均负载大于某一个阈值 或者服务器的熔断数量大于某一个阈值 才被认为是一个不可用区域。
阈值配置
private volatile DynamicDoubleProperty triggeringLoad = new DynamicDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer.triggeringLoadPerServerThreshold", 0.2d);
private volatile DynamicDoubleProperty triggeringBlackoutPercentage = new DynamicDoubleProperty("ZoneAwareNIWSDiscoveryLoadBalancer.avoidZoneWithBlackoutPercetage", 0.99999d);
- ZoneAwareNIWSDiscoveryLoadBalancer.triggeringLoadPerServerThreshold 该配置为负载阈值 默认为0.2。
- ZoneAwareNIWSDiscoveryLoadBalancer.avoidZoneWithBlackoutPercetage 该配置决定了熔断服务器的阈值 默认为0.9999
上面两个默认值后面我们说到 ZoneAvoidanceRule 的时候还会再提到。
断言方法
@Override
public boolean apply(@Nullable PredicateKey input) {
//算是一个开关由niws.loadbalancer.zoneAvoidanceRule.enabled配置决定 默认为true
//也就是默认会执行断言逻辑
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;
}
//最主要的是ZoneAvoidanceRule类的getAvailableZones方法 这个我们后面还要重点提到
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
if (availableZones != null) {
return availableZones.contains(input.getServer().getZone());
} else {
return false;
}
}
CompositePredicate
组合断言。
属性
//代理断言 断言功能是由delegate来完成
private AbstractServerPredicate delegate;
//回退的断言 如果delegate无法满足 就会执行fallbacks中的断言
private List<AbstractServerPredicate> fallbacks = Lists.newArrayList();
//最少需要的服务器数量
private int minimalFilteredServers = 1;
//这是一个阈值 如果留下的服务器列表的数量 小于 服务总数*minimalFilteredPercentage这个值会
//触发fallbacks 默认为0 不启用
private float minimalFilteredPercentage = 0;
注意***minimalFilteredServers***和***minimalFilteredPercentage***无法通过配置来解决。
getEligibleServers
@Override
public List<Server> getEligibleServers(List<Server> servers, Object loadBalancerKey) {
//使用delegate来筛选
List<Server> result = super.getEligibleServers(servers, loadBalancerKey);
Iterator<AbstractServerPredicate> i = fallbacks.iterator();
//注意while里面的条件表达式 和我们上面说的两个配置的值有关。
while (!(result.size() >= minimalFilteredServers && result.size() > (int) (servers.size() * minimalFilteredPercentage))
&& i.hasNext()) {
AbstractServerPredicate predicate = i.next();
result = predicate.getEligibleServers(servers, loadBalancerKey);
}
return result;
}
介绍完 AbstractServerPredicate 回到今天的主角 PredicateBasedRule 它有两个实现类。ZoneAvoidanceRule 和 AvailabilityFilteringRule。
AvailabilityFilteringRule
看到这个名字 就猜到一定和上面我们说的***AvailabilityPredicate***有关。
private AbstractServerPredicate predicate;
public AvailabilityFilteringRule() {
super();
predicate = CompositePredicate.withPredicate(new AvailabilityPredicate(this, null))
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
.build();
}
@Override
public Server choose(Object key) {
int count = 0;
Server server = roundRobinRule.choose(key);
//将该服务器交给CompositePredicate来判断 当然最终交给AvailabilityPredicate
//根据当前负载和是否熔断来判断(上面已对AvailabilityPredicate 做了解释)
while (count++ <= 10) {
if (predicate.apply(new PredicateKey(server))) {
return server;
}
server = roundRobinRule.choose(key);
}
//最多选择10次如果还是不合格 那么就交给父选择器兜底
return super.choose(key);
}
AvailabilityFilteringRule 它是使用***CompositePredicate*** 来完成服务器选择的。但是最终会委托给 AvailabilityPredicate,只不过 CompositePredicate 的功能更强大不仅可以完成正常的服务器选择 而且可以支持回退。
ZoneAvoidanceRule
内部使用 ZoneAvoidancePredicate 和 AvailabilityPredicate 来完成服务器的筛选。所以会过滤掉 熔断率偏高或者平均负载过高的zone区域(zone级别的过滤),并且过滤掉已经熔断or活跃请求数过高的服务器(服务器级别的过滤)
创建 ZoneAvoidanceRule
public ZoneAvoidanceRule() {
super();
ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this);
AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this);
//创建了一个组合断言 ZoneAvoidancePredicate在前 AvailabilityPredicate在后
compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate);
}
private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) {
return CompositePredicate.withPredicates(p1, p2)
.addFallbackPredicate(p2)
.addFallbackPredicate(AbstractServerPredicate.alwaysTrue())
.build();
}
ZoneAvoidanceRule 并不是只使用某一个断言来完成筛选 而是使用 ZoneAvoidancePredicate 和 AvailabilityPredicate 组合进行筛选。回退的逻辑使用 AvailabilityPredicate。
CompositePredicate.withPredicates 最终是使用 Predicates.and(primaryPredicates) 来构建一个 Predicate。如果两个都是true才会保证最终返回true。
getAvailableZones:
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());
/**如果只有一个zone 直接返回不需要判断*/
if (availableZones.size() == 1) {
return availableZones;
}
/**保存最坏的zone*/
Set<String> worstZones = new HashSet<String>();
/**所有的zone中平均负载最高值*/
double maxLoadPerServer = 0;
/**是否有一部分可用的负载 false时意为全部可用 true的时候有限可用*/
boolean limitedZoneAvailability = false;
for (Map.Entry<String, ZoneSnapshot> zoneEntry : snapshot.entrySet()) {
String zone = zoneEntry.getKey();
ZoneSnapshot zoneSnapshot = zoneEntry.getValue();
int instanceCount = zoneSnapshot.getInstanceCount();
/**如果zone内没有可用实例 从可用的zone中移除 将部分可用赋值为true*/
if (instanceCount == 0) {
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
double loadPerServer = zoneSnapshot.getLoadPerServer();
/**如果熔断率大于传过来的阈值 或者实例的平均负载小于0 也从可用的zone中移除
* 前半部分条件:熔断实例数/总实例数 >= 阈值(阈值为0.99999d) 也就差不多是所有的server都被
* 熔断了 该zone才不可用
* 后半部分条件:loadPerServer < 0 这里是什么意思?什么情况下loadPerServer才会小于0 那就要
* 去看看 LoadBalancerStats#getZoneSnapshot()
* if (circuitBreakerTrippedCount == instanceCount)
* loadPerServer = -1
* 也就是所有的实例都熔断了 那么loadPerServer久没有任何意义了 所以就赋值为-1
* 那前半部分的条件和后半部分岂不是一样的?前半部分是可以配置的 通过下面
* ZoneAwareNIWSDiscoveryLoadBalancer.clientName.avoidZoneWithBlackoutPercetage 默认是0.99999d
* */
if (((double) zoneSnapshot.getCircuitTrippedCount())
/ instanceCount >= triggeringBlackoutPercentage
|| loadPerServer < 0) {
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
/**当前的平均负载和上一次最大的负载一样大 那就将当前的区域加入 最坏负载集合
* 这样worstZones 就会有多个值 一般情况下worstZones这个集合会出现一个值
* 但是如果有两个区域负载相同时 就会有2个值 以此类推
* */
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);
}
}
}
}
/**如果全部区域都可用 并且最大负载没达到阈值 返回全部的可用区域
* triggeringLoad 负载阈值 默认值为0.2 通过
* ZoneAwareNIWSDiscoveryLoadBalancer.triggeringLoadPerServerThreshold来设置
* 请求量/实例数 = 平均负载阈值 默认值为0.2有点不合理 加入一个区域10台服务器 有两个活跃的请求
* 这个zone的平均负载就是0.2。超过两个请求进来就会负载过重。
*
* 这么设置的后果:假如有两个zone区域 总会有一个被移除掉 会导致负载均衡策略完全失效。
* (对于这个结论我们可以在AvailableZonesTest中模拟)
*
* 所以生产环境如果有多个zone区域的话 建议不要使用默认值
* ZoneAwareNIWSDiscoveryLoadBalancer.triggeringLoadPerServerThreshold = 50
* */
if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
// zone override is not needed here
return availableZones;
}
/**
* 这一步是吧最坏的负载区域移除掉 worstZones这个集合上面说了 一般会是一个值
* 当出现最大负载有多个区域时 才会有多个值
*
* 程序走到这里 availableZones肯定是不止一个zone的。因为在最开始 就进行了判断
* availableZones 如果仅有一个值 直接返回了。
* */
String zoneToAvoid = randomChooseZone(snapshot, worstZones);
if (zoneToAvoid != null) {
availableZones.remove(zoneToAvoid);
}
return availableZones;
}
这个计算可用区域的方法非常重要 在多个地方使用到了。我们前面说到的 AvailabilityPredicate 就是依赖该方法来完成断言的。
该方法后两个参数 triggeringLoad(负载阈值), triggeringBlackoutPercentage(熔断服务器的百分比) 默认值为0.2和0.9999 。负载阈值0.2 意味着 一个区域如果有10台服务器 有3个请求就会达到这个阈值,所以默认值设置的有点小,如果启用相关区域规则的组件 这个默认值是需要注意的地方。
总结
本篇文章介绍了关于 ClientConfigEnabledRoundRobinRule(可配置的规则) 的几个子类。BestAvailableRule、ZoneAvoidanceRule、 AvailabilityFilteringRule 都是可以通过IClientConfig配置的,例如服务器的负载阈值,熔断比例等。