十三、soul源碼學習-http請求鏈路跟蹤

前面我們從配置的修改是如何更新SoulAdmin本地緩存的,再到網關和SoulAdmin是如何同步數據等,講解了數據同步的機制,是為了保證我們網關能夠正確的處理請求,并針對配置的插件進行正確的處理,接下來我們從一個真正的用戶請求http到網關以及如何最后到我們真正請求的整個鏈路做一下分析

SoulWebHandler,是網關請求的入口。

//org.dromara.soul.web.handler.SoulWebHandler
//實現了WebHandler
public final class SoulWebHandler implements WebHandler {

    private final List<SoulPlugin> plugins;

    private final Scheduler scheduler;

    /**
     * 初始化的時候注入所有的SoulPlugin插件
     */
    public SoulWebHandler(final List<SoulPlugin> plugins) {
        this.plugins = plugins;
        String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
        if (Objects.equals(schedulerType, "fixed")) {
            int threads = Integer.parseInt(System.getProperty(
                    "soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
            scheduler = Schedulers.newParallel("soul-work-threads", threads);
        } else {
            scheduler = Schedulers.elastic();
        }
    }
}

在我們請求過來的時候,會走到handle

//org.dromara.soul.web.handler.SoulWebHandler#handle
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
  //監控相關
  MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
  Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
  //構造DefaultSoulPluginChain,默認的插件鏈進行處理
  return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
    .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}

DefaultSoulPluginChain 使用了責任鏈的設計模式,針對一個請求,對所有的插件進行過濾

//org.dromara.soul.web.handler.SoulWebHandler.DefaultSoulPluginChain
private static class DefaultSoulPluginChain implements SoulPluginChain {

  private int index;

  private final List<SoulPlugin> plugins;
  
  DefaultSoulPluginChain(final List<SoulPlugin> plugins) {
    this.plugins = plugins;
  }

  /**
         * Delegate to the next {@code WebFilter} in the chain.
         *
         * @param exchange the current server exchange
         * @return {@code Mono<Void>} to indicate when request handling is complete
         */
  @Override
  public Mono<Void> execute(final ServerWebExchange exchange) {
    return Mono.defer(() -> {
      if (this.index < plugins.size()) {
        SoulPlugin plugin = plugins.get(this.index++);
        Boolean skip = plugin.skip(exchange);
        if (skip) {
          return this.execute(exchange);
        }
        return plugin.execute(exchange, this);
      }
      return Mono.empty();
    });
  }
}

我們看到plugins是按照順序循環處理的,而且每次的順序是一致的,GlobalPlugin肯定在第一位,這是怎么實現的,我們看下GlobalPlugin插件

//org.dromara.soul.plugin.global.GlobalPlugin
public class GlobalPlugin implements SoulPlugin {
    
    private final SoulContextBuilder builder;
    
    public GlobalPlugin(final SoulContextBuilder builder) {
        this.builder = builder;
    }
  
  //通過getOrder保證初始化的順序
    @Override
    public int getOrder() {
        return 0;
    }
    
}

通過看getOrder的調用方我們發現

//org.dromara.soul.web.configuration.SoulConfiguration
@Configuration
@ComponentScan("org.dromara.soul")
@Import(value = {ErrorHandlerConfiguration.class, SoulExtConfiguration.class, SpringExtConfiguration.class})
@Slf4j
public class SoulConfiguration {

   
    @Bean("webHandler")
    public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
        List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
      //在這里進行重排序
        final List<SoulPlugin> soulPlugins = pluginList.stream()
                .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
        soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
        return new SoulWebHandler(soulPlugins);
    }
}

所以 插件的順序是定義好的,每次請求的第一個肯定是GlobalPlugin。GlobalPlugin是最先執行的插件

//org.dromara.soul.plugin.global.GlobalPlugin
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
  final ServerHttpRequest request = exchange.getRequest();
  final HttpHeaders headers = request.getHeaders();
  final String upgrade = headers.getFirst("Upgrade");
  SoulContext soulContext;
  //先忽略Upgrade,普通請求upgrade為空
  if (StringUtils.isBlank(upgrade) || !"websocket".equals(upgrade)) {
    soulContext = builder.build(exchange);
  } else {
    final MultiValueMap<String, String> queryParams = request.getQueryParams();
    soulContext = transformMap(queryParams);
  }
  exchange.getAttributes().put(Constants.CONTEXT, soulContext);
  return chain.execute(exchange);
}

