Ribbon架构篇 - ZoneAvoidanceRule

前言

ZoneAvoidanceRule 基于分区下的服务器的可用性选出可用分区列表,再从可用分区列表中随机选择一个分区,采用轮询的策略选择该分区的一个服务器。

ZoneAwareLoadBalancer

属性 属性描述 默认值
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、先获取可用分区集合。

可用分区选取的策略如下:

  • 如果只有一个分区,则选择该分区作为可用分区。
  • 如果有多个分区,满足分区下的服务器数量大于0、服务器故障率小于阈值的、再随机排除一个服务器的负载大于平均负载的分区,作为可用分区,添加到可用分区集合中。

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;
}

判断断路器是否打开。

策略如下:

  • 断路器处于关闭状态:
    • 断路器打开的超时截止时间 <= 0
    • 0 < 断路器打开的超时截止时间 <= 当前时间
  • 断路器处于开启状态:
    • 断路器打开的超时截止时间 > 当前时间
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;
    }
}

你可能感兴趣的:(Ribbon,java,spring,cloud)