Soul网关源码分析-http数据同步(一)

265 阅读3分钟

简介

上一篇已经分析了websocket方式数据同步的整理流程,这一篇来分析一下http方式同步数据到soul网关是如何处理的。

示例运行

涉及修改的配置如下:
soul-Admin端:

  sync:
      http:
        enabled: true

soul-Bootstrap端:

soul :
    file:
      enabled: true
    corss:
      enabled: true
    dubbo :
      parameter: multi
    sync:
        http:
             url : http://localhost:9095

分别启动Soul-admin、Soul-Bootstrap、Soul-Example-HTTP后进入源码分析

源码分析

通过Soul网关源码分析-websocket数据同步的分析,我们知道数据同步的核心类是CommonPluginDataSubscriber,这个类是用于订阅消息变更的,那么我们就从这个类入手,一步步将Http方式数据同步的流程梳理出来,我们在插件数据更新方法中打个断点,代码片段如下:

public class CommonPluginDataSubscriber implements PluginDataSubscriber {
    
    @Override
    public void onSubscribe(final PluginData pluginData) {
        subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
    }
}

通过alt+F7查看方法调用栈,发现是PluginDataRefresh的refresh方法调用到此方法,代码片段如下:

public class PluginDataRefresh extends AbstractDataRefresh<PluginData> {
@Override
    protected void refresh(final List<PluginData> data) {
        if (CollectionUtils.isEmpty(data)) {
            log.info("clear all plugin data cache");
            pluginDataSubscriber.refreshPluginDataAll();
        } else {
            pluginDataSubscriber.refreshPluginDataAll();
            data.forEach(pluginDataSubscriber::onSubscribe);
        }
    }
}

继续跟踪方法调用栈,发现AbstractDataRefresh这个类的refresh方法调用到PluginDataRefresh的refresh方法,关键代码如下:

public abstract class AbstractDataRefresh<T> implements DataRefresh {
 @Override
    public Boolean refresh(final JsonObject data) {
        boolean updated = false;
        JsonObject jsonObject = convert(data);
        if (null != jsonObject) {
            ConfigData<T> result = fromJson(jsonObject);
            if (this.updateCacheIfNeed(result)) {
                updated = true;
                refresh(result.getData());
            }
        }
        return updated;
    }
}

继续跟踪方法调用栈,发现是DataRefreshFactory的executor方法调用了AbstractDataRefresh这个类的refresh方法,而且这里看起来是使用了工厂模式,根据不同的数据类型,生产对应的DataRefresh来更新数据,关键代码如下:

public final class DataRefreshFactory {
    private static final EnumMap<ConfigGroupEnum, DataRefresh> ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);

    /**
     * Instantiates a new Data refresh factory.
     *
     * @param pluginDataSubscriber the plugin data subscriber
     * @param metaDataSubscribers  the meta data subscribers
     * @param authDataSubscribers  the auth data subscribers
     */
    public DataRefreshFactory(final PluginDataSubscriber pluginDataSubscriber,
                              final List<MetaDataSubscriber> metaDataSubscribers,
                              final List<AuthDataSubscriber> authDataSubscribers) {
        ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataRefresh(pluginDataSubscriber));
        ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataRefresh(pluginDataSubscriber));
        ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataRefresh(pluginDataSubscriber));
        ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AppAuthDataRefresh(authDataSubscribers));
        ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataRefresh(metaDataSubscribers));
    }

    /**
     * Executor.
     *
     * @param data the data
     * @return the boolean
     */
    public boolean executor(final JsonObject data) {
        final boolean[] success = {false};
        ENUM_MAP.values().parallelStream().forEach(dataRefresh -> success[0] = dataRefresh.refresh(data));
        return success[0];
    }
}

继续跟踪方法调用栈,发现是HttpSyncDataService类的updateCacheWithJson方法调用了DataRefreshFactory的executor方法,关键代码如下:

public class HttpSyncDataService implements SyncDataService, AutoCloseable {
    private boolean updateCacheWithJson(final String json) {
        JsonObject jsonObject = GSON.fromJson(json, JsonObject.class);
        JsonObject data = jsonObject.getAsJsonObject("data");
        // if the config cache will be updated?
        return factory.executor(data);
    }
}

继续跟踪方法栈,发现它自身的doFetchGroupConfig方法调用了updateCacheWithJson方法,如下:

private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
...
 // update local cache
        boolean updated = this.updateCacheWithJson(json);
        if (updated) {
            log.info("get latest configs: [{}]", json);
            return;
        }
...
}

继续跟踪,发现还是自身的fetchGroupConfig方法对doFetchGroupConfig进行了调用,如下;

 private void fetchGroupConfig(final ConfigGroupEnum... groups) throws SoulException {
        for (int index = 0; index < this.serverList.size(); index++) {
            String server = serverList.get(index);
            try {
                this.doFetchGroupConfig(server, groups);
                break;
            } catch (SoulException e) {
                // no available server, throw exception.
                if (index >= serverList.size() - 1) {
                    throw e;
                }
                log.warn("fetch config fail, try another one: {}", serverList.get(index + 1));
            }
        }
    }

继续跟,发现是自身的start方法调用了fetchGroupConfig方法,如下:

private void start() {
        // It could be initialized multiple times, so you need to control that.
        if (RUNNING.compareAndSet(false, true)) {
            // fetch all group configs.
            this.fetchGroupConfig(ConfigGroupEnum.values());
            int threadSize = serverList.size();
            this.executor = new ThreadPoolExecutor(threadSize, threadSize, 60L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(),
                    SoulThreadFactory.create("http-long-polling", true));
            // start long polling, each server creates a thread to listen for changes.
            this.serverList.forEach(server -> this.executor.execute(new HttpLongPollingTask(server)));
        } else {
            log.info("soul http long polling was started, executor=[{}]", executor);
        }
    }

接着往下跟,发现自身的构造函数被执行时就会执行start方法,如下:

 public HttpSyncDataService(final HttpConfig httpConfig, final PluginDataSubscriber pluginDataSubscriber,
                               final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
        this.factory = new DataRefreshFactory(pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
        this.httpConfig = httpConfig;
        this.serverList = Lists.newArrayList(Splitter.on(",").split(httpConfig.getUrl()));
        this.httpClient = createRestTemplate();
        this.start();
    }

最后发现是HttpSyncDataConfiguration这个类在自动装载时,会调用HttpSyncDataService的构造函数创建HttpSyncDataService实例对象,如下;

@Configuration
@ConditionalOnClass(HttpSyncDataService.class)
@ConditionalOnProperty(prefix = "soul.sync.http", name = "url")
@Slf4j
public class HttpSyncDataConfiguration {
@Bean
    public SyncDataService httpSyncDataService(final ObjectProvider<HttpConfig> httpConfig, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
                                           final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
        log.info("you use http long pull sync soul data");
        return new HttpSyncDataService(Objects.requireNonNull(httpConfig.getIfAvailable()), Objects.requireNonNull(pluginSubscriber.getIfAvailable()),
                metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
    }
}

总结

经过上述分析,http数据同步的大体流程如下图所示: 流程图
下一篇再根据这个大体流程深究里面的细节。