soul网关源码学习09-Nacos数据同步

soul网关源码学习09-Nacos数据同步

目标:

  • 梳理Nacos同步数据的流程

一、数据流向分析

  • 启动admin,全量更新一次数据到Nacos,监听adminCURD操作。
  • 启动bootstrap,从Nacos拉取一次全量数据更新到本地缓存并启动监听。
  • adminCURD操作,触发监听,推送全量数据到Nacos
  • bootstrap触发监听,从Nacos拉取全量数据更新到本地缓存。

soul网关源码学习09-Nacos数据同步_第1张图片

二、数据同步流程

  • 启动admin,加载事件分发器DataChangedEventDispatcher
  • 实例化DataChangedListener的实现类NacosDataChangedListener,开始监听admin数据的变化。
  • 根据配置加载NacosDataInit,初始化数据到Nacos
  • 启动bootstrap,加载Nacos数据同步类NacosSyncDataService
  • Nacos客户端会全量从Nacos拉取一次数据更新到缓存中。
  • Nacos客户端开始监听Nacos,若有变化,则拉取全量的数据更新到缓存中。
  • adminbootstrap都启动之后,修改admin的数据,会触发事件监听,admin会把数据推送到Nacos,客户端监听到数据变化,拉取全量的数据更新到缓存中。
    soul网关源码学习09-Nacos数据同步_第2张图片

三、运行项目

修改配置

soul-bootstrapapplication.yml修改数据同步方式为Nacos,url是Nacos服务地址,namespace是命名空间,用于隔离不同的环境。

      nacos:
        url: localhost:8848
        namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
        acm:
          enabled: false
          endpoint: acm.aliyun.com
          namespace:
          accessKey:
          secretKey:

soul-bootstrappom.xml增加Nacos同步数据依赖。

        <dependency>
            <groupId>org.dromaragroupId>
            <artifactId>soul-spring-boot-starter-sync-data-nacosartifactId>
            <version>${project.version}version>
        dependency>

soul-adminapplication.yml修改数据同步方式为Nacos。

        nacos:
              url: localhost:8848
              namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
              acm:
                enabled: false
                endpoint: acm.aliyun.com
                namespace:
                accessKey:
                secretKey:

启动项目

修改完配置,本地先启动Nacos,运行命令:startup.cmd -m standalone
soul网关源码学习09-Nacos数据同步_第3张图片
打开Nacos管理界面:http://localhost:8848/nacos,账号密码:nacos/nacos,根据上面配置的namespace创建一个命名空间。
soul网关源码学习09-Nacos数据同步_第4张图片
启动soul-admin
soul网关源码学习09-Nacos数据同步_第5张图片
启动soul-bootstrap
soul网关源码学习09-Nacos数据同步_第6张图片
查看Nacos配置,可以看到admin已经把配置都推送到Nacos了。
soul网关源码学习09-Nacos数据同步_第7张图片

四、源码分析

soul-admin

还是从soul-admin的配置类DataSyncConfiguration入手,找到Nacos的内部类NacosListener,注意这个ConfigService,是整个配置中心的核心,执行获取数据、监听等操作。

    @Configuration
    @ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
    @Import(NacosConfiguration.class)
    static class NacosListener {
     

        /**
         * Data changed listener data changed listener.
         *
         * @param configService the config service
         * @return the data changed listener
         */
        @Bean
        @ConditionalOnMissingBean(NacosDataChangedListener.class)
        public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
     
            return new NacosDataChangedListener(configService);
        }
    }

进入NacosDataChangedListener,这个类实现了DataChangedListener接口,onchange方法监听数据的变化。这里看一下onAppAuthChanged方法,其他几个类似。

    public void onAppAuthChanged(final List<AppAuthData> changed, final DataEventTypeEnum eventType) {
     
        updateAuthMap(getConfig(AUTH_DATA_ID));
        switch (eventType) {
     
            case DELETE:
                changed.forEach(appAuth -> AUTH_MAP.remove(appAuth.getAppKey()));
                break;
            case REFRESH:
            case MYSELF:
                Set<String> set = new HashSet<>(AUTH_MAP.keySet());
                changed.forEach(appAuth -> {
     
                    set.remove(appAuth.getAppKey());
                    AUTH_MAP.put(appAuth.getAppKey(), appAuth);
                });
                AUTH_MAP.keySet().removeAll(set);
                break;
            default:
                changed.forEach(appAuth -> AUTH_MAP.put(appAuth.getAppKey(), appAuth));
                break;
        }
        publishConfig(AUTH_DATA_ID, AUTH_MAP);
    }

