ZoneAvoidanceRule 基于分区下的服务器的可用性选出可用分区列表,再从可用分区列表中随机选择一个分区,采用轮询的策略选择该分区的一个服务器。
属性 | 属性描述 | 默认值 |
---|---|---|
ZoneAwareNIWSDiscoveryLoadBalancer.enabled | 是否开启ZoneAwareLoadBalancer | true |
niws.loadbalancer.default.connectionFailureCountThreshold | 连接失败的数量的阈值 | 3 |
niws.loadbalancer.default.circuitTripTimeoutFactorSeconds | 断路器打开的超时时间因子 | 10 |
niws.loadbalancer.default.circuitTripMaxTimeoutSeconds | 断路器打开的超时时间阈值 | 30 |
ZoneAwareNIWSDiscoveryLoadBalancer.default.triggeringLoadPerServerThreshold | 每台服务器的负载的阈值 | 0.2 |
niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds | 活跃请求数发生变化的超时时间 | 600 |
ZoneAwareNIWSDiscoveryLoadBalancer.default.avoidZoneWithBlackoutPercetage | 服务器故障率阈值 | 0.99999 |
@Override
public Server chooseServer(Object key) {
// 如果没有开启 ZoneAwareLoadBalancer 或者 LoadBalancerStats记录的可用分区数 <= 1
// 则执行 BaseLoadBalancer#chooseServer(...) 的逻辑
if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
logger.debug("Zone aware logic disabled or there is only one zone");
return super.chooseServer(key);
}
Server server = null;
try {
LoadBalancerStats lbStats = getLoadBalancerStats();
// 创建分区快照
Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
logger.debug("Zone snapshots: {}", zoneSnapshot);
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);
}
// 获取可用的分区集合
Set<String> availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
logger.debug("Available zones: {}", availableZones);
if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
// 从可用的分区集合中随机选择一个分区
String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
logger.debug("Zone chosen: {}", zone);
if (zone != null) {
// 从缓存中获取分区对应的负载均衡器,初始会创建 BaseLoadBalancer 负载均衡器
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);
}
}
1、先获取可用分区集合。
可用分区选取的策略如下:
2、从可用分区集合中选择一个分区。
选择策略:累加每个可用分区中的服务器数量,基于这个总数生成一个随机数,找到对应服务器所在的分区。
3、从缓存中获取分区对应的负载均衡器,默认采用轮询的策略从服务器列表中选择一个服务器。
1.1 ZoneAvoidanceRule#createSnapshot
static Map<String, ZoneSnapshot> createSnapshot(LoadBalancerStats lbStats) {
Map<String, ZoneSnapshot> map = new HashMap<String, ZoneSnapshot>();
// 遍历 LoadBalancerStats 存储的所有可用分区(即 LoadBalancerStats#upServerListZoneMap 的所有key)
for (String zone : lbStats.getAvailableZones()) {
// 获取指定分区的分区快照
ZoneSnapshot snapshot = lbStats.getZoneSnapshot(zone);
// 将 <分区,分区快照> 加入到缓存中
map.put(zone, snapshot);
}
return map;
}
创建分区快照。
LoadBalancerStats#getZoneSnapshot
public ZoneSnapshot getZoneSnapshot(String zone) {
// 如果分区为空,则创建新的分区快照并返回
if (zone == null) {
return new ZoneSnapshot();
}
// 将分区名小写
zone = zone.toLowerCase();
// 从缓存中获取指定分区的服务器列表
List<? extends Server> currentList = upServerListZoneMap.get(zone);
// 获取指定服务器列表的分区快照
return getZoneSnapshot(currentList);
}
获取指定分区的分区快照。
public ZoneSnapshot getZoneSnapshot(List<? extends Server> servers) {
// 如果服务器列表为空或者数量为0,则创建分区快照并返回
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) {
// 从serverStatsCache缓存中获取对应服务器的状态
ServerStats stat = getSingleServerStat(server);
// 如果服务器对应的断路器打开,则记录触发断路器打开的服务器数量加一
if (stat.isCircuitBreakerTripped(currentTime)) {
circuitBreakerTrippedCount++;
// 否则累加可用的服务器的活跃连接数
} else {
activeConnectionsCountOnAvailableServer += stat.getActiveRequestsCount(currentTime);
}
// 累加服务器的活跃连接数
activeConnectionsCount += stat.getActiveRequestsCount(currentTime);
}
// 如果所有服务器对应的断路器都处于打开状态,则记录每台服务器的负载为 -1
if (circuitBreakerTrippedCount == instanceCount) {
if (instanceCount > 0) {
loadPerServer = -1;
}
// 否则记录服务器的平均负载 = 可用的服务器的活跃连接数 / 可用的服务器的数量(即 服务器数量 - 触发断路器打开的服务器数量)
} else {
loadPerServer = ((double) activeConnectionsCountOnAvailableServer) / (instanceCount - circuitBreakerTrippedCount);
}
return new ZoneSnapshot(instanceCount, circuitBreakerTrippedCount, activeConnectionsCount, loadPerServer);
}
计算服务器的平均负载。
根据服务器总数量、触发断路器打开的服务器数量、活跃连接数、服务器的平均负载创建分区快照。
ServerStats#isCircuitBreakerTripped
public boolean isCircuitBreakerTripped(long currentTime) {
// 获取断路器打开的超时截止时间
long circuitBreakerTimeout = getCircuitBreakerTimeout();
if (circuitBreakerTimeout <= 0) {
return false;
}
// 判断断路器打开的超时截止时间 > 当前时间
return circuitBreakerTimeout > currentTime;
}
判断断路器是否打开。
策略如下:
private long getCircuitBreakerTimeout() {
// 获取断路器打开的持续时间
long blackOutPeriod = getCircuitBreakerBlackoutPeriod();
if (blackOutPeriod <= 0) {
return 0;
}
// 上一次连接失败的时间 + 断路器打开的持续时间
return lastConnectionFailedTimestamp + blackOutPeriod;
}
获取断路器打开的超时截止时间。
private long getCircuitBreakerBlackoutPeriod() {
// 连续连接失败的次数
int failureCount = successiveConnectionFailureCount.get();
// 连接失败的数量的阈值(默认3)
int threshold = connectionFailureThreshold.get();
if (failureCount < threshold) {
return 0;
}
int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
// 2 * diff * 断路器打开的超时时间因子
int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
blackOutSeconds = maxCircuitTrippedTimeout.get();
}
return blackOutSeconds * 1000L;
}
niws.loadbalancer.default.connectionFailureCountThreshold:连接失败的数量的阈值,默认3
niws.loadbalancer.default.circuitTripTimeoutFactorSeconds:断路器打开的超时时间因子,默认10
niws.loadbalancer.default.circuitTripMaxTimeoutSeconds:断路器打开的超时时间阈值,默认30
获取断路器打开的持续时间。
策略如下:
如果连续连接失败的次数 < 连续失败的数量的阈值(默认3),返回0。
计算 diff:【16, 连续连接失败的次数 - 连续失败的数量的阈值(默认3)】取最小值。
计算断路器打开的持续时间:【2 * diff * 断路器打开的超时因子,断路器打开的超时时间阈值】取最小值。
ServerStats#getActiveRequestCount
public int getActiveRequestsCount(long currentTime) {
// 活跃请求数
int count = activeRequestsCount.get();
if (count == 0) {
return 0;
// 如果活跃请求数 > 0 并且 当前时间 - 上一次的活跃请求数发生变化的时间 > 活跃请求数发生变化的超时时间,
// 则重置活跃请求数为0
} else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) {
activeRequestsCount.set(0);
return 0;
} else {
return count;
}
}
niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds:活跃请求数发生变化的超时时间,默认600
获取活跃请求数。
1.2 ZoneAvoidanceRule#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());
// 如果分区快照中只有一个分区,则直接返回该分区
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();
// 如果分区快照中记录的服务器数量为0,则从可用分区集合中剔除该分区,并且设置分区可用性受限
if (instanceCount == 0) {
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
double loadPerServer = zoneSnapshot.getLoadPerServer();
// 如果 触发断路器打开的服务器数量 / 服务器总数量 >= 服务器故障率阈值 或者 每台服务器的负载 < 0,
// 则从可用分区集合中移除该分区,并且设置分区可用性受限
if (((double) zoneSnapshot.getCircuitTrippedCount())
/ instanceCount >= triggeringBlackoutPercentage
|| loadPerServer < 0) {
availableZones.remove(zone);
limitedZoneAvailability = true;
} else {
// 如果每台服务器的负载接近最大负载
if (Math.abs(loadPerServer - maxLoadPerServer) < 0.000001d) {
// 从最差的分区集合中添加该分区
worstZones.add(zone);
// 如果每台服务器的负载 > 最大负载
} else if (loadPerServer > maxLoadPerServer) {
// 重置最大负载
maxLoadPerServer = loadPerServer;
// 清空最差的分区集合
worstZones.clear();
// 从最差的分区集合中添加该分区
worstZones.add(zone);
}
}
}
}
if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) {
return availableZones;
}
// 从最差的分区集合中随机选择一个分区
String zoneToAvoid = randomChooseZone(snapshot, worstZones);
if (zoneToAvoid != null) {
// 从可用分区中移除该分区
availableZones.remove(zoneToAvoid);
}
return availableZones;
}
ZoneAwareNIWSDiscoveryLoadBalancer.default.avoidZoneWithBlackoutPercetage:服务器故障率,默认0.99999d。
获取可用分区。
可用分区选取的策略如下:
如果只有一个分区,则选择该分区作为可用分区。
如果有多个分区,满足分区下的服务器数量大于0、服务器故障率小于阈值的、再随机排除一个服务器的负载大于平均负载的分区,作为可用分区。
1.3 ZoneAvoidanceRule#randomChooseZone
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();
// 如果随机数小于等于上面的sum,则返回该分区
if (index <= sum) {
selectedZone = zone;
break;
}
}
return selectedZone;
}
累加每个可用分区中的服务器数量,基于这个总数生成一个随机数,找到对应服务器所在的分区。
1.4 ZoneAwareLoadBalancer#getLoadBalancer
@VisibleForTesting
BaseLoadBalancer getLoadBalancer(String zone) {
zone = zone.toLowerCase();
// 从缓存中获取分区对应的负载均衡器
BaseLoadBalancer loadBalancer = balancers.get(zone);
// 如果缓存未命中
if (loadBalancer == null) {
IRule rule = cloneRule(this.getRule());
// 创建新的负载均衡器
loadBalancer = new BaseLoadBalancer(this.getName() + "_" + zone, rule, this.getLoadBalancerStats());
// 放到缓存中
BaseLoadBalancer prev = balancers.putIfAbsent(zone, loadBalancer);
if (prev != null) {
loadBalancer = prev;
}
}
return loadBalancer;
}
1.5 BaseLoadBalancer#chooseServer
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
// PredicateBasedRule#choose
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
1.6 PredicateBasedRule#choose
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
AbstractServerPredicate
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
// 采用轮询的策略选择服务器
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
// 采用轮询的策略
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}