Soul网关源码学习系列之【divide插件】

本文深入探讨Soul网关的Divide插件,详细解析其设计原理和代码实现。Divide插件作为处理HTTP协议请求的核心组件,承担正向代理和负载均衡的角色。文章介绍了数据库中的配置结构,以及Divide插件如何进行负载均衡调用,包括RandomLoadBalance、HashLoadBalance和RoundRobinLoadBalance等策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Soul的插件设计

  • 插件采用数据库设计,来存储插件,选择器,规则配置数据,以及对应关系。
  • 数据库表UML类图:
    在这里插入图片描述
  • 设计详解:
    • 一个插件对应多个选择器,一个选择器对应多个规则。
    • 一个选择器对应多个匹配条件,一个规则对应多个匹配条件。
    • 每个规则在对应插件下,不同的处理表现为handle字段,handle字段就是一个json字符串。具体的可以在admin使用过程中进行查看。

Divide插件简介

  • divide插件是网关处理 http协议请求的核心处理插件。
  • divide插件是进行http正向代理的插件,所有http类型的请求,都是由该插件进行负载均衡的调用。
  • 想更详细了解,可以参考官方文档。我这里还是主要看一下其代码实现。

Divide插件代码

  • 整个Soul的代码结构还是比较清晰的,所以我们可以快速的定位到soul-plugin下的soul-plugin-divide
  • 看一下我们的DividePlugin类,继承了AbstractSoulPlugin,这个是所有插件的一个父类,并且这个父类实现了SoulPlugin的接口。
    public class DividePlugin extends AbstractSoulPlugin {...}
    
  • 在插件的execute处打上断点,以便跟踪代码处理,断点进来后,可以发现程序是从SoulWebHandlerexecute方法进来的。我们暂且认为这里是入口,因为实际请求是通过spring封装好的Netty过来的,这块后面再单独看。
  • 那现在我们把焦点放在我们divide插件的execute方法上
    	@Override
        public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
            String pluginName = named(); // 插件名称
            final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName); //根据插件名称获取插件信息,这里是把数据放在了一个Map中,前面有看过。
            if (pluginData != null && pluginData.getEnabled()) { // 判断插件是否开启,这个是我们后台管理页面里控制的。
                final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);  // 拿到插件下的所有Selector
                if (CollectionUtils.isEmpty(selectors)) {
                    return handleSelectorIsNull(pluginName, exchange, chain);
                }
                final SelectorData selectorData = matchSelector(exchange, selectors); // 这个match的处理就是找到我们请求的url里面的selector
                if (Objects.isNull(selectorData)) {
                    return handleSelectorIsNull(pluginName, exchange, chain);
                }
                selectorLog(selectorData, pluginName); // 这里知识输出log
                final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId()); // 找到selector下面对应的rules
                if (CollectionUtils.isEmpty(rules)) {
                    return handleRuleIsNull(pluginName, exchange, chain);
                }
                RuleData rule;
                if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) { // 如果我们的selector定义的类型是full,也就是拦截所有的请求,则拿最后一个规则。
                    //get last
                    rule = rules.get(rules.size() - 1);
                } else { // 否则需要拿到匹配的规则。
                    rule = matchRule(exchange, rules);
                }
                if (Objects.isNull(rule)) {
                    return handleRuleIsNull(pluginName, exchange, chain);
                }
                ruleLog(rule, pluginName); // 输出log
                return doExecute(exchange, chain, selectorData, rule); //调用divide插件的处理
            }
            return chain.execute(exchange);
        }
    
  • 接下来再看另一个核心处理doExecute方法。
    	@Override
        protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
            final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT); // soulContext包含了module,method,rpcType,httpMethod,path,contextPath,realUrl等信息,也就是我们请求的一些信息
            assert soulContext != null;
            final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class); // 这里通过Json格式转成DivideRuleHandle类,这里面包含了loadBalance负载均衡算法,retry重试次数,timeout超时时间
            final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId()); // 通过selectorId拿到我们的ip,端口这些信息。
            if (CollectionUtils.isEmpty(upstreamList)) {
                log.error("divide upstream configuration error: {}", rule.toString());
                Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
                return WebFluxResultUtils.result(exchange, error);
            }
            final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
            DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip); // 通过我们定义的负载均衡方法,找到一个service
            if (Objects.isNull(divideUpstream)) {
                log.error("divide has no upstream");
                Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
                return WebFluxResultUtils.result(exchange, error);
            }
            // set the http url
            String domain = buildDomain(divideUpstream);
            String realURL = buildRealURL(domain, soulContext, exchange);
            exchange.getAttributes().put(Constants.HTTP_URL, realURL);
            // set the http timeout
            exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
            exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
            return chain.execute(exchange); // 这里最后是把我们找的的service以及服务的url转发出去。
        }
    
  • 接下来我们看一下负载均衡是怎么实现的。
    • RandomLoadBalance
      	@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);
          }
      
    • HashLoadBalance
      	@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();
          }
      
    • 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);
              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);
          }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值