首先看一下getConfig方法,根据dataIdGROUP去Nacos配置中心拿到配置信息,接着看updateAuthMap方法,从Nacos拿到配置信息再结合参数changed的数据全量更新到缓存。然后调用publishConfig,把最新的配置全量推送到Nacos

    private void updateAuthMap(final String configInfo) {
     
        JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
        Set<String> set = new HashSet<>(AUTH_MAP.keySet());
        for (Entry<String, JsonElement> e : jo.entrySet()) {
     
            set.remove(e.getKey());
            AUTH_MAP.put(e.getKey(), GsonUtils.getInstance().fromJson(e.getValue(), AppAuthData.class));
        }
        AUTH_MAP.keySet().removeAll(set);
    }

然后再往前看一下在哪里会调用这个onAppAuthChanged方法,又是DataChangedEventDispatcher事件分发器。

    public void onApplicationEvent(final DataChangedEvent event) {
     
        for (DataChangedListener listener : listeners) {
     
            switch (event.getGroupKey()) {
     
                case APP_AUTH:
                    listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
       //省略

走到这里,整个soul-admin就启动完成了,跟之前的差不多,先初始化所有的数据到Nacos,然后启动了一个监听器,soul-admin修改数据时会发送事件,监听器接收到事件之后更新缓存并更新到Nacos

soul-bootstrap

可以根据老套路,在日志中(you use nacos sync soul data)找到配置类NacosSyncDataConfiguration,直接进入NacosSyncDataService

    public NacosSyncDataService(final ConfigService configService, final PluginDataSubscriber pluginDataSubscriber,
                                final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
     
        super(configService, pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
        start();
    }

start方法,有五个方法,先看第一个就行,其他的类似。

    public void start() {
     
        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);
    }

watcherData方法,启动一个监听listenerreceiveConfigInfo方法从监听中回调获取配置信息configInfo

    protected void watcherData(final String dataId, final OnChange oc) {
     
        Listener listener = new Listener() {
     
            @Override
            public void receiveConfigInfo(final String configInfo) {
     
                oc.change(configInfo);
            }

            @Override
            public Executor getExecutor() {
     
                return null;
            }
        };
        //全量从Nacos获取一次全量配置
        oc.change(getConfigAndSignListener(dataId, listener));
    }

接着看参数this::updatePluginMap,这种双冒号的写法其实就是lambda写法转化而来的(前提是代码片段就是函数体本身,函数的入参是箭头(->)左边的那个参数),其实就是下面的这种lambda写法。

watcherData(PLUGIN_DATA_ID, changeData -> updatePluginMap(changeData));

把上面的写法再转化一下,其实应该就是把OnChange接口的实现作为参数传进去,然后这个实现里面调用了updatePluginMap方法。

        watcherData(PLUGIN_DATA_ID, new OnChange() {
     
            @Override
            public void change(final String changeData) {
     
                updatePluginMap(changeData);
            }
        });

再往前看其实就是把updatePluginMap方法注册到listener,作为回调方法receiveConfigInfo里面执行的一个方法,就是监听到Nacos有数据变化的时候执行updatePluginMap方法去更新缓存。
接着看一下updatePluginMap方法,遍历所有插件的配置信息,从缓存里面先删除再新增,也就是全量更新。

    protected void updatePluginMap(final String configInfo) {
     
        try {
     
            List<PluginData> pluginDataList = new ArrayList<>(GsonUtils.getInstance().toObjectMap(configInfo, PluginData.class).values());
            pluginDataList.forEach(pluginData -> Optional.ofNullable(pluginDataSubscriber).ifPresent(subscriber -> {
     
                subscriber.unSubscribe(pluginData);
                subscriber.onSubscribe(pluginData);
            }));
        } catch (JsonParseException e) {
     
            log.error("sync plugin data have error:", e);
        }
    }

整个流程大概就是先全量更新一次数据,然后启动监听,如果有数据变化在全量更新缓存。
Nacos的配置中心数据同步可以看一下这篇:https://blog.csdn.net/m0_54065725/article/details/113075357

五、总结

走完整个流程的感受就是,Nacos捞数据的时候底层好像也是用了http长轮询,感觉直接用http长轮询就可以了,引入一个中间件好像会增加了不稳定性,而且都是全量更新数据,不像zookeeper可以增量更新。

你可能感兴趣的:(soul源码,中间件,网关)