Soul源码解析(13)-Soul网关负载均衡源码解读

本文详细解读了Soul网关中的负载均衡实现,涉及RoundRobin、Hash和Random三种算法,通过AbstractLoadBalance模板,展示了它们在AbstractLoadBalance、RandomLoadBalance、HashLoadBalance和RoundRobinLoadBalance中的具体实现。

一、目标

1、搞清楚Soul网关负载均衡实现原理;

二、内容

2.1 背景

  • 负载均衡:负载均衡是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性;

  • 常用负载均衡算法:

    • **Round Robin(轮询):**为第一个请求选择列表中的第一个服务器,然后按顺序向下移动列表直到结尾,然后循环。
    • **Least Connections(最小连接):**优先选择连接数最少的服务器,在普遍会话较长的情况下推荐使用。
    • **Hash:**根据请求源的 IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。
  • Soul网关支持的负载均衡算法:

    1、Random

    2、Hash

    3、Round Robin

    2.2 负载均衡源码分析

    2.2.1 AbstractLoadBalance类
  • AbstractLoadBalance类是soul网关支持的负载均衡算法的基类,实现了LoadBalance接口,重写select方法,提供了抽象方法doSelect,负载均衡的具体实现类会重写doSelect方法 ;

  • LoadBalance接口如下,传入IP(你请求的真实IP)和一个DivideUpstream集合,最后返回一个DivideUpstream对象:

    @SPI
    public interface LoadBalance {
    
        /**
         * this is select one for upstream list.
         *
         * @param upstreamList upstream list
         * @param ip ip
         * @return divide upstream
         */
        DivideUpstream select(List<DivideUpstream> upstreamList, String ip);
    }
    
  • DivideUpstream对象包括:

@Data
@ToString
@Builder
public class DivideUpstream implements Serializable {
	// 存活的IP
    private String upstreamHost;
    //协议
    private String protocol;
	//请求URL
    private String upstreamUrl;
	//权重
    private int weight;
	//开闭状态,默认true
    @Builder.Default
    private boolean status = true;
    //开始时间
    private long timestamp;
	//预热时间(ms)
    private int warmup;

}

  • 接下来看AbstractLoadBalance类

    public abstract class AbstractLoadBalance implements LoadBalance {
    
        //负载均衡的具体实现类
        protected abstract DivideUpstream doSelect(List<DivideUpstream> upstreamList, String ip);
    	
        //重写LoadBalance接口里面的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);
            }
            return doSelect(upstreamList, ip);
        }
    	//取得权重参数
        protected int getWeight(final DivideUpstream upstream) {
            if (!upstream.isStatus()) {
                return 0;
            }
            int weight = getWeight(upstream.getTimestamp(), getWarmup(upstream.getWarmup(), Constants.DEFAULT_WARMUP), upstream.getWeight());
            return weight;
        }
    
        private int getWeight(final long timestamp, final int warmup, final int weight) {
            if (weight > 0 && timestamp > 0) {
                int uptime = (int) (System.currentTimeMillis() - timestamp);
                if (uptime > 0 && uptime < warmup) {
                    return calculateWarmupWeight(uptime, warmup, weight);
                }
            }
            return weight;
        }
    
        private int getWarmup(final int warmup, final int defaultWarmup) {
            if (warmup > 0) {
                return warmup;
            }
            return defaultWarmup;
        }
    	//根据时间差,预热时间,权重计算实际权重的方法
        private int calculateWarmupWeight(final int uptime, final int warmup, final int weight) {
            int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
            return ww < 1 ? 1 : (ww > weight ? weight : ww);
        }
    
    }
    
  • 可以在下图的位置选择负载均衡策略:

在这里插入图片描述

2.2.2 RandomLoadBalance分析

RandomLoadBalance是随机负载均衡算法的实现类,它继承了AbstractLoadBalance方法,重写了doSelect()方法。这里重点看一下里面的随机负载均衡的实现方法:

private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) {
    // 如果权重不相同且权重大于0,则按权重总数随机
    int offset = RANDOM.nextInt(totalWeight);
    // 确定随机值落在哪个段上
    for (DivideUpstream divideUpstream : upstreamList) {
        offset -= getWeight(divideUpstream);
        if (offset < 0) {
            return divideUpstream;
        }
    }
    return upstreamList.get(0);
}
2.2.3 HashLoadBalance分析

HashLoadBalance是根据请求源的 IP 的散列(hash)来选择要转发的服务器,同样继承了AbstractLoadBalance方法,重写了doSelect()方法。核心算法实现如下:

@Join
public class HashLoadBalance extends AbstractLoadBalance {
 private static final int VIRTUAL_NODE_NUM = 5;
 @Override
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
    final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new ConcurrentSkipListMap<>();
    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);
        }
    }
    long hash = hash(String.valueOf(ip));
    SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
    if (!lastRing.isEmpty()) {
        return lastRing.get(lastRing.firstKey());
    }
    return treeMap.firstEntry().getValue();
}
 private static long hash(final String key) {
    // md5 byte
    MessageDigest md5;
    try {
        md5 = MessageDigest.getInstance("MD5");
    } catch (NoSuchAlgorithmException e) {
        throw new SoulException("MD5 not supported", e);
    }
    md5.reset();
    byte[] keyBytes;
    keyBytes = key.getBytes(StandardCharsets.UTF_8);
     md5.update(keyBytes);
    byte[] digest = md5.digest();
     // hash code, Truncate to 32-bits
    long hashCode = (long) (digest[3] & 0xFF) << 24
            | ((long) (digest[2] & 0xFF) << 16)
            | ((long) (digest[1] & 0xFF) << 8)
            | (digest[0] & 0xFF);
    return hashCode & 0xffffffffL;
}
}
2.2.4 RoundRobinLoadBalance分析

RoundRobinLoadBalance为第一个请求选择列表中的第一个服务器,然后按顺序向下移动列表直到结尾,然后循环。结构跟上面两个都一样,核心实现如下:

public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
    String key = upstreamList.get(0).getUpstreamUrl();
    ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
    if (map == null) {
        methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
        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);
        if (weightedRoundRobin == null) {
            weightedRoundRobin = new WeightedRoundRobin();
            weightedRoundRobin.setWeight(weight);
            map.putIfAbsent(rKey, weightedRoundRobin);
        }
        if (weight != weightedRoundRobin.getWeight()) {
            //weight changed
            weightedRoundRobin.setWeight(weight);
        }
        long cur = weightedRoundRobin.increaseCurrent();
        weightedRoundRobin.setLastUpdate(now);
        if (cur > maxCurrent) {
            maxCurrent = cur;
            selectedInvoker = upstream;
            selectedWRR = weightedRoundRobin;
        }
        totalWeight += weight;
    }
    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);
        }
    }
    if (selectedInvoker != null) {
        selectedWRR.sel(totalWeight);
        return selectedInvoker;
    }
    // should not happen here
    return upstreamList.get(0);
}

三、总结

今天一起学习了Soul网关三种不同的负载均衡实现方法,这里使用了抽象模板设计模式,在子类里面实现了各自不同的负载计算方法。结合例子自己跑一遍代码,会更清楚整个流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值