這里會走到DefaultSoulContextBuilder

//org.dromara.soul.plugin.global.DefaultSoulContextBuilder
@Override
public SoulContext build(final ServerWebExchange exchange) {
  final ServerHttpRequest request = exchange.getRequest();
  //獲取到請求的path
  String path = request.getURI().getPath();
  //http先不關注metaData
  MetaData metaData = MetaDataCache.getInstance().obtain(path);
  if (Objects.nonNull(metaData) && metaData.getEnabled()) {
    exchange.getAttributes().put(Constants.META_DATA, metaData);
  }
  //將請求和元數據轉換成SoulContext
  return transform(request, metaData);
}
//org.dromara.soul.plugin.global.DefaultSoulContextBuilder#transform
//構造Soul的上下文信息
private SoulContext transform(final ServerHttpRequest request, final MetaData metaData) {
  //Constants.APP_KEY = appKey
  final String appKey = request.getHeaders().getFirst(Constants.APP_KEY);
  //Constants.SIGN = sign
  final String sign = request.getHeaders().getFirst(Constants.SIGN);
  //Constants.TIMESTAMP = timestamp
  final String timestamp = request.getHeaders().getFirst(Constants.TIMESTAMP);
  //從header獲取信息
  SoulContext soulContext = new SoulContext();
  String path = request.getURI().getPath();
  soulContext.setPath(path);
  //判斷元數據信息,通過元數據來拍斷當前的請求是屬于什么類型
  if (Objects.nonNull(metaData) && metaData.getEnabled()) {
    if (RpcTypeEnum.SPRING_CLOUD.getName().equals(metaData.getRpcType())) {
      setSoulContextByHttp(soulContext, path);
      soulContext.setRpcType(metaData.getRpcType());
    } else if (RpcTypeEnum.DUBBO.getName().equals(metaData.getRpcType())) {
      setSoulContextByDubbo(soulContext, metaData);
    } else if (RpcTypeEnum.SOFA.getName().equals(metaData.getRpcType())) {
      setSoulContextBySofa(soulContext, metaData);
    } else if (RpcTypeEnum.TARS.getName().equals(metaData.getRpcType())) {
      setSoulContextByTars(soulContext, metaData);
    } else {
      setSoulContextByHttp(soulContext, path);
      soulContext.setRpcType(RpcTypeEnum.HTTP.getName());
    }
    //默認當成http處理
  } else {
    setSoulContextByHttp(soulContext, path);
    soulContext.setRpcType(RpcTypeEnum.HTTP.getName());
  }
  //注入必要信息
  soulContext.setAppKey(appKey);
  soulContext.setSign(sign);
  soulContext.setTimestamp(timestamp);
  soulContext.setStartDateTime(LocalDateTime.now());
  Optional.ofNullable(request.getMethod()).ifPresent(httpMethod -> soulContext.setHttpMethod(httpMethod.name()));
  return soulContext;
}

GlobalPlugin是最關鍵的插件,通過上面流程,構造Soul的上下文,從而使得后面的插件判斷才有依據來決定是走哪一個插件

通過責任鏈,依次循環調用所有的插件,直到中間某個插件匹配調用生效為止。我們來看下http請求,最終會命中Divide插件,我們來看下DividePlugin插件

//org.dromara.soul.plugin.divide.DividePlugin#skip
@Override
public Boolean skip(final ServerWebExchange exchange) {
  //GlobalPlugin構造的上下文
  final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
  //DividePlugin會判斷當前的SoulContext的RpcType是否是Http
  return !Objects.equals(Objects.requireNonNull(soulContext).getRpcType(), RpcTypeEnum.HTTP.getName());
}

發現不需要跳過后,會進入AbstractSoulPlugin的execute。包括剛才的GlobalPlugin也會經過這里

