天天看点

Soul 网关 Nacos 数据同步源码解析

学习目标:

学习Soul 网关 Nacos 数据同步源码解析

学习内容:

  1. 环境配置
  2. Soul 网关 Nacos 数据同步基本概念
  3. 源码分析

学习时间:2020年1月28号 早7点

学习产出:

环境配置:

  1. 引入依赖

    在 soul-bootstrap 项目的 pom.xml 文件中引入了 soul-spring-boot-starter-sync-data-nacos 这个 starter 。

<dependency>
          <groupId>org.dromara</groupId>
           <artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
           <version>${last.version}</version>
     </dependency>
           
  1. 环境配置

在 soul-bootstrap 项目的 application-local.yml 文件中,配置

soul :
	 sync:
	    nacos:
	         url: localhost:8848
	         namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
	         acm:
	           enabled: false
	           endpoint: acm.aliyun.com
	           namespace:
	           accessKey:
	           secretKey:
	           #url: 配置成你的nacos地址,集群环境请使用(,)分隔。
		
           

在 soul-admin 项目中的 application.yml 文件中配置 nacos 同步的相关配置如下:

soul:
  sync:
    nacos:
      url: localhost:8848
      namespace: 7aba82c9-426b-4d72-89c9-060d9af63e14
       # 此处为前文中提到的命名空间ID或命名空间名称 soul
           

Nacos 的关键特性

服务发现和服务健康监测

动态配置服务

动态 DNS 服务

服务及其元数据管理

Nacos 中的几个概念

命名空间(Namespace): 不同环境的配置隔离

配置分组(Group): 不同的服务可以归类到同一分组。一般将一个项目的配置分到一组

配置集(Data ID) : 一个配置文件通常就是一个配置集

  1. nacos同步策略-源码追踪

    从 soul-bootstrap 开始追踪

    NacosSyncDataConfiguration 这个类加载了 soul.sync.nacos 这段配置。该类创建了 nacosSyncDataService 。

@Bean
public SyncDataService nacosSyncDataService(final ObjectProvider<ConfigService> configService, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
                                            final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
    log.info("you use nacos sync soul data.......");
    return new NacosSyncDataService(configService.getIfAvailable(), pluginSubscriber.getIfAvailable(),
                                metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
           
public NacosSyncDataService(final ConfigService configService, final PluginDataSubscriber pluginDataSubscriber,
                            final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
    super(configService, pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
    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 方法,此时我们会进入一个重要的类 NacosCacheHandler 。

// 即便对 lambda 不熟悉,我们也可以大致推测,这里的功能应该是通过调用updateXxxMap方法,对元数据、插件、选择器等进行监听
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;
        }
    };
    oc.change(getConfigAndSignListener(dataId, listener));
    LISTENERS.computeIfAbsent(dataId, key -> new ArrayList<>()).add(listener);
}

protected interface OnChange {
    void change(String changeData);
}
           

updatePluginMap 方法:

protected void updatePluginMap(final String configInfo) {
    try {
        // Fix bug #656(https://github.com/dromara/soul/issues/656) 此处解决了一个 nacos data sync error 的 bug
        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);
    }
}
           

追踪一下 unSubscribe(pluginData) 和 onSubscribe(pluginData) ,进入 CommonPluginDataSubscriber 中:

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

@Override
public void unSubscribe(final PluginData pluginData) {
    subscribeDataHandler(pluginData, DataEventTypeEnum.DELETE);
}

// 根据传入的数据类型ID,和数据处理类型,调用相应的处理函数,看具体是执行update更新还是delete删除
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));
            }
        }
    });
}
           

admin追踪:

是 DataSyncConfiguration 这个类加载了 soul.sync.nacos 这段配置。该类创建了 NacosDataChangedListener 。

/**
 * The type Nacos listener.
 */
@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);
    }

    /**
         * Nacos data init zookeeper data init.
         *
         * @param configService the config service
         * @param syncDataService the sync data service
         * @return the nacos data init
         */
    @Bean
    @ConditionalOnMissingBean(NacosDataInit.class)
    public NacosDataInit nacosDataInit(final ConfigService configService, final SyncDataService syncDataService) {
        return new NacosDataInit(configService, syncDataService);
    }
}
           
@Override
public void run(final String... args) {
    String pluginDataId = NacosPathConstants.PLUGIN_DATA_ID;
    String authDataId = NacosPathConstants.AUTH_DATA_ID;
    String metaDataId = NacosPathConstants.META_DATA_ID;
    if (dataIdNotExist(pluginDataId) && dataIdNotExist(authDataId) && dataIdNotExist(metaDataId)) {
        syncDataService.syncAll(DataEventTypeEnum.REFRESH);
    }
}
           
// type 是 REFRESH
@Override
public boolean syncAll(final DataEventTypeEnum type) {
    appAuthService.syncData();
    List<PluginData> pluginDataList = pluginService.listAll();
    eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
    List<SelectorData> selectorDataList = selectorService.listAll();
    eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
    List<RuleData> ruleDataList = ruleService.listAll();
    eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
    metaDataService.syncData();
    return true;
}
           
public static void send(final String message, final DataEventTypeEnum type) {
    if (StringUtils.isNotBlank(message)) {
        if (DataEventTypeEnum.MYSELF == type) {
            Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
            if (session != null) {
                sendMessageBySession(session, message);
            }
        } else {
            SESSION_SET.forEach(session -> sendMessageBySession(session, message));
        }
    }
}