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

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

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

一、前戏

本篇是divide插件的三大负载均衡算法的最后一种的分析,也是相对前两种理解上稍微难一点。

RoundRobin(轮询)通过遍历自增每个请求连接的当前权重,选出最高权重连接被使用,同时通过与总权重的差值,来降低其当前权重,从而达到负载均衡的目的。

二、源码分析

// 以选择器中负载均衡list中第一条数据的ip为key,value存储整个负载均衡list
    private final ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<>(16);

    // 通过原子类实现的cas锁(乐观锁)
    private final AtomicBoolean updateLock = new AtomicBoolean();

public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
    // 选择器中负载均衡list中第一条数据的ip为key,查询methodWeightMap是否存在
    String key = upstreamList.get(0).getUpstreamUrl();
    ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
    if (map == null) {
        // 查询methodWeightMap是不存在,创建16个容量的ConcurrentHashMap
        methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
        // map是这个新建ConcurrentHashMap的引用,后面代码操作map实际就是操作methodWeightMap
        map = methodWeightMap.get(key);
    }
    int totalWeight = 0;
    long maxCurrent = Long.MIN_VALUE;
    long now = System.currentTimeMillis();
    DivideUpstream selectedInvoker = null;
    WeightedRoundRobin selectedWRR = null;
    for (DivideUpstream upstream : upstreamList) {
        String rKey = upstream.getUpstreamUrl();
        WeightedRoundRobin weightedRoundRobin = map.get(rKey);
        int weight = getWeight(upstream);
        // 遍历负载均衡list,将"不存在"请求连接添加到map中
        if (weightedRoundRobin == null) {
            weightedRoundRobin = new WeightedRoundRobin();
            weightedRoundRobin.setWeight(weight);
            map.putIfAbsent(rKey, weightedRoundRobin);
        }
        // 比较同一连接,权重发生更改,重新赋值
        if (weight != weightedRoundRobin.getWeight()) {
            //weight changed
            weightedRoundRobin.setWeight(weight);
        }
        // 关键点1:缓存的节点权重增加"最新权重",并设置更新时间用于过期比较
        long cur = weightedRoundRobin.increaseCurrent();
        weightedRoundRobin.setLastUpdate(now);
        // 关键点2:选出当前权重最大的连接对象
        if (cur > maxCurrent) {
            maxCurrent = cur;
            selectedInvoker = upstream;
            // 记录最大权重引用
            selectedWRR = weightedRoundRobin;
        }
        // 计算总权重
        totalWeight += weight;
    }
    // 关键点3:剔除过期的连接,通过原子类保证线程安全
    if (!updateLock.get() && upstreamList.size() != map.size() && updateLock.compareAndSet(false, true)) {
        try {
            // copy -> modify -> update reference
            ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<>(map);
            newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > recyclePeriod);
            methodWeightMap.put(key, newMap);
        } finally {
            updateLock.set(false);
        }
    }
    // 关键点4: 相当于服务降级,当前最大权重 - 总权重 =  "当前最大权重"
    if (selectedInvoker != null) {
        // 通过引用操作原对象
        selectedWRR.sel(totalWeight);
        return selectedInvoker;
    }
    // should not happen here
    return upstreamList.get(0);
}

此处模拟下三个节点,三次调用的计算过程


image.png

三、小结

  • 疑问1: 如果修改负载均衡list的第一条,数据会产生冗余?
image
  • 疑问2:关于源码分析中的关键点3,原子类实现cas锁,代码“看懂”,却没有模拟出场景来,难受!
  • 日拱一卒

相关文章

网友评论

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

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