1. 程式人生 > 實用技巧 >Soul閘道器原始碼閱讀(九)外掛配置載入初探

Soul閘道器原始碼閱讀(九)外掛配置載入初探

Soul閘道器原始碼閱讀(九)外掛配置載入初探


簡介

    今日來探索一下外掛的初始化,及相關的配置的載入

原始碼Debug

外掛初始化

    首先來到我們非常熟悉的外掛鏈呼叫的類: SoulWebHandler ,在其中的 DefaultSoulPluginChain ,我們看到plugins是通過建構函式傳入的

    我們來跟蹤一下是誰傳入的,在SoulWebHandler的public那一行打上斷點,重啟後跟蹤呼叫棧

public final class SoulWebHandler implements WebHandler {

    private final List<SoulPlugin> plugins;

    // 在這裡也看到plugins是通過建構函式傳入的,在public這一行打上斷點,然後重啟
    public SoulWebHandler(final List<SoulPlugin> plugins) {
        this.plugins = plugins;
        String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
        if (Objects.equals(schedulerType, "fixed")) {
            int threads = Integer.parseInt(System.getProperty(
                    "soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
            scheduler = Schedulers.newParallel("soul-work-threads", threads);
        } else {
            scheduler = Schedulers.elastic();
        }
    }

    @Override
    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());
        // 傳入 plugins 到 DefaultSoulPluginChain
        return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
                .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
    }

    private static class DefaultSoulPluginChain implements SoulPluginChain {

        private int index;

        private final List<SoulPlugin> plugins;

        // 通過建構函式得到 plugins
        DefaultSoulPluginChain(final List<SoulPlugin> plugins) {
            this.plugins = plugins;
        }

        @Override
        public Mono<Void> execute(final ServerWebExchange exchange) {
            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();
            });
        }
    }
}

    我這有點慢,重啟等以後後,我們檢視上一個呼叫,來到了 SoulConfiguration

    通過類的大致資訊可以看到是Spring之類的載入機制。通過前面文章的分析中,大致應該就是配置了 auto configuration ,然後啟動的時候Spring進行載入的

public class SoulConfiguration {

    @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);
    }
}

    大致就知道是Spring自動載入的,通過AutoConfiguration機制,下面就是Spring相關了,這裡就不繼續跟蹤了

配置初始化

    在上一篇的文章分析中,我們分析了路由匹配的大致細節,需要選擇器和規則的配置,下面我們來跟蹤一下這些配置的初始化

    首先進入路由匹配核心類: AbstractSoulPlugin ,其中進行 pluginData 、selectorData、rules的獲取,如下面程式碼註釋:

public abstract class AbstractSoulPlugin implements SoulPlugin {

    @Override
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        String pluginName = named();
        // 獲取外掛資料
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        if (pluginData != null && pluginData.getEnabled()) {
            // 根據外掛名稱,獲取選擇器資料
            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
            if (CollectionUtils.isEmpty(selectors)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            final SelectorData selectorData = matchSelector(exchange, selectors);
            if (Objects.isNull(selectorData)) {
                return handleSelectorIsNull(pluginName, exchange, chain);
            }
            selectorLog(selectorData, pluginName);
            // 根據選擇器的id,獲取其規則資料
            final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
            if (CollectionUtils.isEmpty(rules)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            RuleData rule;
            if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
                rule = rules.get(rules.size() - 1);
            } else {
                rule = matchRule(exchange, rules);
            }
            if (Objects.isNull(rule)) {
                return handleRuleIsNull(pluginName, exchange, chain);
            }
            ruleLog(rule, pluginName);
            return doExecute(exchange, chain, selectorData, rule);
        }
        return chain.execute(exchange);
    }
}

    我們到這個類: BaseDataCache 看一下,看到它有兩個資料的Map儲存:外掛資料、選擇器資料、規則資料

public final class BaseDataCache {
     
    /**
     * pluginName -> PluginData.
     */
    private static final ConcurrentMap<String, PluginData> PLUGIN_MAP = Maps.newConcurrentMap();
    
    /**
     * pluginName -> SelectorData.
     */
    private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();
    
    /**
     * selectorId -> RuleData.
     */
    private static final ConcurrentMap<String, List<RuleData>> RULE_MAP = Maps.newConcurrentMap();
    
}

    我們發現它是一個靜態單例,而外掛資料的配置是使用下面的函式,我們在函式上打上斷點,看看是誰呼叫了它,它的呼叫棧是怎麼樣的

public final class BaseDataCache {
    public void cachePluginData(final PluginData pluginData) {
        Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.put(data.getName(), data));
    }
}

    在public這一行打上斷點,重啟,順利進入上面的函式,我們檢視下 pluginData ,還挺簡單,大致如下

