做者:沈祥俊java
插件是 Soul 的靈魂。web
Soul 使用了插件化設計思想,實現了插件的熱插拔,且極易擴展。內置豐富的插件支持,鑑權,限流,熔斷,防火牆等等。spring
Soul 是如何實現插件化設計的呢?編程
在探究插件化設計以前,咱們須要先了解下微內核架構(又稱插件化架構)。bootstrap
微內核架構也被稱爲插件化架構,是一種面向功能進行拆分的可擴展性架構,一般用於實現基於產品的應用。springboot
應用邏輯被分割爲獨立的插件模塊和核心系統,提供了可擴展性、靈活性、功能隔離和自定義處理邏輯的特性。markdown
微內核架構的本質,是將變化封裝在插件裏面,從而達到快速靈活擴展的目的,而又不影響總體系統的穩定。架構
核心系統設計的關鍵技術:負載均衡
**插件管理:**當前有哪些插件可用?如何加載這些插件?何時加載插件?框架
常見的實現方法是插件註冊表機制。
**插件鏈接:**插件如何鏈接到核心系統?
一般由核心系統制定鏈接規範,而後插件按照規範實現,核心系統按照規範加載便可。
常見鏈接機制主要有:OSGi(Eclipse使用)、消息模式、依賴注入(Spring使用)。
**插件通訊:**插件與插件、插件與核心系統如何通訊?
通訊必須通過核心系統,所以一般由核心系統提供插件通訊機制。
參照微內核架構來看,Soul 的 soul-web
模塊至關於核心系統,soul-plugin
下的子模塊至關於插件模塊。
插件管理方面:
soul-bootstrap
模塊的 pom 文件充當插件列表, 以硬編碼的方式引入各插件。
在容器啓動階段,藉助 springboot 的 starter 機制自動掃描並註冊插件 bean 到 Spring 容器。
插件鏈接方面:
藉助 springboot 支持的多實例自動注入能力(ObjectProvider plugins),將插件 Bean 列表注入到網關的插件鏈,實現插件與網關的鏈接。
插件通訊方面:
先在插件鏈初始化階段完成插件排序,而後在插件處理時,藉助貫穿整個插件鏈的 ServerWebExchange 完成向下遊插件的定向傳參,即某種意義上的插件通訊機制。
Soul 網關中定義了一條插件鏈,全部的插件都在這條鏈上依次處理。
在探究插件鏈以前,咱們先來看看插件實現。
Soul 中全部插件最終均繼承自 SoulPlugin,其完整繼承關係以下所示:
能夠看到,Soul 的插件生態極其豐富,正是如此豐富的插件支撐起了 Soul 網關強大的擴展能力。
咱們以經常使用的 DividePlugin 爲例,分析插件內部所作工做。
DividePlugin 繼承結構:
DividePlugin 繼承自 AbstractSoulPlugin,最終實現了 SoulPlugin 接口。
1)先關注 SoulPlugin,該插件接口結構以下:
每次處理時,將先進行 skip 判斷,不跳過則執行 excute 處理方法。
2)再來看下 AbstractSoulPlugin,該抽象類結構以下:
重點關注 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,其結構以下:
重點關注 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 將衆多插件整合到一塊兒進行統一調度處理。
插件鏈繼承結構:
能夠看到,Soul 中插件鏈 SoulPluginChain 僅有一個默認實現類 DefaultSoulPluginChain。
1)DefaultSoulPluginChain 類結構以下:
其持有經過構造方法傳入的插件鏈,看看 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 結構以下:
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 結構以下:
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 裏指定的配置類自動加載到容器。
最後,藉助 spring4.3 開始支持的 ObjectProvider,實現容器內插件 bean 的集合式注入,最終造成咱們看到的插件鏈。
本篇從微內核架構提及,並以此爲框架分析 Soul 的插件化設計,再結合源碼實現,基本理清了 Soul 中插件式設計的實現。
須要注意:
1)由 SoulConfiguration 自動裝配 SoulWebHandler,此時 SoulWebHandler 持有插件列表,但未初始化插件鏈。
2)待調用 handle 方法處理請求時,才初始化插件鏈進入插件處理。