Soul网关中的负载均衡
27 Jan 2021 | Soul |在divide
插件中,Soul
网关提供了负载均衡算法,对请求网关的IP
选择一个真实的服务。在Soul
中,负载均衡算法有三种:HashLoadBalance
,RandomLoadBalance
,RoundRobinLoadBalance
。默认使用的是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
网关中的负载均衡实现原理。