//org.dromara.soul.plugin.base.AbstractSoulPlugin#execute
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
  String pluginName = named();
  //從本地緩存中獲取PluginData數據,這里的本地緩存就是之前我們講的數據同步所維護的緩存
  final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
  //判斷plugin是否為空并且開啟
  if (pluginData != null && pluginData.getEnabled()) {
    //在獲取本地Selector數據緩存。獲取SelectorData
    final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
    //如果該插件selector為空會走這里,里面邏輯實際上就是調用下一個插件
    if (CollectionUtils.isEmpty(selectors)) {
      return handleSelectorIsNull(pluginName, exchange, chain);
    }
    //如果Selectors不為空,則去看是否匹配,匹配邏輯暫時先不展開講,之后再看
    final SelectorData selectorData = matchSelector(exchange, selectors);
    //如果匹配為空則繼續調用下一個插件
    if (Objects.isNull(selectorData)) {
      return handleSelectorIsNull(pluginName, exchange, chain);
    }
    selectorLog(selectorData, pluginName);
    //在獲取規則數據
    final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
    //規則為空則繼續執行下一個插件
    if (CollectionUtils.isEmpty(rules)) {
      return handleRuleIsNull(pluginName, exchange, chain);
    }
    RuleData rule;
    //如果是全流量
    if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
      //則獲取最后一個規則
      rule = rules.get(rules.size() - 1);
    } else {
      //判斷是否有匹配規則,具體匹配校驗之后再說
      rule = matchRule(exchange, rules);
    }
    //如果規則為空,繼續執行下一個插件
    if (Objects.isNull(rule)) {
      return handleRuleIsNull(pluginName, exchange, chain);
    }
    ruleLog(rule, pluginName);
    //如果規則不為空,則調用doExecute。對應插件的具體實現
    return doExecute(exchange, chain, selectorData, rule);
  }
  return chain.execute(exchange);
}
//org.dromara.soul.plugin.divide.DividePlugin
public class DividePlugin extends AbstractSoulPlugin {

  
    @Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
      //這里拿到之前GlobalPlugin的上下文
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
      //獲取Divide規則處理器,DivideRuleHandle包含了負載均衡策略以及重試次數和超時時間信息
        final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
      //這里根據之前的探活機制,獲取到對應選擇器的上游服務列表
        final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
      //如果為空則拋異常,并直接返回WebFlux結果
        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);
        }
      //獲取當前調用方IP
        final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
      //通過負載均衡獲取對應的上游
        DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
      //如果上游為空則返回異常信息
        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);
        }
        // 注入必要信息
        String domain = buildDomain(divideUpstream);
        String realURL = buildRealURL(domain, soulContext, exchange);
        exchange.getAttributes().put(Constants.HTTP_URL, realURL);
      //在調用下一個插件
        exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
        exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
        return chain.execute(exchange);
    }
}

我們發現Divide插件并沒有真正的去調用,而是主要做一些獲取上游服務器列表以及根據負載均衡選擇一個有效的遠端服務,并注入到對應的屬性中,供后面使用,真正調用遠端的插件式WebClientPlugin插件

//org.dromara.soul.plugin.httpclient.WebClientPlugin#execute
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
  //獲取上下文
  final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
  assert soulContext != null;
  //獲取url地址
  String urlPath = exchange.getAttribute(Constants.HTTP_URL);
  if (StringUtils.isEmpty(urlPath)) {
    Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
    return WebFluxResultUtils.result(exchange, error);
  }
  long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
  int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
  log.info("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
  HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
  WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
  //調用遠端服務
  return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
}
//org.dromara.soul.plugin.httpclient.WebClientPlugin#handleRequestBody
private Mono<Void> handleRequestBody(final WebClient.RequestBodySpec requestBodySpec,
                                         final ServerWebExchange exchange,
                                         final long timeout,
                                         final int retryTimes,
                                         final SoulPluginChain chain) {
  //使用異步編程方式調用遠端服務并返回結果
  return requestBodySpec.headers(httpHeaders -> {
    httpHeaders.addAll(exchange.getRequest().getHeaders());
    httpHeaders.remove(HttpHeaders.HOST);
  })
    .contentType(buildMediaType(exchange))
    .body(BodyInserters.fromDataBuffers(exchange.getRequest().getBody()))
    .exchange()
    .doOnError(e -> log.error(e.getMessage()))
    .timeout(Duration.ofMillis(timeout))
    .retryWhen(Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
               .retryMax(retryTimes)
               .backoff(Backoff.exponential(Duration.ofMillis(200), Duration.ofSeconds(20), 2, true)))
    .flatMap(e -> doNext(e, exchange, chain));

}

到這里我們就完整走了一遍http的鏈路跟蹤,中間還有很多其他的細節需要之后在講解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容