通過上篇旋膳,我們把基本的環(huán)境搭建好盅称,可以著手去源碼層面去看看nacos
的數(shù)據(jù)同步實現(xiàn)奈偏。
soul-admin
- 找到數(shù)據(jù)同步配置的入口
DataSyncConfiguration
淮捆,我們可以看到關(guān)于Nacos
配置監(jiān)聽的初始化類NacosListener
-
當(dāng)然下圖會更直觀
- 我們再看下
NacosListener
該類的實現(xiàn)
- 分為兩塊:數(shù)據(jù)變更監(jiān)聽
NacosDataChangedListener
和數(shù)據(jù)初始化的處理NacosDataInit
數(shù)據(jù)初始化的處理-NacosDataInit
- 先看數(shù)據(jù)初始化的處理
NacosDataInit
郁油。該類實現(xiàn)了spring
的CommandLineRunner
,也就是這個bean
是屬于spring
應(yīng)用的一部分攀痊,程序啟動時會自動執(zhí)行該bean
的run
方法桐腌,我們看下run
方法邏輯
public void run(final String... args) {
// 存在三部分的data,plugin & auth & meta
String pluginDataId = NacosPathConstants.PLUGIN_DATA_ID;
String authDataId = NacosPathConstants.AUTH_DATA_ID;
String metaDataId = NacosPathConstants.META_DATA_ID;
// 只有當(dāng)admin的配置數(shù)據(jù)沒有在nacos上存在時苟径,才同步所有數(shù)據(jù)
if (dataIdNotExist(pluginDataId) && dataIdNotExist(authDataId) && dataIdNotExist(metaDataId)) {
syncDataService.syncAll(DataEventTypeEnum.REFRESH);
}
}
- 我們知道案站,整個
soul-admin
中的配置變更,都是通過spring
的應(yīng)用事件機制實現(xiàn)棘街。初始化中調(diào)用syncDataService.syncAll
則會從soul-admin
數(shù)據(jù)庫中取出所有的配置數(shù)據(jù)(plugin & selector & rule & auth)蟆盐,并發(fā)布DataChangedEvent
的數(shù)據(jù)變更事件承边。
public boolean syncAll(final DataEventTypeEnum type) {
appAuthService.syncData();
List<PluginData> pluginDataList = pluginService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
List<SelectorData> selectorDataList = selectorService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
List<RuleData> ruleDataList = ruleService.listAll();
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
metaDataService.syncData();
return true;
}
- 在前面文章分析,我們可以知道舱禽,對于配置數(shù)據(jù)變更是由
DataChangedEventDispatcher
來接收事件的處理炒刁,其主要邏輯就是遍歷DataChangedListener
的bean,然后根據(jù)配置的不同類型去調(diào)用listener
中不同的方法 - 當(dāng)我們采用
nacos
的同步時誊稚,則會調(diào)用NacosDataChangedListener
的相關(guān)方法
數(shù)據(jù)變更監(jiān)聽-NacosDataChangedListener
- 我們只分析插件數(shù)據(jù)的變更處理
onPluginChanged
翔始,其余類型的變更處理類似
public void onPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) {
// 1. 更新加載到內(nèi)存的數(shù)據(jù)
// 更新策略:將原有內(nèi)存中的數(shù)據(jù)清掉,然后將nacos拉回來的數(shù)據(jù)重新載入到內(nèi)存
updatePluginMap(getConfig(NacosPathConstants.PLUGIN_DATA_ID));
// 2. 處理此次變更的數(shù)據(jù)
switch (eventType) {
case DELETE:
changed.forEach(plugin -> PLUGIN_MAP.remove(plugin.getName()));
break;
case REFRESH:
case MYSELF:
Set<String> set = new HashSet<>(PLUGIN_MAP.keySet());
// 替換掉內(nèi)存中的數(shù)據(jù)里伯,以數(shù)據(jù)庫全量的內(nèi)容為準(zhǔn)
// 也就是說城瞎,每次的REFRESH或者MYSELF變更,傳過來的數(shù)據(jù)都是當(dāng)前數(shù)據(jù)庫的全量數(shù)據(jù)
changed.forEach(plugin -> {
set.remove(plugin.getName());
PLUGIN_MAP.put(plugin.getName(), plugin);
});
PLUGIN_MAP.keySet().removeAll(set);
break;
default:
changed.forEach(plugin -> PLUGIN_MAP.put(plugin.getName(), plugin));
break;
}
// 3. 再發(fā)布處理完之后的配置給到nacos
publishConfig(NacosPathConstants.PLUGIN_DATA_ID, PLUGIN_MAP);
}
總結(jié)
soul-admin
端配置數(shù)據(jù)同步采用nacos
疾瓮,其大致處理流程如下:
-
soul-admin
端的數(shù)據(jù)同步分為兩塊:數(shù)據(jù)變更監(jiān)聽NacosDataChangedListener
和數(shù)據(jù)初始化的處理NacosDataInit
脖镀; - 當(dāng)
soul-admin
啟動時就會將所有配置數(shù)據(jù)從數(shù)據(jù)庫加載出來,發(fā)布數(shù)據(jù)變更事件狼电;如果是有多個節(jié)點蜒灰,則都是同樣的操作,也就是每個啟動啟動時肩碟,都會從數(shù)據(jù)庫拉取到所有配置數(shù)據(jù)强窖,同步到nacos
- 數(shù)據(jù)變更的監(jiān)聽器
NacosDataChangedListener
,會監(jiān)聽到數(shù)據(jù)變更的事件削祈,根據(jù)不同的配置類型翅溺,調(diào)用不同的處理方法onXxxChanged
進行處理 - 所有的配置數(shù)據(jù)都會先在內(nèi)存放置一份,
NacosDataChangedListener
中XXX_MAP
存放髓抑; - 當(dāng)有配置變更時咙崎,先從
nacos
中拉取一份配置下來,將現(xiàn)有內(nèi)存XXX_MAP
中存放的數(shù)據(jù)清除吨拍,然后用nacos
拉取的配置進行替換褪猛; - 如果要
delete
或者add
,則直接在nacos
拉取的配置上進行對應(yīng)操作羹饰;如果是refresh
伊滋,則需要再將當(dāng)前數(shù)據(jù)庫中最新配置替換掉nacos
拉取的配置 - 采用此策略同步配置時,
soul-admin
的各節(jié)點中的內(nèi)存會存在不一致的情況严里。不過沒有關(guān)系新啼,因為每次同步之前都會從nacos
取回數(shù)據(jù)再進行操作。
soul-bootstrap
接下來分析soul-bootstrap
端刹碾。
-
先看下關(guān)鍵類圖
-
NacosSyncDataConfiguration
配置中會初始化bean
:NacosSyncDataService
與nacos
的ConfigService
-
nacos
數(shù)據(jù)同步服務(wù)實例化過程中會執(zhí)行啟動邏輯燥撞,改邏輯會監(jiān)聽nacos
上各個類型的數(shù)據(jù)配置
public NacosSyncDataService(final ConfigService configService, final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
super(configService, pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
start();
}
public void start() {
// 監(jiān)聽邏輯
watcherData(PLUGIN_DATA_ID, this::updatePluginMap);
watcherData(SELECTOR_DATA_ID, this::updateSelectorMap);
watcherData(RULE_DATA_ID, this::updateRuleMap);
watcherData(META_DATA_ID, this::updateMetaDataMap);
watcherData(AUTH_DATA_ID, this::updateAuthMap);
}
- 看下這里的
watchData
邏輯
protected void watcherData(final String dataId, OnChange oc) {
// 創(chuàng)建nacos的監(jiān)聽器
Listener listener = new Listener() {
@Override
public void receiveConfigInfo(final String configInfo) {
// 監(jiān)聽的處理邏輯
oc.change(configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
};
// 初始拉取一次配置,并處理配置
oc.change(getConfigAndSignListener(dataId, listener));
// 添加nacos的配置監(jiān)聽,computeIfAbsent線程安全
LISTENERS.computeIfAbsent(dataId, key -> new ArrayList<>()).add(listener);
}
- 從上得知物舒,
nacos
配置監(jiān)聽的邏輯實際上就是調(diào)用updateXXXMap
方法色洞,我們以updatePluginMap
為例
protected void updatePluginMap(final String configInfo) {
try {
List<PluginData> pluginDataList = new ArrayList<>(GsonUtils.getInstance().toObjectMap(configInfo, PluginData.class).values());
// 將從nacos獲取到的插件配置,更新內(nèi)存中的配置
// 更新策略:先remove cache, 然后再put冠胯,一個個處理
pluginDataList.forEach(pluginData -> Optional.ofNullable(pluginDataSubscriber).ifPresent(subscriber -> {
// 先刪數(shù)據(jù)
subscriber.unSubscribe(pluginData);
// 然后添加數(shù)據(jù)
subscriber.onSubscribe(pluginData);
}));
} catch (JsonParseException e) {
log.error("sync plugin data have error:", e);
}
}
- 該方法的基本邏輯是將從
nacos
獲取到的配置火诸,去更新soul-boostrap
端內(nèi)存中存儲的配置 - 其更新的策略是先刪除配置數(shù)據(jù),然后將新的數(shù)據(jù)添加到內(nèi)存
- 更新的調(diào)用邏輯如下:
- 刪除配置:
PluginDataSubscriber.unSubscribe
->CommonPluginDataSubscriber.unSubscribe
->CommonPluginDataSubscriber.subscribeDataHandler
->BaseDataCache.getInstance().removePluginData
->PluginDataHandler.removePlugin
- 添加配置:
PluginDataSubscriber.onSubscribe
->CommonPluginDataSubscriber.onSubscribe
->CommonPluginDataSubscriber.subscribeDataHandler
->BaseDataCache.getInstance().cachePluginData
->PluginDataHandler.handlerPlugin
- 刪除配置:
- 關(guān)于
nacos
如何實現(xiàn)的配置變更的監(jiān)聽這里就不展開了荠察,對于應(yīng)用端置蜀,只要完成NacosConfigService
初始化以及添加監(jiān)聽邏輯就行了。
總結(jié)
在分析了soul-admin
與soul-bootstrap
基于nacos
的配置數(shù)據(jù)同步實現(xiàn)后悉盆,感覺相比較HttpLongPolling
的在代碼實現(xiàn)層面要簡單一些盯荤。主要的差別在于配置變更監(jiān)聽的實現(xiàn):對于nacos
的實現(xiàn)方式而言,因nacos
本身就是一個配置服務(wù)中間件焕盟,其提供的nacos-client
能在nacos-server
端配置存在變更后秋秤,nacos-client
端就能獲取到變更的配置,通過client
端添加自身配置變更監(jiān)聽的邏輯后脚翘,就能完成soul-bootstrap
內(nèi)存中配置數(shù)據(jù)的更新灼卢;而HttpLongPolling
則需要自身完成配置變更監(jiān)聽機制,實現(xiàn)邏輯會更復(fù)雜些来农。