前面幾篇我們重點(diǎn)去挖掘了網(wǎng)關(guān)配置數(shù)據(jù)的同步,接下來我們會去分析soul
網(wǎng)關(guān)的插件體系带兜,就開始吧。
前言
我們知道網(wǎng)關(guān)最核心的能力是進(jìn)行http
請求的轉(zhuǎn)發(fā)斩熊。那在我們的soul
網(wǎng)關(guān)中又是如何實(shí)現(xiàn)這一功能的里逆?這里的實(shí)現(xiàn)就是我們今天要分析的主題divide
插件进胯。
分析
- 先從
configuration
入手,找到divide
插件的配置類DividePluginConfiguration
原押。
- 從上圖中可以看到配置類創(chuàng)建了幾個關(guān)鍵的
bean
胁镐。我們先來分析DividePlugin
,核心邏輯doExecute
诸衔。
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
// context模式
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 獲取分流規(guī)則handle
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
// 根據(jù)當(dāng)前選擇器獲取到后臺節(jié)點(diǎn)list
// TODO 需分析后臺節(jié)點(diǎn)獲取的實(shí)現(xiàn)盯漂,會涉及到后臺節(jié)點(diǎn)的探活,增加活剔除節(jié)點(diǎn)
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
// 如果獲取到的后臺節(jié)點(diǎn)為空笨农,則直接返回
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();
// 根據(jù)遠(yuǎn)程客戶端ip獲取后臺節(jié)點(diǎn)就缆,經(jīng)過負(fù)載均衡策略
// TODO 需分析負(fù)載均衡的實(shí)現(xiàn)
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
// 經(jīng)過負(fù)載均衡策略后,未選擇到節(jié)點(diǎn)則返回錯誤
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
// 根據(jù)后臺節(jié)點(diǎn)對象構(gòu)建服務(wù)地址谒亦,并將其放于交換器竭宰,傳遞下去,進(jìn)行轉(zhuǎn)發(fā)
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
// 調(diào)用的幾個關(guān)鍵參數(shù):服務(wù)地址 請求參數(shù) 超時 重試次數(shù)
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);
}
- 從上圖中我們可以繼續(xù)跟蹤兩個子邏輯【后端節(jié)點(diǎn)列表的獲取】以及【負(fù)載均衡算法】
后端節(jié)點(diǎn)列表的獲取
soul-bootstrap
后端節(jié)點(diǎn)探活
- 由
UpstreamCacheManager
實(shí)現(xiàn)份招,其中后端節(jié)點(diǎn)探活的結(jié)構(gòu)圖如下
- 我們來分解一下這個圖
- 存放后端節(jié)點(diǎn)數(shù)據(jù)有兩個
map
切揭,UPSTREAM_MAP
與UPSTREAM_MAP_TEMP
。至于為什么會有兩個map
锁摔,主要是前者會存儲所有的后端節(jié)點(diǎn)廓旬,由soul-admin
那邊同步過來,不管其是否存活谐腰;而后者則只存儲探活成功的節(jié)點(diǎn)孕豹,為網(wǎng)關(guān)負(fù)載的有效節(jié)點(diǎn)。 - 還有個
scheduler
十气,該scheduler
開啟了一個線程scheduled-upstream-task
執(zhí)行調(diào)度任務(wù)scheduled
励背,默認(rèn)探活調(diào)度的間隔為30s
,建議設(shè)置為1s
桦踊,需手設(shè)置 - 該
scheduled
邏輯是遍歷所有后端節(jié)點(diǎn)UPSTREAM_MAP
椅野,通過挨個探活check
终畅,完成有效后端節(jié)點(diǎn)UPSTREAM_MAP_TEMP
的替換與剔除 - 這里探活
check
的實(shí)現(xiàn)方式:后端節(jié)點(diǎn)upstream
服務(wù)地址url
是否為ip
籍胯,為ip
則用ip+port
建立socket
連接進(jìn)行探測;否則直接判斷節(jié)點(diǎn)host
是否可達(dá)离福;采用socket
方式探活時沒有顯式的超時設(shè)置杖狼,而host
是否可達(dá)則會1s
超時
- 存放后端節(jié)點(diǎn)數(shù)據(jù)有兩個
- 以上只是分析了
upstream
的探活實(shí)現(xiàn),當(dāng)然還有所有后端節(jié)點(diǎn)UPSTREAM_MAP
數(shù)據(jù)的初始化與變更 - 這個邏輯就比較簡單妖爷,通過公共的數(shù)據(jù)變更機(jī)制
handler
蝶涩,在選擇器selector
配置發(fā)生變化的時候理朋,進(jìn)行相應(yīng)選擇器后端節(jié)點(diǎn)列表的添加add
與移除remove
補(bǔ)充soul-admin
端的后端節(jié)點(diǎn)探活
- 關(guān)鍵類
UpstreamCheckService
,我們看到這個類在實(shí)例化之后的時候就會去執(zhí)行后端節(jié)點(diǎn)探活的邏輯
@PostConstruct
public void setup() {
// 從數(shù)據(jù)庫中獲取到所有的后端節(jié)點(diǎn)數(shù)據(jù)绿聘,初始邏輯
PluginDO pluginDO = pluginMapper.selectByName(PluginEnum.DIVIDE.getName());
if (pluginDO != null) {
List<SelectorDO> selectorDOList = selectorMapper.findByPluginId(pluginDO.getId());
for (SelectorDO selectorDO : selectorDOList) {
List<DivideUpstream> divideUpstreams = GsonUtils.getInstance().fromList(selectorDO.getHandle(), DivideUpstream.class);
if (CollectionUtils.isNotEmpty(divideUpstreams)) {
UPSTREAM_MAP.put(selectorDO.getName(), divideUpstreams);
}
}
}
// 如果探活開啟嗽上,則會執(zhí)行探活,可配置熄攘,默認(rèn)是開啟的
if (check) {
// 開啟調(diào)度線程池默認(rèn)每10s執(zhí)行一次check
new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), SoulThreadFactory.create("scheduled-upstream-task", false))
.scheduleWithFixedDelay(this::scheduled, 10, scheduledTime, TimeUnit.SECONDS);
}
}
- 從上得知啟動過程會開啟調(diào)度線程池兽愤,默認(rèn)每
10s
執(zhí)行后端探活的邏輯scheduled
方法,探活線程的個數(shù)為cpu
的核心數(shù) - 這里對后端節(jié)點(diǎn)的探活邏輯與上述
soul-bootstrap
端類似挪圾,只是在得到存活節(jié)點(diǎn)的list
之后浅萧,會判斷是否存在后端節(jié)點(diǎn)數(shù)目的變化(這里的探活不會增加新節(jié)點(diǎn),只有可能剔除一些不存活的節(jié)點(diǎn)哲思,所以只要存在存活節(jié)點(diǎn)數(shù)與所有節(jié)點(diǎn)不一致的情況洼畅,則就發(fā)生了變更
) - 如果存在變化,則會執(zhí)行
updateSelectorHandler
的邏輯棚赔,該邏輯主要會做兩件事情:- 將存活節(jié)點(diǎn)
list
更新到soul-admin
數(shù)據(jù)庫帝簇,保存起來 - 同時發(fā)布
selector
配置變更的事件給到soul-bootstrap
,soul-bootstrap
端就會更新其后端節(jié)點(diǎn)的數(shù)據(jù)靠益,完成探活節(jié)點(diǎn)的同步
- 將存活節(jié)點(diǎn)
負(fù)載均衡算法
下篇講解己儒,To be contined...