【Soul閘道器探祕】http資料同步-Admin通知前處理

騰業發表於2021-01-29

引言

本篇開始研究 Soul 閘道器 http 資料同步,將分為三篇進行分析:

  • 《Admin通知前處理》
  • 《變更通知機制》
  • 《Bootstrap處理變更通知》

希望三篇完結後能對 Soul 的 http 資料同步策略有所收穫。

本篇旨在探究 soul-admin 端在發起變更通知前所做的處理。

不同資料變更的處理模式應當是一致的,故本篇以 selector 配置變更為切入點進行深入。

一、配置變更入口

找到 SelectorController,這是 selector 配置變更的入口

image-20210129065312321

其持有一個 SelectorService 引用,通過 SelectorService 實現 selector 配置變更。

二、配置變更服務

再來看看 SelectorService,實現了配置變更的具體處理。

image-20210129065816692

其內部持有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 的時間通知機制。

事件定義

image-20210129072813542

DataChangedEvent 繼承 ApplicationEvent,提供了 DataChangedEvent(groupKey, type, source) 事件構造方法

事件監聽器

image-20210129072918112

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 介面

image-20210129073522328

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 同步策略的變更通知機制,期待驚喜。

個人知識庫

高效能微服務API閘道器-Soul

相關文章