[源码阅读]Motan RPC中的负载均衡算法|Random/RoundRobin/ActiveWeight/LocalFirst/ConsistentHash/ConfigurableWeight

Motan RPC框架中的六个负载均衡算法预览

[源码阅读]Motan RPC中的负载均衡算法|Random/RoundRobin/ActiveWeight/LocalFirst/ConsistentHash/ConfigurableWeight_第1张图片
[源码阅读]Motan RPC中的负载均衡算法|Random/RoundRobin/ActiveWeight/LocalFirst/ConsistentHash/ConfigurableWeight_第2张图片
6个负载均衡算法的总结:
[源码阅读]Motan RPC中的负载均衡算法|Random/RoundRobin/ActiveWeight/LocalFirst/ConsistentHash/ConfigurableWeight_第3张图片
RandomLoadBalance

  • 负载随机分配
  • ThreadLocalRandom提高多线程并发随机数性能

RoundRobinLoadBalance

  • 负载轮流分配
  • AtomicInteger idx 保存状态

ActiveWeightLoadBalance

  • 低并发优先
  • 避免O(N)查找极值

LocalFirstLoadBalance

  • IP筛选 本机优先
  • 退化为ActiveWeightLoadBalance

ConsistentHashLoadBalance

  • 对参数哈希
  • 虚拟节点扩充

ConfigurableWeightLoadBalance

  • 按分组配置权重,组内RoundRobinLoadBalance
  • 未配置权重则退化为RandomLoadBalance

用到的设计模式

使用SPI的策略模式

模板方法模式

AbstractLoadBalance抽象类固定流程,扩展点通过抽象方法留给子类实现
固定流程:

    @Override
    public Referer<T> select(Request request) {
        List<Referer<T>> referers = this.referers;
        if (referers == null) {
            throw new MotanServiceException(this.getClass().getSimpleName() + " No available referers for call request:" + request);
        }
        Referer<T> ref = null;
        if (referers.size() > 1) {
            ref = doSelect(request);

        } else if (referers.size() == 1) {
            ref = referers.get(0).isAvailable() ? referers.get(0) : null;
        }

        if (ref != null) {
            return ref;
        }
        throw new MotanServiceException(this.getClass().getSimpleName() + " No available referers for call request:" + request);
    }

扩展点留出:

    protected abstract Referer<T> doSelect(Request request);

    protected abstract void doSelectToHolder(Request request, List<Referer<T>> refersHolder);

负载均衡顶层接口

@Spi(scope = Scope.PROTOTYPE)
public interface LoadBalance<T> {

    void onRefresh(List<Referer<T>> referers);

    Referer<T> select(Request request);

    void selectToHolder(Request request, List<Referer<T>> refersHolder);

    void setWeightString(String weightString);

}
  • onRefresh:更新服务提供者
  • select:按照负载均衡算法选择具体的服务提供者
  • selectToHolder:按照负载均衡算个从服务提供者列表中按序批量选取服务提供者
  • setWeightString:设置权重信息

随机负载均衡算法:RandomLoadBalance

核心思想:从服务提供者列表中随机选取一个提供服务,Random 使用 AtomicLong CAS (compare-and-set)操作来更新它的seed,CAS在资源高度竞争时存在性能问题。RandomLoadBalance算法使用了ThreadLocalRandom随机类规避了Random在多线程激烈竞争下的性能问题。
ThreadLocalRandom设计有两个要点:一是通过seed是线程隔离的,不存在竞争,二是通过缓存行填充手段避免伪共享问题。

@SpiMeta(name = "random")
public class RandomLoadBalance<T> extends AbstractLoadBalance<T> {

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        int idx = (int) (ThreadLocalRandom.current().nextDouble() * referers.size());
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> ref = referers.get((i + idx) % referers.size());
            if (ref.isAvailable()) {
                return ref;
            }
        }
        return null;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        List<Referer<T>> referers = getReferers();

        int idx = (int) (ThreadLocalRandom.current().nextDouble() * referers.size());
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> referer = referers.get((i + idx) % referers.size());
            if (referer.isAvailable()) {
                refersHolder.add(referer);
            }
        }
    }
}

轮询负载均衡算法:RoundRobinLoadBalance

核心思想:服务提供者轮流提供服务,因为要轮流提供服务,所以该算法是有状态的,需要记录上次服务提供者。RoundRobinLoadBalance算法中使用AtomicInteger类型的idx变量记录服务提供者的服务顺序,依次递增。

@SpiMeta(name = "roundrobin")
public class RoundRobinLoadBalance<T> extends AbstractLoadBalance<T> {

    private AtomicInteger idx = new AtomicInteger(0);

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        int index = getNextNonNegative();
        for (int i = 0; i < referers.size(); i++) {
            Referer<T> ref = referers.get((i + index) % referers.size());
            if (ref.isAvailable()) {
                return ref;
            }
        }
        return null;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        List<Referer<T>> referers = getReferers();

