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
处打上断点,以便跟踪代码处理,断点进来后,可以发现程序是从SoulWebHandler
的execute
方法进来的。我们暂且认为这里是入口,因为实际请求是通过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); }
- RandomLoadBalance