聊聊eureka的ZoneAffinityServerListFilter

weixin_33670713發表於2018-05-22

本文主要研究一下eureka的ZoneAffinityServerListFilter

ZoneAffinityServerListFilter

ribbon-loadbalancer-2.2.5-sources.jar!/com/netflix/loadbalancer/ZoneAffinityServerListFilter.java

/**
 * This server list filter deals with filtering out servers based on the Zone affinity. 
 * This filtering will be turned on if either {@link CommonClientConfigKey#EnableZoneAffinity} 
 * or {@link CommonClientConfigKey#EnableZoneExclusivity} is set to true in {@link IClientConfig} object
 * passed into this class during initialization. When turned on, servers outside the same zone (as 
 * indicated by {@link Server#getZone()}) will be filtered out. By default, zone affinity 
 * and exclusivity are turned off and nothing is filtered out.
 * 
 * @author stonse
 *
 */
public class ZoneAffinityServerListFilter<T extends Server> extends
        AbstractServerListFilter<T> implements IClientConfigAware {
     //......
    @Override
    public List<T> getFilteredListOfServers(List<T> servers) {
        if (zone != null && (zoneAffinity || zoneExclusive) && servers !=null && servers.size() > 0){
            List<T> filteredServers = Lists.newArrayList(Iterables.filter(
                    servers, this.zoneAffinityPredicate.getServerOnlyPredicate()));
            if (shouldEnableZoneAffinity(filteredServers)) {
                return filteredServers;
            } else if (zoneAffinity) {
                overrideCounter.increment();
            }
        }
        return servers;
    }   
}

可以看到這裡首先呼叫了zoneAffinityPredicate.getServerOnlyPredicate()進行過濾;然後再呼叫shouldEnableZoneAffinity判斷是否真的需要返回過濾後的資料。

ZoneAffinityPredicate

ribbon-loadbalancer-2.2.5-sources.jar!/com/netflix/loadbalancer/ZoneAffinityPredicate.java

/**
 * A predicate the filters out servers that are not in the same zone as the client's current
 * zone. The current zone is determined from the call
 * 
 * <pre>{@code
 * ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone);
 * }</pre>
 * 
 * @author awang
 *
 */
public class ZoneAffinityPredicate extends AbstractServerPredicate {

    private final String zone = ConfigurationManager.getDeploymentContext().getValue(ContextKey.zone);
    
    public ZoneAffinityPredicate() {        
    }

    @Override
    public boolean apply(PredicateKey input) {
        Server s = input.getServer();
        String az = s.getZone();
        if (az != null && zone != null && az.toLowerCase().equals(zone.toLowerCase())) {
            return true;
        } else {
            return false;
        }
    }
}

可以看到這裡對server的zone進行判斷,相同的zone才選取出來。

shouldEnableZoneAffinity

    private boolean shouldEnableZoneAffinity(List<T> filtered) {    
        if (!zoneAffinity && !zoneExclusive) {
            return false;
        }
        if (zoneExclusive) {
            return true;
        }
        LoadBalancerStats stats = getLoadBalancerStats();
        if (stats == null) {
            return zoneAffinity;
        } else {
            logger.debug("Determining if zone affinity should be enabled with given server list: {}", filtered);
            ZoneSnapshot snapshot = stats.getZoneSnapshot(filtered);
            double loadPerServer = snapshot.getLoadPerServer();
            int instanceCount = snapshot.getInstanceCount();            
            int circuitBreakerTrippedCount = snapshot.getCircuitTrippedCount();
            if (((double) circuitBreakerTrippedCount) / instanceCount >= blackOutServerPercentageThreshold.get() 
                    || loadPerServer >= activeReqeustsPerServerThreshold.get()
                    || (instanceCount - circuitBreakerTrippedCount) < availableServersThreshold.get()) {
                logger.debug("zoneAffinity is overriden. blackOutServerPercentage: {}, activeReqeustsPerServer: {}, availableServers: {}", 
                        new Object[] {(double) circuitBreakerTrippedCount / instanceCount,  loadPerServer, instanceCount - circuitBreakerTrippedCount});
                return false;
            } else {
                return true;
            }
            
        }
    }

這裡進行判斷,如果目標zone的server統計資料不是太好,達到斷路的標準,則不會返回該zone的server。

ZonePreferenceServerListFilter

spring-cloud-netflix-ribbon-2.0.0.RC1-sources.jar!/org/springframework/cloud/netflix/ribbon/ZonePreferenceServerListFilter.java

/**
 * A filter that actively prefers the local zone (as defined by the deployment context, or
 * the Eureka instance metadata).
 *
 * @author Dave Syer
 */
public class ZonePreferenceServerListFilter extends ZoneAffinityServerListFilter<Server> {
    //......
    @Override
    public List<Server> getFilteredListOfServers(List<Server> servers) {
        List<Server> output = super.getFilteredListOfServers(servers);
        if (this.zone != null && output.size() == servers.size()) {
            List<Server> local = new ArrayList<>();
            for (Server server : output) {
                if (this.zone.equalsIgnoreCase(server.getZone())) {
                    local.add(server);
                }
            }
            if (!local.isEmpty()) {
                return local;
            }
        }
        return output;
    }
}

Spring Cloud的ZonePreferenceServerListFilter繼承了eureka的ZoneAffinityServerListFilter,重寫了getFilteredListOfServers方法,即eureka的ZoneAffinityServerListFilter計算出來沒有根據zone過濾的話,那麼它會再過濾一次,選出跟例項相同zone的server。注意這裡進行了判斷,如根據zone過濾出來為空,則返回父類過濾出來server,即不再根據zone進行過濾。

小結

eureka提供了ZoneAffinityServerListFilter,可以對server進行zone親和性過濾,同時還會根據server的健康統計判斷是否需要使用進行zone親和過濾後的server,如果不應該開啟,則返回沒有過濾的server列表。比如沒有跟該例項相同zone的server列表,那麼很明顯就是不應該返回根據zone進行過濾後的空列表。

doc

相關文章