        int index = getNextNonNegative();
        for (int i = 0, count = 0; i < referers.size() && count < MAX_REFERER_COUNT; i++) {
            Referer<T> referer = referers.get((i + index) % referers.size());
            if (referer.isAvailable()) {
                refersHolder.add(referer);
                count++;
            }
        }
    }

    // get non-negative int
    private int getNextNonNegative() {
        return MathUtil.getNonNegative(idx.incrementAndGet());
    }
}

低并发优先负载均衡算法:ActiveWeightLoadBalance

核心思想:优先将负载分配给并发度低的服务提供者。
基本思想和原理:
Motan记录了服务提供者的并发度,大致记录方式如下:

 		incrActiveCount(request);
        Response response = null;
        try {
            response = doCall(request);
            return response;
        } finally {
            decrActiveCount(request, response);
        }

因此可以按照服务提供者的并发度进行负载分配。同时由于Provider可能比较多,在众多的Provider中找出并发度最低的Provider,时间复杂度是O(N),为了避免N太大是对于请求响应的影响,Motan的做法是随机从Provider列表中选取一段(连续10个),在这一段中找出并发度最低的Provider进行请求的响应。

@SpiMeta(name = "activeWeight")
public class ActiveWeightLoadBalance<T> extends AbstractLoadBalance<T> {

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        int refererSize = referers.size();
        int startIndex = ThreadLocalRandom.current().nextInt(refererSize);
        int currentCursor = 0;
        int currentAvailableCursor = 0;

        Referer<T> referer = null;

        while (currentAvailableCursor < MAX_REFERER_COUNT && currentCursor < refererSize) {
            Referer<T> temp = referers.get((startIndex + currentCursor) % refererSize);
            currentCursor++;

            if (!temp.isAvailable()) {
                continue;
            }

            currentAvailableCursor++;

            if (referer == null) {
                referer = temp;
            } else {
                if (compare(referer, temp) > 0) {
                    referer = temp;
                }
            }
        }

        return referer;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
       //略
    }

    private int compare(Referer<T> referer1, Referer<T> referer2) {
        return referer1.activeRefererCount() - referer2.activeRefererCount();
    }

    static class LowActivePriorityComparator<T> implements Comparator<Referer<T>> {
        @Override
        public int compare(Referer<T> referer1, Referer<T> referer2) {
            return referer1.activeRefererCount() - referer2.activeRefererCount();
        }
    }
}

一致性哈希负载均衡算法:ConsistentHashLoadBalance

核心思想:将服务节点放置在一个环上,对请求参数计算hash值,请求参数hash值相同的请求,会定位到环中相同的节点,如果实际服务提供者较少,比如只有4个,因此需要对请求参数的hash值对4进行求模,余数就是服务提供者,节点较少,极容易造成数据倾斜。因此Motan的做法是对将节点数扩大1000倍。假如有a、b、c、d四个实际服务节点,环上将会存在4个实际服务节点和4*1000-4=3996个虚拟节点,使负载分配更加均匀。

@SpiMeta(name = "consistent")
public class ConsistentHashLoadBalance<T> extends AbstractLoadBalance<T> {

    private List<Referer<T>> consistentHashReferers;

    @Override
    public void onRefresh(List<Referer<T>> referers) {
        super.onRefresh(referers);

        List<Referer<T>> copyReferers = new ArrayList<Referer<T>>(referers);
        List<Referer<T>> tempRefers = new ArrayList<Referer<T>>();
        for (int i = 0; i < MotanConstants.DEFAULT_CONSISTENT_HASH_BASE_LOOP; i++) {
            Collections.shuffle(copyReferers);
            for (Referer<T> ref : copyReferers) {
                tempRefers.add(ref);
            }
        }
        consistentHashReferers = tempRefers;
    }

    @Override
    protected Referer<T> doSelect(Request request) {

        int hash = getHash(request);
        Referer<T> ref;
        for (int i = 0; i < getReferers().size(); i++) {
            ref = consistentHashReferers.get((hash + i) % consistentHashReferers.size());
            if (ref.isAvailable()) {
                return ref;
            }
        }
        return null;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        //略
    }

    private int getHash(Request request) {
        int hashcode;
        if (request.getArguments() == null || request.getArguments().length == 0) {
            hashcode = request.hashCode();
        } else {
            hashcode = Arrays.hashCode(request.getArguments());
        }
        return MathUtil.getNonNegative(hashcode);
    }
}

按服务分组分配权重的负载均衡算法:ConfigurableWeightLoadBalance

