写在前面

在前面的文章里,我们已经在 soul-bootstrap 网关里拿到了需要代理的接口数据,并且会存储到内存中。再加上 web socket 做数据同步的功能,能保证接口的变化会更新到 soul-bootstrap 网关上。

但是我们真正使用的时候是直接把接口请求发送给 soul-bootstrap 网关的,并没有直接去请求真实的服务。

那么今天我们就一起来看看 soul-bootstrap 是怎么代理接口的。

soul-bootstrap 代理接口的过程

这个项目用了 Spring Boot 的 WebFlux,跟一般的 Spring Mvc 请求过程有些区别。

引入了 soul-web 模块,这个模块会从加载「插件」开始。

  1. public class SoulConfiguration {
  2. @Bean("webHandler")
  3. public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
  4. List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
  5. final List<SoulPlugin> soulPlugins = pluginList.stream()
  6. .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
  7. soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
  8. return new SoulWebHandler(soulPlugins);
  9. }
  10. }

然后会每当有请求发送过来的时候,就会去从插件里面去匹配一个来处理,http 接口的处理插件是 Divide,所以我们只关注 Divide 插件就行了。

我们先猜测下这个 Divide 插件处理的过程吧。

  1. 从缓存里拿到「选择器」数据
  2. 遍历「选择器」数据匹配请求的 uri 里面的 contextPath
  3. 匹配成功之后从缓存里拿到「规则」数据
  4. 遍历「规则」数据匹配 uri 里面的接口地址
  5. 从缓存里拿到真实接口服务的地址,发送新的请求
  6. 拿到返回值

大概的逻辑就是这样,但是中间过程会比这个复杂的多。

举个例子,「选择器」和「规则」匹配数据的时候,还会有各自的「匹配条件」。

什么是「匹配条件」呢?比如根据请求的方式(get 还是 post 或者 option 等)去匹配;比如根据携带的参数去匹配,参数 xxx == xxx,还是参数 xxx != xxx 等等。

还有,对真实服务的请求也会有很多的其他处理,比如同一个接口有多个服务,那么当代理这个接口的时候,最终请求到哪一个服务也是可以用网关控制的。也就是所谓的负载均衡,针对每个服务设置权重,控制对每个服务请求的压力。

  1. public abstract class AbstractSoulPlugin implements SoulPlugin {
  2. @Override
  3. public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
  4. String pluginName = named();
  5. // 从缓存拿到插件数据
  6. final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
  7. if (pluginData != null && pluginData.getEnabled()) {
  8. // 从缓存拿到选择器数据
  9. final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
  10. // 省略判断空的逻辑 ...
  11. // 匹配选择器
  12. final SelectorData selectorData = matchSelector(exchange, selectors);
  13. // 省略判断空的逻辑 日志输出...
  14. // 从缓存拿到规则数据
  15. final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
  16. // 匹配规则 省略部分代码 ...
  17. RuleData rule;
  18. // 省略判断空逻辑 日志输出 ...
  19. return doExecute(exchange, chain, selectorData, rule);
  20. }
  21. return chain.execute(exchange);
  22. }
  23. }

在 AbstractSoulPlugin 这个通用的插件里面就做了上面我们分析的前 3 个步骤,剩下的步骤在哪呢?

就在每个具体的插件处理类里面,这些插件都实现了上面那个类的 doExecute 方法,我们还是只关注 Divide 插件继续看下去:

  1. public class DividePlugin extends AbstractSoulPlugin {
  2. @Override
  3. protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
  4. // 加载上游的服务数据 也就是真实的服务数据
  5. final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
  6. // 负载均衡处理
  7. final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
  8. DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
  9. // 构造真实的服务地址
  10. String domain = buildDomain(divideUpstream);
  11. String realURL = buildRealURL(domain, soulContext, exchange);
  12. exchange.getAttributes().put(Constants.HTTP_URL, realURL);
  13. return chain.execute(exchange);
  14. }
  15. }

这个类里就完成了上面分析的第 4 第 5 两个步骤。最后一个步骤在 WebClientPlugin 类里面,这个类会在最后被执行,主要功能就是向真实的服务发起请求拿到响应。

  1. public class WebClientPlugin implements SoulPlugin {
  2. @Override
  3. public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
  4. // 拿到请求方法 url 等参数,发送请求处理返回值
  5. HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
  6. WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
  7. return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
  8. }
  9. }

到了这里,一个完整的网关代理接口的过程就完成了。虽然过程有些简陋,但是核心的过程是完整的。

总结

我在写这篇文章的难点在于对 WebFlux 不熟悉,加上用了非 Spring Mvc 的接口处理过程,而是使用了 React Netty 的 WebHandler 去处理,一开始被这个绕晕了头。

后来尝试去忽略掉一些过程,通过调试和猜测去寻找整个流程,从中慢慢找到大概的流程之后,整体的逻辑也就满清晰了。

尽管还是对 WebFlux 一窍不通,但是并不影响整个流程的梳理。