PluginData(id=1, name=sign, config=null, role=1, enabled=false)

    我們跟蹤其呼叫棧,其呼叫鏈的類和函式如下,類在呼叫棧的順序也是對應的

public class CommonPluginDataSubscriber implements PluginDataSubscriber {
    
    // 在這個裡面觸發 subscribeDataHandler , subscribeDataHandler 觸發 DaseDataCache的呼叫
    @Override
    public void onSubscribe(final PluginData pluginData) {
        subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
    }
    
    private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
        Optional.ofNullable(classData).ifPresent(data -> {
            // 外掛資料的操作
            if (data instanceof PluginData) {
                PluginData pluginData = (PluginData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    // 在這進行觸發呼叫
                    BaseDataCache.getInstance().cachePluginData(pluginData);
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removePluginData(pluginData);
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.removePlugin(pluginData));
                }
            // 選擇器的操作
            } else if (data instanceof SelectorData) {
                SelectorData selectorData = (SelectorData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.removeSelector(selectorData));
                }
            // 規則的操作
            } else if (data instanceof RuleData) {
                RuleData ruleData = (RuleData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheRuleData(ruleData);
                    Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeRuleData(ruleData);
                    Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.removeRule(ruleData));
                }
            }
        });
    }
}

// 這個類中觸發了上面單個外掛的配置,並且這個類拿到了所有的外掛資料
public class PluginDataHandler extends AbstractDataHandler<PluginData> {

    @Override
    protected void doRefresh(final List<PluginData> dataList) {
        // 觸發上面的呼叫
        pluginDataSubscriber.refreshPluginDataSelf(dataList);
        dataList.forEach(pluginDataSubscriber::onSubscribe);
    }
}

// 繼續跟下去,看看datalist的來源
public abstract class AbstractDataHandler<T> implements DataHandler {

    @Override
    public void handle(final String json, final String eventType) {
        List<T> dataList = convert(json);
        if (CollectionUtils.isNotEmpty(dataList)) {
            DataEventTypeEnum eventTypeEnum = DataEventTypeEnum.acquireByName(eventType);
            switch (eventTypeEnum) {
                case REFRESH:
                case MYSELF:
                    // 觸發上面函式的呼叫
                    doRefresh(dataList);
                    break;
                case UPDATE:
                case CREATE:
                    doUpdate(dataList);
                    break;
                case DELETE:
                    doDelete(dataList);
                    break;
                default:
                    break;
            }
        }
    }
}

// 來到了websocket,進行跟
public class WebsocketDataHandler {
    public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
        ENUM_MAP.get(type).handle(json, eventType);
    }
}

// 在這個類中,看到外掛資料列表是從websocket來的,那就是從Soul-Admin那邊獲取來到的資料
public final class SoulWebsocketClient extends WebSocketClient {

    public void onMessage(final String result) {
        handleResult(result);
    }  

    @SuppressWarnings("ALL")
    private void handleResult(final String result) {
        WebsocketData websocketData = GsonUtils.getInstance().fromJson(result, WebsocketData.class);
        ConfigGroupEnum groupEnum = ConfigGroupEnum.acquireByName(websocketData.getGroupType());
        String eventType = websocketData.getEventType();
        String json = GsonUtils.getInstance().toJson(websocketData.getData());
        websocketDataHandler.executor(groupEnum, json, eventType);
    }
}

    看下result的大致內容:也就是一些簡單的配置

{
	"groupType": "PLUGIN",
	"eventType": "MYSELF",
	"data": [{
		"id": "1",
		"name": "sign",
		"role": 1,
		"enabled": false
	}, {
		"id": "9",
		"name": "hystrix",
		"role": 0,
		"enabled": false
	}]
}

    結合路由匹配的關鍵函式,好像它的作用就是用來判斷是否開啟和根據name獲得選擇器

public abstract class AbstractSoulPlugin implements SoulPlugin {

    @Override
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        String pluginName = named();
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        // 用於判斷是否開啟
        if (pluginData != null && pluginData.getEnabled()) {
            .......
        }
    }
}

    外掛資料這一塊的流程定位下來比較輕鬆,畢竟前面流程也梳理差不多了

    選擇器和規則資料的初始化流程,這裡就不進行定位了,邏輯和外掛資料基本一樣,這裡就不贅述了

總結

    今天分析了外掛的初始化和相關配置的初始化

    外掛的初始化是使用Spring的自動配置載入機制進行實現了,結合使用外掛都需要引入哪些start的依賴

    外掛資料、選擇器資料、規則資料的初始化,都是從Websocket(或者同步通訊模組)那邊過來的,接收到資料以後,進行配置載入到本地

    我們還看到一些刪除和更新相關的操作,這些屬於後面的資料同步分析了,這裡先放一放

Soul閘道器原始碼分析文章列表

Github

掘金