美文网首页
soul从入门到放弃13--divide插件的负载均衡(一)

soul从入门到放弃13--divide插件的负载均衡(一)

作者: 滴流乱转的小胖子 | 来源:发表于2021-01-29 07:07 被阅读0次

    零、前戏

    DividePlugin插件具有 Random、Hash、RoundRobin三种不同的负载均衡方式,本篇将主要介绍Random、Hash。

    一、负载配置

    • soul-examples-http修改端口,配置可以启动多个实例,分别启动8188与9188
    image
    • soul-admin的divide插件中配置负载地址
    image

    二、源码分析

    • 代码核心结构
    image

    负载均衡相关类是基于模板模式,LoadBalance接口定义模板方法select并含有SPI注解。AbstractLoadBalance是其抽象类,实现select。各个具体的实现类在doSelect中实现具体负载策略。

    • 负载调用的分析

    DividePlugin#doExecute获取匹配的负载策略,为拼凑真实请求地址做好准备。

    @Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        // some code ……
        DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
        // another some code ……
        return chain.execute(exchange);
    }
    

    LoadBalanceUtils#selector

    通过SPI的调用方式,根据soul-admin配置的algorithm,加载不同的LoadBalance实现类。

    public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) {
        LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
        // 调用AbstractLoadBalance#select
        return loadBalance.select(upstreamList, ip);
    }
    

    spi类声名

    image

    AbstractLoadBalance#select,如果备选地址为空直接返回,如果备选地址只有一条,直接返回第一条,介绍处理逻辑,节省时间。

    @Override
    public DivideUpstream select(final List<DivideUpstream> upstreamList, final String ip) {
        if (CollectionUtils.isEmpty(upstreamList)) {
            return null;
        }
        if (upstreamList.size() == 1) {
            return upstreamList.get(0);
        }
        // 进入实现类中的doSelect
        return doSelect(upstreamList, ip);
    }
    
    • Random负载分析 -- 随机负载
    @Override
    public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
        // 计算所以两个请求地址的总权重
        int totalWeight = calculateTotalWeight(upstreamList);
        // 判断两个请求的地址权重是否一样
        boolean sameWeight = isAllUpStreamSameWeight(upstreamList);
        // 两个权重不一样,负载处理路径
        if (totalWeight > 0 && !sameWeight) {
            return random(totalWeight, upstreamList);
        }
        // 总权重为0或两权重一致,负载处理路径
        return random(upstreamList);
    }
    

    两种具体random算法:

    判断请求地址列表中所有的权重是否一致,如果一致,随机选取一个地址返回。

    如果不一致,则计算在权重范围内偏移量权重与每个地址权重的差值,返回差值第一个大于0的,这样能保证大概率命中权重大的地址。

    // 所有upstreamList元素权重不一致的random算法
    private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) {
        // 从0~totalWeight,生成一个偏移量
        int offset = RANDOM.nextInt(totalWeight);
        // 循环查找,用偏移量去减去各自权重,最终取第一个偏移量为负值的服务
        for (DivideUpstream divideUpstream : upstreamList) {
            offset -= getWeight(divideUpstream);
            if (offset < 0) {
                return divideUpstream;
            }
        }
        return upstreamList.get(0);
    }
    // 所有upstreamList元素权重一致的random算法
    private DivideUpstream random(final List<DivideUpstream> upstreamList) {
        // 从0~upstreamList.size()-1 之间随机获取一个元素
        return upstreamList.get(RANDOM.nextInt(upstreamList.size()));
    }
    
    • Hash负载分析 -- 哈希负载

    Hash负载精髓是使用了ConcurrentSkipListMap数据结构, 及其tailMap(K fromKey)方法,截取Key大于等于fromKey的所有元素。

    科普贴:https://www.cnblogs.com/java-zzl/p/9767255.html

    @Override
    public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
        final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new ConcurrentSkipListMap<>();
        // 循环所有地址列表,每个地址生成VIRTUAL_NODE_NUM数目的元素,放入线程安全、key有序的treeMap中
        for (DivideUpstream address : upstreamList) {
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                long addressHash = hash("SOUL-" + address.getUpstreamUrl() + "-HASH-" + i);
                treeMap.put(addressHash, address);
            }
        }
        // 生成ip的hash值
        long hash = hash(String.valueOf(ip));
        // 获取treeMap中key大于hash的,所有记录
        SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
        // 返回符合条件的第一条
        if (!lastRing.isEmpty()) {
            return lastRing.get(lastRing.firstKey());
        }
        // 不存在key大于hash的记录,则返回treeMap中第一条
        return treeMap.firstEntry().getValue();
    }
    

    emmm…… 权重好像没有用上,怎么负载大权重的呢???????????

    三、小结

    • Random、Hash的代码还是很清晰易懂,Hash是如何做到均衡分配的?要细看下hash生成的方法?Hash的权重哪去了??
    • 日拱一卒

    相关文章

      网友评论

          本文标题:soul从入门到放弃13--divide插件的负载均衡(一)

          本文链接:https://www.haomeiwen.com/subject/esgftltx.html