【高性能网关soul学习】11. 插件之DevidePlugin
本文目标:
- 简单介绍 soul 对于请求的处理和插件链的执行逻辑
- 跑通divide插件,研究其负载均衡原理
Soul对于请求的使用插件链方式的处理
-
请求首先到 SoulWebHandler ,SoulWebHandler调用 handle 方法,会构建一个 DefaultSoulPluginChain 来处理请求
-
DefaultSoulPluginChain 负责插件链的处理,插件按责任链模式的机制进行遍历,SoulPlugin execute 方法,每执行的一个插件功能 index 都会+1,直到所有插件都执行完毕
-
SoulPlugin 所有插件的都会实现的一个接口 (其他插件具体的实现暂时先不细究)
-
抽象类 AbstractSoulPlugin:
-
使用了模板方法模式,其execute 会根据插件的名称获取缓存的插件数据,依次判断插件是否处于启动状态,选择器、规则是否满足条件
-
然后符合条件的调用其模板方法 doExecute(由子类实现)
-
-
因为本文目标主要是使用 Devide 插件做路由代理,因此我们主要研究 DevidePlugin 插件对于 ServerWebExchange 的处理
Divide插件处理
- DividePlugin 为 AbstractSoulPlugin的一个子类,实现了 doExecute 方法
- 大致就是提取了 exchange 中的ip地址,然后根据ip地址做负载均衡,然后把本次获取到的 divideUpstream 转换为 exchange中的url属性,用于后续插件的调用处理
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
// ...
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
// 略... divideUpstream 转换为设置http的一些属性,设置进exchange的属性中
// 责任链继续执行
return chain.execute(exchange);
}
LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) {
LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
return loadBalance.select(upstreamList, ip);
}
LoadBalance 负载均衡的三种实现
LoadBalance的继承关系
这里使用用了模板方法模式,抽象类AbstractLoadBalance 中的select 对 upstreamList 做了简单判断,如果只有当备选的 upstream 大于1时,才会进入负载均衡逻辑,调用子类实现的 doSelect 方法
// AbstractLoadBalance.java
@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);
}
接下来我们分析下三种负载均衡的实现逻辑,即对 doSelect方法的实现
HashLoadBalance
- 实现比较简单,将 upstreamDddress 进行 hash,然后放入map中
- 对ip进行hash,返回获取到的 lastRing 的第一个元素
- treeMap.tailMap(hash) :返回此映射 map 的部分视图,其键大于等于 fromKey
@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();
}
RandomLoadBalance
- random 的方式首先计算权重,并对等权重的情况作了一个优化
- 等权重:直接随机返回一个
- 非等权重的 random 筛选方式,首选获取到一个 totalWeight 范围内的随机数,然后进行遍历,每次减去divideUpstream的权重值,如果小于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);
}
// 非等权重的 random方式
private DivideUpstream random(final int totalWeight, final List<DivideUpstream> upstreamList) {
// If the weights are not the same and the weights are greater than 0, then random by the total number of weights
int offset = RANDOM.nextInt(totalWeight);
// Determine which segment the random value falls on
for (DivideUpstream divideUpstream : upstreamList) {
offset -= getWeight(divideUpstream);
if (offset < 0) {
return divideUpstream;
}
}
return upstreamList.get(0);
}
RoundRobinLoadBalance
- 实现逻辑看起来比较复杂,简单说来就是 对于每个 DivideUpstream 都维护一个权重对象,每次遍历所有权重对象获取到权重最大的那个 DivideUpstream,同时减去被选中的 DivideUpstream 的权重(减去总权重,优先级降为最低)
- (下面是主流程代码)
@Override
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
String key = upstreamList.get(0).getUpstreamUrl();
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
// 初始化map
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);
// 初始化 weightedRoundRobin
long cur = weightedRoundRobin.increaseCurrent();
weightedRoundRobin.setLastUpdate(now);
// 选择当前权重最大的一个作为调用
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = upstream;
selectedWRR = weightedRoundRobin;
}
totalWeight += weight;
}
// 。。。
if (selectedInvoker != null) {
// 减去总权重值,权重降为最低
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
}
总结
- 简单介绍 soul 对于请求的处理和插件链的执行逻辑
- 介绍了 Devide 的三种负载均衡原理,实现的技巧很值得参考