Soul网关中的负载均衡

divide插件中,Soul网关提供了负载均衡算法,对请求网关的IP选择一个真实的服务。在Soul中,负载均衡算法有三种:HashLoadBalanceRandomLoadBalanceRoundRobinLoadBalance。默认使用的是RandomLoadBalance算法。

使用时机

在执行divide插件时,会调用负载均衡算法,根据选择的结果,设置真实的请求服务。

//org.dromara.soul.plugin.divide.DividePlugin#doExecute
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
	
    	//省略了其他代码
    
        final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
    	//使用负载均衡    
       DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
      
        // 设置真实的请求服务
        String domain = buildDomain(divideUpstream);
      
   		 //省略了其他代码
        return chain.execute(exchange);
    }
LoadBalance的继承关系如下

类的设计使用的时模板方法设计模式:在抽象类中实现每个组件通用的功能,一些具体的功能留给子类去实现。在这里AbstrctLoadBalance实现了负载均衡的通用方法:获取权重,入参判断等。doSelect()留给了子类去实现,即具体的负载均衡算法逻辑。

public abstract class AbstractLoadBalance implements LoadBalance {
	
    //子类去实现
    protected abstract DivideUpstream doSelect(List<DivideUpstream> upstreamList, String ip);

    @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) {
     //省略了代码的具体实现
    }

	//省略其他代码
}

select()方法中,先判断有没有服务;然后判断是否只有一个,是的话,就直接返回。因为一个不需要负载均衡,只能请求它。有多个就会通过负载均衡选择具体的类。

HashLoadBalance的原理

通过哈希算法计算所有服务地址,保存到map。然后也对当前请求的IP计算hash值,根据这个值到map中获取服务。

    @Override
    public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
        final ConcurrentSkipListMap<Long, DivideUpstream> treeMap = new ConcurrentSkipListMap<>();
       	//计算每个服务的hash
        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);
            }
        }
        //计算当前的hash
        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();
    }
RandomLoadBalance的原理

首先,计算所有服务的权重,然后判断每个服务的权重是否相同,如果权重都相等或者为0,那么就随机选择一个。否则,会先获取到一个所有权重范围内的随机数,然后进行遍历,每次减去当前服务的权重值,如果小于0则返回结果。这里就会倾向于权重较大的那个服务。

    @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);
        }
        // If the weights are the same or the weights are 0 then random
        return random(upstreamList);
    }
RoundRobinLoadBalance的原理

这个实现比较复杂,完整代码逻辑请看源码。核心思想: 对于每个服务维护一个权重对象,每次遍历所有权重对象获取到权重最大的那个服务,同时减去权重,以降低优先级。

@Override
    public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
        String key = upstreamList.get(0).getUpstreamUrl();
        ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);

        for (DivideUpstream upstream : upstreamList) {
            String rKey = upstream.getUpstreamUrl();
  			
            //省略了其他代码
            
            //选择最大的
            if (cur > maxCurrent) {
                maxCurrent = cur;
                selectedInvoker = upstream;
                selectedWRR = weightedRoundRobin;
            }
            totalWeight += weight;
        }
        //省略了其他代码
        
        if (selectedInvoker != null) {
            selectedWRR.sel(totalWeight);
            return selectedInvoker;
        }
        // should not happen here
        return upstreamList.get(0);
    }

小结,主要讲解了Soul网关中的负载均衡实现原理。