【高性能网关soul学习】11. 插件之DevidePlugin

【高性能网关soul学习】11. 插件之DevidePlugin

本文目标:

  1. 简单介绍 soul 对于请求的处理和插件链的执行逻辑
  2. 跑通divide插件,研究其负载均衡原理

Soul对于请求的使用插件链方式的处理

req SoulWebHandler DefaultSoulPluginChain 请求 handle execute:遍历plugins loop [责任链处理] req SoulWebHandler DefaultSoulPluginChain
  1. 请求首先到 SoulWebHandler ,SoulWebHandler调用 handle 方法,会构建一个 DefaultSoulPluginChain 来处理请求

  2. DefaultSoulPluginChain 负责插件链的处理,插件按责任链模式的机制进行遍历,SoulPlugin execute 方法,每执行的一个插件功能 index 都会+1,直到所有插件都执行完毕

    1. SoulPlugin 所有插件的都会实现的一个接口 (其他插件具体的实现暂时先不细究)

    2. 抽象类 AbstractSoulPlugin:

      1. 使用了模板方法模式,其execute 会根据插件的名称获取缓存的插件数据,依次判断插件是否处于启动状态,选择器、规则是否满足条件

      2. 然后符合条件的调用其模板方法 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
LoadBalance
HashLoadBalance
RandomLoadBalance
RoundRobinLoadBalance

这里使用用了模板方法模式,抽象类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;
    }
}
总结
  1. 简单介绍 soul 对于请求的处理和插件链的执行逻辑
  2. 介绍了 Devide 的三种负载均衡原理,实现的技巧很值得参考
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值