Soul網關學習插件鏈實現

Soul網關學習插件鏈實現

做者:沈祥俊java

1、引言

插件是 Soul 的靈魂。web

Soul 使用了插件化設計思想,實現了插件的熱插拔,且極易擴展。內置豐富的插件支持,鑑權,限流,熔斷,防火牆等等。spring

image-20210122021834793

Soul 是如何實現插件化設計的呢?編程

在探究插件化設計以前,咱們須要先了解下微內核架構(又稱插件化架構)。bootstrap

2、微內核架構

一、架構釋義

image.png

微內核架構也被稱爲插件化架構,是一種面向功能進行拆分的可擴展性架構,一般用於實現基於產品的應用。springboot

應用邏輯被分割爲獨立的插件模塊核心系統,提供了可擴展性、靈活性、功能隔離和自定義處理邏輯的特性。markdown

微內核架構的本質,是將變化封裝在插件裏面,從而達到快速靈活擴展的目的,而又不影響總體系統的穩定。架構

二、設計關鍵點

核心系統設計的關鍵技術:負載均衡

  • **插件管理:**當前有哪些插件可用?如何加載這些插件?何時加載插件?框架

    常見的實現方法是插件註冊表機制。

  • **插件鏈接:**插件如何鏈接到核心系統?

    一般由核心系統制定鏈接規範,而後插件按照規範實現,核心系統按照規範加載便可。

    常見鏈接機制主要有:OSGi(Eclipse使用)、消息模式、依賴注入(Spring使用)。

  • **插件通訊:**插件與插件、插件與核心系統如何通訊?

    通訊必須通過核心系統,所以一般由核心系統提供插件通訊機制。

3、Soul 的插件化設計

參照微內核架構來看,Soul 的 soul-web 模塊至關於核心系統,soul-plugin 下的子模塊至關於插件模塊。

插件管理方面:

soul-bootstrap 模塊的 pom 文件充當插件列表, 以硬編碼的方式引入各插件。

在容器啓動階段,藉助 springboot 的 starter 機制自動掃描並註冊插件 bean 到 Spring 容器。

插件鏈接方面:

藉助 springboot 支持的多實例自動注入能力(ObjectProvider plugins),將插件 Bean 列表注入到網關的插件鏈,實現插件與網關的鏈接。

插件通訊方面:

先在插件鏈初始化階段完成插件排序,而後在插件處理時,藉助貫穿整個插件鏈的 ServerWebExchange 完成向下遊插件的定向傳參,即某種意義上的插件通訊機制。

4、Soul 的插件化實現

Soul 網關中定義了一條插件鏈,全部的插件都在這條鏈上依次處理。

在探究插件鏈以前,咱們先來看看插件實現。

一、插件實現

Soul 中全部插件最終均繼承自 SoulPlugin,其完整繼承關係以下所示:

SoulPlugin

能夠看到,Soul 的插件生態極其豐富,正是如此豐富的插件支撐起了 Soul 網關強大的擴展能力。

咱們以經常使用的 DividePlugin 爲例,分析插件內部所作工做。

DividePlugin 繼承結構:

DividePlugin

DividePlugin 繼承自 AbstractSoulPlugin,最終實現了 SoulPlugin 接口。

1)先關注 SoulPlugin,該插件接口結構以下:

image-20210122025700589

  • execute 方法:處理方法,須要傳入 exchange交換區 和 SoulPluginChain插件鏈
  • getOrder 方法:取得序號,用做插件排序
  • named 方法:得到插件名
  • skip 方法:判斷是否跳過本次處理

每次處理時,將先進行 skip 判斷,不跳過則執行 excute 處理方法。

2)再來看下 AbstractSoulPlugin,該抽象類結構以下:

image-20210122030444704

重點關注 execute 方法,其核心代碼以下:

if (pluginData.getEnable()){
	// 獲取插件數據
	final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
	// 獲取選擇器數據
	final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
	final SelectorData selectorData = matchSelector(exchange, selectors);
	// 獲取規則
	final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
	RuleData rule;
  if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
  	//get last
    rule = rules.get(rules.size() - 1);
  } else {
    rule = matchRule(exchange, rules);
  }
  // 執行具體處理
  return doExecute(exchange, chain, selectorData, rule);
}
// 繼續執行後續插件處理
return chain.execute(exchange);
複製代碼

獲取選擇器數據和規則,而後傳入 doExecute 方法進行具體處理,doExecute 方法爲抽象方法,交由子類具體實現。

3)查看插件子類 DividePlugin,其結構以下:

image-20210122032336069

重點關注 doExecute 方法,如下是核心代碼:

// 獲取網關上下文和規則處理器
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
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);
// 設置 http url 
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// 設置 http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
複製代碼

很明顯,divide 插件只是完成目標上游服務的待分發,即根據選擇器和規則找到對應服務,再經過負載均衡策略分配上游服務實例。

而調用上游服務的工做是由其餘相應的 client 類插件完成。

二、插件鏈實現

藉由插件鏈,Soul 將衆多插件整合到一塊兒進行統一調度處理。

插件鏈繼承結構:

SoulPluginChain

能夠看到,Soul 中插件鏈 SoulPluginChain 僅有一個默認實現類 DefaultSoulPluginChain。

1)DefaultSoulPluginChain 類結構以下:

image-20210122040245671

其持有經過構造方法傳入的插件鏈,看看 execute 方法:

public Mono<Void> execute(final ServerWebExchange exchange) {
    // 反應式編程語法:Mono.defer
  	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();
    });
}
複製代碼

依次處理插件鏈上的插件,執行插件處理邏輯。

DefaultSoulPluginChain 是 SoulWebHandler 的內部類,看下 SoulWebHandler 的實現。

2)SoulWebHandler 結構以下:

image-20210122035525261

SoulWebHandler 是 web 請求處理的起點,在此建立並開始插件鏈的處理。

同 DefaultSoulPluginChain 同樣,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());
    return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
            .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
複製代碼

handle 方法負責插件鏈執行指標度量的採集,經過在 DefaultSoulPluginChain 執行時加訂閱實現,DefaultSoulPluginChain 在此處完成初始化。

全局查找 SoulWebHandler 構造方法,定位到 SoulConfiguration 的 soulWebHandler 方法。

3)SoulConfiguration 結構以下:

image-20210122042354171

SoulConfiguration 是 Soul 的核心配置類,負責自動裝配網關所需的核心 bean 對象。

如裝配 SoulWebHandler:

@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);
}
複製代碼

注意此處的插件列表通過了一次重排,重排順序參見 PluginEnum。

4)初始化 SoulWebHandler

soul-bootstrap 啓動的過程當中,全部插件是怎麼造成 ObjectProvider<List> plugins,而後初始化 SoulWebHandler 的呢?

SoulWebHandler 所在的配置類經過配置 @ComponentScan("org.dromara.soul"),通知 spring 掃描 org.dromara.soul 包。

藉助 springboot 的 starter 機制,將 spring.factories 裏指定的配置類自動加載到容器。

DividePluginConfiguration

最後,藉助 spring4.3 開始支持的 ObjectProvider,實現容器內插件 bean 的集合式注入,最終造成咱們看到的插件鏈。

總結

本篇從微內核架構提及,並以此爲框架分析 Soul 的插件化設計,再結合源碼實現,基本理清了 Soul 中插件式設計的實現。

須要注意:

1)由 SoulConfiguration 自動裝配 SoulWebHandler,此時 SoulWebHandler 持有插件列表,但未初始化插件鏈。

2)待調用 handle 方法處理請求時,才初始化插件鏈進入插件處理。

相關文章
相關標籤/搜索