核心思想:分组权重和被分配到负载的概率是线性相关,组内采用RoundRobinLoadBalance策略。

        @Override
        Referer<T> next() {
            //分组权重和被分配到负载的概率是线性相关
            String group = randomKeyList.get(ThreadLocalRandom.current().nextInt(randomKeySize));
            //按照权重找到了当前请求的服务提供组
            AtomicInteger ai = cursors.get(group);
            List<Referer<T>> referers = groupReferers.get(group);
            //组内RoundRobinLoadBalance
            return referers.get(MathUtil.getNonNegative(ai.getAndIncrement()) % referers.size());
        }
核心字段/数据结构

weightString:表示权重配置的字符串,如groupA:3,groupB:4,groupC:5,权重配置顾名思义
MultiGroupHolder:存储分组权重信息的holder类
关键字段:
List< String > randomKeyList:存储的是分组名,分组名会有重复,分组权重越大,重复的越多,下边会举例说明。
Map cursors:存储每个分组的当前服务提供者下标,每次请求下标递增,组内轮流提供服务实现负载均衡。
Map> groupReferers:存储每个分组的服务提供者,key是分组名,value是该分组服务提供者列表。
配置示例:
groupA:3,groupB:4,groupC:5
经过解析,
randomKeyList=[groupA,groupA,groupA,groupB,groupB,groupB,groupB,groupC,groupC,groupC,groupC,groupC]
该列表会使用java.util.Collections.shuffle(java.util.List)方法进行打散重新洗牌。

上边举例为各组权重最大公约数为1的情况,如果最小公倍数不是1,每组的权重需要除以最大公约数作为最终的权重。
配置示例:
groupA:2,groupB:4,groupC:8
算出2,4,8的最大公约数是2,因此实际存储时会将权重存储为1,2,4。

最大公约数算法:辗转相除法

利用了最大公约数的两个性质:
若 r 是 a ÷ b 的余数, 则 gcd(a,b) = gcd(b,r)
a 和其倍数之最大公约数为 a。
example:
f[10,4]=f[4,10%4]=f[4,2]=f[2,4%2]=f[2,0]=2
f[3,4]=f[4,3%4]=f[4,3]=f[3,4%3]=f[3,1]=f[1,3%1]=f[1,0]=1

    	// 求最大公约数
        private int findGcd(int n, int m) {
            return (n == 0 || m == 0) ? n + m : findGcd(m, n % m);
        }

        // 求最大公约数
        private int findGcd(int[] arr) {
            int i = 0;
            for (; i < arr.length - 1; i++) {
                arr[i + 1] = findGcd(arr[i], arr[i + 1]);
            }
            return findGcd(arr[i], arr[i - 1]);
        }

ConfigurableWeightLoadBalance算法中,按照参数会有三种Holder:

  • emptyHolder:无服务提供者,不需考虑
  • SingleGroupHolder:未配置分组权重,退化为RandomLoadBalance
  • MultiGroupHolder:配置分组不同权重,组内策略为RoundRobinLoadBalance
    public void onRefresh(List<Referer<T>> referers) {
        super.onRefresh(referers);

        if (CollectionUtil.isEmpty(referers)) {
            holder = emptyHolder;
        } else if (StringUtils.isEmpty(weightString)) {
            holder = new SingleGroupHolder<T>(referers);
        } else {
            holder = new MultiGroupHolder<T>(weightString, referers);
        }
    }

MultiGroupHolder核心功能:解析分组权重配置参数,填充randomKeyList(分组权重和被分配到负载的概率是线性相关具体实现)和groupReferers(每个分组的服务提供者)等数据结构。

class MultiGroupHolder<T> extends RefererListCacheHolder<T> {

        private int randomKeySize = 0;
        private List<String> randomKeyList = new ArrayList<String>();
        private Map<String, AtomicInteger> cursors = new HashMap<String, AtomicInteger>();
        private Map<String, List<Referer<T>>> groupReferers = new HashMap<String, List<Referer<T>>>();

        MultiGroupHolder(String weights, List<Referer<T>> list) {
            LoggerUtil.info("ConfigurableWeightLoadBalance build new MultiGroupHolder. weights:" + weights);
            //weights=groupA:2,groupB:4,groupC:8
            String[] groupsAndWeights = weights.split(",");
            //weightsArr=[2,4,8]
            int[] weightsArr = new int[groupsAndWeights.length];
            //weightsMap={groupA=1,groupB=2,groupC=4}
            Map<String, Integer> weightsMap = new HashMap<String, Integer>(groupsAndWeights.length);
            int i = 0;
            for (String groupAndWeight : groupsAndWeights) {
                String[] gw = groupAndWeight.split(":");
                if (gw.length == 2) {
                    Integer w = Integer.valueOf(gw[1]);
                    weightsMap.put(gw[0], w);
                    groupReferers.put(gw[0], new ArrayList<Referer<T>>());
                    weightsArr[i++] = w;
                }
            }

            // 求出最大公约数,若不为1,每个分组的权重均除以最大公约数
            int weightGcd = findGcd(weightsArr);
            if (weightGcd != 1) {
                for (Map.Entry<String, Integer> entry : weightsMap.entrySet()) {
                    weightsMap.put(entry.getKey(), entry.getValue() / weightGcd);
                }
            }

            //最大公约数为1的情况,不需要额外处理
            for (Map.Entry<String, Integer> entry : weightsMap.entrySet()) {
                for (int j = 0; j < entry.getValue(); j++) {
                    randomKeyList.add(entry.getKey());
                }
            }
            //重新打散洗牌
            Collections.shuffle(randomKeyList);
            randomKeySize = randomKeyList.size();

            //每个分组的当前请求服务提供者下标初始化为0
            for (String key : weightsMap.keySet()) {
                cursors.put(key, new AtomicInteger(0));
            }
            //将服务提供者分类存储
            for (Referer<T> referer : list) {
                groupReferers.get(referer.getServiceUrl().getGroup()).add(referer);
            }
        }

