本文主要研究一下ribbon的ServerListSubsetFilter

ServerListSubsetFilter

ribbon-loadbalancer-2.3.0-sources.jar!/com/netflix/loadbalancer/ServerListSubsetFilter.java

public class ServerListSubsetFilter<T extends Server> extends ZoneAffinityServerListFilter<T> implements IClientConfigAware, Comparator<T>{    private Random random = new Random();    private volatile Set<T> currentSubset = Sets.newHashSet();     private DynamicIntProperty sizeProp = new DynamicIntProperty(DefaultClientConfigImpl.DEFAULT_PROPERTY_NAME_SPACE + ".ServerListSubsetFilter.size", 20);    private DynamicFloatProperty eliminationPercent =             new DynamicFloatProperty(DefaultClientConfigImpl.DEFAULT_PROPERTY_NAME_SPACE + ".ServerListSubsetFilter.forceEliminatePercent", 0.1f);    private DynamicIntProperty eliminationFailureCountThreshold =             new DynamicIntProperty(DefaultClientConfigImpl.DEFAULT_PROPERTY_NAME_SPACE + ".ServerListSubsetFilter.eliminationFailureThresold", 0);    private DynamicIntProperty eliminationConnectionCountThreshold =             new DynamicIntProperty(DefaultClientConfigImpl.DEFAULT_PROPERTY_NAME_SPACE + ".ServerListSubsetFilter.eliminationConnectionThresold", 0);        @Override    public void initWithNiwsConfig(IClientConfig clientConfig) {        super.initWithNiwsConfig(clientConfig);        sizeProp = new DynamicIntProperty(clientConfig.getClientName() + "." + clientConfig.getNameSpace() + ".ServerListSubsetFilter.size", 20);        eliminationPercent =                 new DynamicFloatProperty(clientConfig.getClientName() + "." + clientConfig.getNameSpace() + ".ServerListSubsetFilter.forceEliminatePercent", 0.1f);        eliminationFailureCountThreshold = new DynamicIntProperty( clientConfig.getClientName() + "." + clientConfig.getNameSpace()                + ".ServerListSubsetFilter.eliminationFailureThresold", 0);        eliminationConnectionCountThreshold = new DynamicIntProperty(clientConfig.getClientName() + "." + clientConfig.getNameSpace()                + ".ServerListSubsetFilter.eliminationConnectionThresold", 0);    }            /**     * Given all the servers, keep only a stable subset of servers to use. This method     * keeps the current list of subset in use and keep returning the same list, with exceptions     * to relatively unhealthy servers, which are defined as the following:     * <p>     * <ul>     * <li>Servers with their concurrent connection count exceeding the client configuration for      * {@code <clientName>.<nameSpace>.ServerListSubsetFilter.eliminationConnectionThresold} (default is 0)     * <li>Servers with their failure count exceeding the client configuration for      * {@code <clientName>.<nameSpace>.ServerListSubsetFilter.eliminationFailureThresold} (default is 0)     * <li>If the servers evicted above is less than the forced eviction percentage as defined by client configuration     * {@code <clientName>.<nameSpace>.ServerListSubsetFilter.forceEliminatePercent} (default is 10%, or 0.1), the     * remaining servers will be sorted by their health status and servers will worst health status will be     * forced evicted.     * </ul>     * <p>     * After the elimination, new servers will be randomly chosen from all servers pool to keep the     * number of the subset unchanged.      *      */    @Override    public List<T> getFilteredListOfServers(List<T> servers) {        List<T> zoneAffinityFiltered = super.getFilteredListOfServers(servers);        Set<T> candidates = Sets.newHashSet(zoneAffinityFiltered);        Set<T> newSubSet = Sets.newHashSet(currentSubset);        LoadBalancerStats lbStats = getLoadBalancerStats();        for (T server: currentSubset) {            // this server is either down or out of service            if (!candidates.contains(server)) {                newSubSet.remove(server);            } else {                ServerStats stats = lbStats.getSingleServerStat(server);                // remove the servers that do not meet health criteria                if (stats.getActiveRequestsCount() > eliminationConnectionCountThreshold.get()                        || stats.getFailureCount() > eliminationFailureCountThreshold.get()) {                    newSubSet.remove(server);                    // also remove from the general pool to avoid selecting them again                    candidates.remove(server);                }            }        }        int targetedListSize = sizeProp.get();        int numEliminated = currentSubset.size() - newSubSet.size();        int minElimination = (int) (targetedListSize * eliminationPercent.get());        int numToForceEliminate = 0;        if (targetedListSize < newSubSet.size()) {            // size is shrinking            numToForceEliminate = newSubSet.size() - targetedListSize;        } else if (minElimination > numEliminated) {            numToForceEliminate = minElimination - numEliminated;         }                if (numToForceEliminate > newSubSet.size()) {            numToForceEliminate = newSubSet.size();        }        if (numToForceEliminate > 0) {            List<T> sortedSubSet = Lists.newArrayList(newSubSet);                       Collections.sort(sortedSubSet, this);            List<T> forceEliminated = sortedSubSet.subList(0, numToForceEliminate);            newSubSet.removeAll(forceEliminated);            candidates.removeAll(forceEliminated);        }                // after forced elimination or elimination of unhealthy instances,        // the size of the set may be less than the targeted size,        // then we just randomly add servers from the big pool        if (newSubSet.size() < targetedListSize) {            int numToChoose = targetedListSize - newSubSet.size();            candidates.removeAll(newSubSet);            if (numToChoose > candidates.size()) {                // Not enough healthy instances to choose, fallback to use the                // total server pool                candidates = Sets.newHashSet(zoneAffinityFiltered);                candidates.removeAll(newSubSet);            }            List<T> chosen = randomChoose(Lists.newArrayList(candidates), numToChoose);            for (T server: chosen) {                newSubSet.add(server);            }        }        currentSubset = newSubSet;               return Lists.newArrayList(newSubSet);                }    /**     * Randomly shuffle the beginning portion of server list (according to the number passed into the method)      * and return them.     *       * @param servers     * @param toChoose     * @return     */    private List<T> randomChoose(List<T> servers, int toChoose) {        int size = servers.size();        if (toChoose >= size || toChoose < 0) {            return servers;        }         for (int i = 0; i < toChoose; i++) {            int index = random.nextInt(size);            T tmp = servers.get(index);            servers.set(index, servers.get(i));            servers.set(i, tmp);        }        return servers.subList(0, toChoose);            }    /**     * Function to sort the list by server health condition, with     * unhealthy servers before healthy servers. The servers are first sorted by     * failures count, and then concurrent connection count.     */    @Override    public int compare(T server1, T server2) {        LoadBalancerStats lbStats = getLoadBalancerStats();        ServerStats stats1 = lbStats.getSingleServerStat(server1);        ServerStats stats2 = lbStats.getSingleServerStat(server2);        int failuresDiff = (int) (stats2.getFailureCount() - stats1.getFailureCount());        if (failuresDiff != 0) {            return failuresDiff;        } else {            return (stats2.getActiveRequestsCount() - stats1.getActiveRequestsCount());        }    }}
  • ServerListSubsetFilter继承了ZoneAffinityServerListFilter,实现了IClientConfigAware、Comparator接口
  • initWithNiwsConfig方法从IClientConfig读取了ServerListSubsetFilter.size、ServerListSubsetFilter.forceEliminatePercent、ServerListSubsetFilter.eliminationFailureThresold、ServerListSubsetFilter.eliminationConnectionThresold配置
  • getFilteredListOfServers方法会先调用父类ZoneAffinityServerListFilter的getFilteredListOfServers先过滤出zoneAffinityFiltered作为candidates,然后遍历currentSubset根据ServerStats剔除activeRequestsCount及failureCount超出阈值的server;然后根据numToForceEliminate,以及failureCount排序,使用subList方法得出forceEliminated,然后移除掉,最后randomChoose出来新的newSubSet,然后重置currentSubset

小结

在server list非常多的场景下,没有必要在连接池的保持这么多的连接,ServerListSubsetFilter可以在这种场景下对server list进行精简,通过剔除相对不健康(failureCount、activeRequestCount)的server来达到此目标

doc

  • ServerListSubsetFilter