/* * * Copyright 2013 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.netflix.loadbalancer; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import com.netflix.client.config.IClientConfig; /** * A rule that uses the a {@link CompositePredicate} to filter servers based on zone and availability. The primary predicate is composed of * a {@link ZoneAvoidancePredicate} and {@link AvailabilityPredicate}, with the fallbacks to {@link AvailabilityPredicate} * and an "always true" predicate returned from {@link AbstractServerPredicate#alwaysTrue()} * * @author awang * */ public class ZoneAvoidanceRule extends PredicateBasedRule { private static final Random random = new Random(); private CompositePredicate compositePredicate; public ZoneAvoidanceRule() { super(); ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this); AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this); compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate); } private CompositePredicate createCompositePredicate(ZoneAvoidancePredicate p1, AvailabilityPredicate p2) { return CompositePredicate.withPredicates(p1, p2) .addFallbackPredicate(p2) .addFallbackPredicate(AbstractServerPredicate.alwaysTrue()) .build(); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { ZoneAvoidancePredicate zonePredicate = new ZoneAvoidancePredicate(this, clientConfig); AvailabilityPredicate availabilityPredicate = new AvailabilityPredicate(this, clientConfig); compositePredicate = createCompositePredicate(zonePredicate, availabilityPredicate); } static Map<String, ZoneSnapshot> createSnapshot(LoadBalancerStats lbStats) { Map<String, ZoneSnapshot> map = new HashMap<String, ZoneSnapshot>(); for (String zone : lbStats.getAvailableZones()) { ZoneSnapshot snapshot = lbStats.getZoneSnapshot(zone); map.put(zone, snapshot); } return map; } 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(); if (index <= sum) { selectedZone = zone; break; } } return selectedZone; } 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(); if (instanceCount == 0) { availableZones.remove(zone); limitedZoneAvailability = true; } else { double loadPerServer = zoneSnapshot.getLoadPerServer(); if (((double) zoneSnapshot.getCircuitTrippedCount()) / instanceCount >= triggeringBlackoutPercentage || loadPerServer < 0) { availableZones.remove(zone); limitedZoneAvailability = true; } else { 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); } } } } if (maxLoadPerServer < triggeringLoad && !limitedZoneAvailability) { // zone override is not needed here return availableZones; } String zoneToAvoid = randomChooseZone(snapshot, worstZones); if (zoneToAvoid != null) { availableZones.remove(zoneToAvoid); } return availableZones; } public static Set<String> getAvailableZones(LoadBalancerStats lbStats, double triggeringLoad, double triggeringBlackoutPercentage) { if (lbStats == null) { return null; } Map<String, ZoneSnapshot> snapshot = createSnapshot(lbStats); return getAvailableZones(snapshot, triggeringLoad, triggeringBlackoutPercentage); } @Override public AbstractServerPredicate getPredicate() { return compositePredicate; } }