        @Override
        Referer<T> next() {
            //分组权重和被分配到负载的概率是线性相关
            String group = randomKeyList.get(ThreadLocalRandom.current().nextInt(randomKeySize));
            //按照权重找到了当前请求的服务提供组
            AtomicInteger ai = cursors.get(group);
            List<Referer<T>> referers = groupReferers.get(group);
            //组内RoundRobinLoadBalance
            return referers.get(MathUtil.getNonNegative(ai.getAndIncrement()) % referers.size());
        }

        // 求最大公约数
        private int findGcd(int n, int m) {
            return (n == 0 || m == 0) ? n + m : findGcd(m, n % m);
        }

        // 求最大公约数
        private int findGcd(int[] arr) {
            int i = 0;
            for (; i < arr.length - 1; i++) {
                arr[i + 1] = findGcd(arr[i], arr[i + 1]);
            }
            return findGcd(arr[i], arr[i - 1]);
        }

    }

本机优先负载均衡算法:LocalFirstLoadBalance

核心思想:针对Consumer和Provider部署在同一台机器上的场景,可以本机直接调用,避免一次网络开销。
基本原理:
Motan是通过IP匹配的,将服务提供者中IP与Consumer的IP相同的筛选出来。
极有可能存在找不到本机服务提供者的情况,Motan的兜底措施是将负载均衡算法退化为ActiveWeightLoadBalance算法。

@SpiMeta(name = "localFirst")
public class LocalFirstLoadBalance<T> extends AbstractLoadBalance<T> {
    public static final int MAX_REFERER_COUNT = 10;
    public static long ipToLong(final String addr) {
        final String[] addressBytes = addr.split("\\.");
        int length = addressBytes.length;
        if (length < 3) {
            return 0;
        }
        long ip = 0;
        try {
            for (int i = 0; i < 4; i++) {
                ip <<= 8;
                ip |= Integer.parseInt(addressBytes[i]);
            }
        } catch (Exception e) {
            LoggerUtil.warn("Warn ipToInt addr is wrong: addr=" + addr);
        }

        return ip;
    }

    @Override
    protected Referer<T> doSelect(Request request) {
        List<Referer<T>> referers = getReferers();

        List<Referer<T>> localReferers = searchLocalReferer(referers, NetUtils.getLocalAddress().getHostAddress());

        if (!localReferers.isEmpty()) {
            referers = localReferers;
        }

        int refererSize = referers.size();
        Referer<T> referer = null;

        for (int i = 0; i < refererSize; i++) {
            Referer<T> temp = referers.get(i % refererSize);

            if (!temp.isAvailable()) {
                continue;
            }

            if (referer == null) {
                referer = temp;
            } else {
                if (compare(referer, temp) > 0) {
                    referer = temp;
                }
            }
        }

        return referer;
    }

    @Override
    protected void doSelectToHolder(Request request, List<Referer<T>> refersHolder) {
        //略
    }

    private List<Referer<T>> searchLocalReferer(List<Referer<T>> referers, String localhost) {
        List<Referer<T>> localReferers = new ArrayList<Referer<T>>();
        long local = ipToLong(localhost);
        for (Referer<T> referer : referers) {
            long tmp = ipToLong(referer.getUrl().getHost());
            if (local != 0 && local == tmp) {
                if (referer.isAvailable()) {
                    localReferers.add(referer);
                }
            }
        }

        return localReferers;
    }

    private int compare(Referer<T> referer1, Referer<T> referer2) {
        return referer1.activeRefererCount() - referer2.activeRefererCount();
    }

    static class LowActivePriorityComparator<T> implements Comparator<Referer<T>> {
        @Override
        public int compare(Referer<T> referer1, Referer<T> referer2) {
            return referer1.activeRefererCount() - referer2.activeRefererCount();
        }
    }
}

你可能感兴趣的:(源码阅读)