引言
本篇開始研究 Soul 閘道器 http 資料同步,將分為三篇進行分析:
- 《Admin通知前處理》
- 《變更通知機制》
- 《Bootstrap處理變更通知》
希望三篇完結後能對 Soul 的 http 資料同步策略有所收穫。
本篇旨在探究 soul-admin
端在發起變更通知前所做的處理。
不同資料變更的處理模式應當是一致的,故本篇以 selector 配置變更為切入點進行深入。
一、配置變更入口
找到 SelectorController,這是 selector 配置變更的入口
其持有一個 SelectorService 引用,通過 SelectorService 實現 selector 配置變更。
二、配置變更服務
再來看看 SelectorService,實現了配置變更的具體處理。
其內部持有5個 mapper、1個 eventPublisher和1個 upstreamCheckService,對外提供一系列對 selector 的crud方法
注意 createOrUpdate 方法
public int createOrUpdate(final SelectorDTO selectorDTO) {
int selectorCount;
SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
List<SelectorConditionDTO> selectorConditionDTOs = selectorDTO.getSelectorConditions();
// 資料落庫
if (StringUtils.isEmpty(selectorDTO.getId())) {
selectorCount = selectorMapper.insertSelective(selectorDO);
selectorConditionDTOs.forEach(selectorConditionDTO -> {
selectorConditionDTO.setSelectorId(selectorDO.getId());
selectorConditionMapper.insertSelective(SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO));
});
} else {
selectorCount = selectorMapper.updateSelective(selectorDO);
//delete rule condition then add
selectorConditionMapper.deleteByQuery(new SelectorConditionQuery(selectorDO.getId()));
selectorConditionDTOs.forEach(selectorConditionDTO -> {
selectorConditionDTO.setSelectorId(selectorDO.getId());
SelectorConditionDO selectorConditionDO = SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO);
selectorConditionMapper.insertSelective(selectorConditionDO);
});
}
// 釋出 spring 事件
publishEvent(selectorDO, selectorConditionDTOs);
// 更新 divide 上游服務
updateDivideUpstream(selectorDO);
return selectorCount;
}
處理策略是先落庫,再發布 spring 事件,最後更新 divide 上游服務
三、spring 事件通知機制
此處涉及 spring 的事件通知機制,在此簡要說明:
ApplicationContext通過ApplicationEvent類和ApplicationListener介面提供事件處理。
如果一個bean實現ApplicationListener介面在容器中,每次一個ApplicationEvent被髮布到ApplicationContext中,這類bean就會收到這些通知。
實現Spring事件機制主要有4個類:
- ApplicationEvent:事件,每個實現類表示一類事件,可攜帶資料。
- ApplicationListener:事件監聽器,用於接收事件處理時間。
- ApplicationEventMulticaster:事件管理者,用於事件監聽器的註冊和事件的廣播。
- ApplicationEventPublisher:事件釋出者,委託ApplicationEventMulticaster完成事件釋出。
四、soul 實現事件通知
下面我們看看 Soul 是如何使用 spring 的時間通知機制。
事件定義
DataChangedEvent 繼承 ApplicationEvent,提供了 DataChangedEvent(groupKey, type, source) 事件構造方法
事件監聽器
DataChangedEventDispatcher 實現了 ApplicationListener介面,藉助 onApplicationEvent 方法監聽事件
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
switch (event.getGroupKey()) {
case APP_AUTH:
listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
break;
case PLUGIN:
listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType());
break;
case RULE:
listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
break;
case SELECTOR:
listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
break;
case META_DATA:
listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType());
break;
default:
throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
}
}
}
該方法內按事件型別分別處理,DataChangedEventDispatcher 同時實現了 InitializingBean 介面,在初始化後完成 listeners 的注入。
五、響應資料變更事件
上面的事件監聽處理用到 soul 的 DataChangedListener 介面
DataChangedListener 實現了不同型別事件的事件響應方法用於響應 DataChangedEvent 事件。
1)AbstractDataChangedListener 的 onSelectorChanged 實現:
public void onSelectorChanged(final List<SelectorData> changed, final DataEventTypeEnum eventType) {
if (CollectionUtils.isEmpty(changed)) {
return;
}
// 更新 selector 快取
this.updateSelectorCache();
// selector 變更後處理,實現具體的變更通知
this.afterSelectorChanged(changed, eventType);
}
可以看到 selector 變更處理是先更快取後發通知。
2)AbstractDataChangedListener 的 updateSelectorCache 實現:
protected void updateSelectorCache() {
this.updateCache(ConfigGroupEnum.SELECTOR, selectorService.listAll());
}
3)AbstractDataChangedListener 的 updateCache 實現:
protected <T> void updateCache(final ConfigGroupEnum group, final List<T> data) {
String json = GsonUtils.getInstance().toJson(data);
ConfigDataCache newVal = new ConfigDataCache(group.name(), json, Md5Utils.md5(json), System.currentTimeMillis());
ConfigDataCache oldVal = CACHE.put(newVal.getGroup(), newVal);
log.info("update config cache[{}], old: {}, updated: {}", group, oldVal, newVal);
}
可以看到最終是建立對應的 ConfigDataCache 存入 CACHE。
總結
本篇梳理了 soul-admin
在真正發出資料變更通知前的處理脈絡,其策略是:先寫庫後更快取,最後發出資料變更通知。
先寫庫保證資料不丟,另外在叢集部署時,其他 soul-admin
節點也可通過瀏覽頁面時查庫保證資料一致。
意外學到 spring 的事件通知機制,soul 中的設計果真小巧精妙。
下篇,將探究 http 同步策略的變更通知機制,期待驚喜。