Soul 网关源码学习(10) - Soul 网关 Nacos 数据同步源码解析

从前面几天的源码学习,我们知道了在 soul 网关中,数据同步的机制是在 soul admin 中的事件机制中将对应的数据变更的事件调用到 org.dromara.soul.admin.listener.DataChangedListener,在数据处理事件变更的代码实现中使用了模板方法设计模式,响应数据变更的通用流程定义在了
org.dromara.soul.admin.listener.AbstractDataChangedListener 类当中,不同的数据同步方式的差异流程处理在具体的相应的数据同步方式的实现类当中。

soul 网关利用 Nacos 实现数据同步的原理

soul 网关利用 Nacos 作为配置中心,实现 soul admin 和开启了使用 Nacos 作为数据同步的 soul-bootstrap 之间完成数据同步。具体的交互的结构图如下:

img.png

soul admin 侧,当配置内容发生变更的时候将配置内容写到对应的配置当中;soul data sync nacos 中通过 org.dromara.soul.sync.data.nacos.NacosSyncDataService 完成配置内容的初始化和变更的更新。在 soul admin 中,启用 nacos 数据同步的配置在:

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

@Bean
@ConditionalOnMissingBean(NacosDataChangedListener.class)
public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
return new NacosDataChangedListener(configService);
}
}

@EnableConfigurationProperties(NacosProperties.class)
public class NacosConfiguration {

@Bean
@ConditionalOnMissingBean(ConfigService.class)
public ConfigService nacosConfigService(final NacosProperties nacosProp) throws Exception {
Properties properties = new Properties();
if (nacosProp.getAcm() != null && nacosProp.getAcm().isEnabled()) {
// Use aliyun ACM service
properties.put(PropertyKeyConst.ENDPOINT, nacosProp.getAcm().getEndpoint());
properties.put(PropertyKeyConst.NAMESPACE, nacosProp.getAcm().getNamespace());
// Use subaccount ACM administrative authority
properties.put(PropertyKeyConst.ACCESS_KEY, nacosProp.getAcm().getAccessKey());
properties.put(PropertyKeyConst.SECRET_KEY, nacosProp.getAcm().getSecretKey());
} else {
properties.put(PropertyKeyConst.SERVER_ADDR, nacosProp.getUrl());
properties.put(PropertyKeyConst.NAMESPACE, nacosProp.getNamespace());
}
return NacosFactory.createConfigService(properties);
}
}

启用 Nacos 数据同步的配置和启用 zookeeper 数据同步的配置类似,都是检查容器启动的 property source 中是否包含了: soul.sync.nacos.url

soul admin 中数据变更事件的响应

soul admin 侧数据变更响应事件的实现在 org.dromara.soul.admin.listener.nacos.NacosDataChangedListener 中实现,

public class NacosDataChangedListener implements DataChangedListener {

private static final ConcurrentMap<String, PluginData> PLUGIN_MAP = Maps.newConcurrentMap();

private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();

private static final ConcurrentMap<String, List<RuleData>> RULE_MAP = Maps.newConcurrentMap();

private static final ConcurrentMap<String, AppAuthData> AUTH_MAP = Maps.newConcurrentMap();

private static final ConcurrentMap<String, MetaData> META_DATA = Maps.newConcurrentMap();

private static final Comparator<SelectorData> SELECTOR_DATA_COMPARATOR = Comparator.comparing(SelectorData::getSort);

private static final Comparator<RuleData> RULE_DATA_COMPARATOR = Comparator.comparing(RuleData::getSort);

private static final String GROUP = "DEFAULT_GROUP";

private static final String PLUGIN_DATA_ID = "soul.plugin.json";

private static final String SELECTOR_DATA_ID = "soul.selector.json";

private static final String RULE_DATA_ID = "soul.rule.json";

private static final String AUTH_DATA_ID = "soul.auth.json";

private static final String META_DATA_ID = "soul.meta.json";

private static final String EMPTY_CONFIG_DEFAULT_VALUE = "{}";

private final ConfigService configService;

public NacosDataChangedListener(final ConfigService configService) {
this.configService = configService;
}

...
}

可以看到 NacosDataChangedListener 的私有常量定义,这些定义的含义分别如下:

常量名 含义
GROUP 配置项所在的配置 Group
PLUGIN_DATA_ID 插件配置信息的 DataID
SELECTOR_DATA_ID 插件选择器配置信息的 DataID
RULE_DATA_ID 规则配置信息的 DataID
AUTH_DATA_ID 认证配置信息的 DataID
META_DATA_ID 元数据配置信息的 DataID
PLUGIN_MAP JVM 进程内插件数据的缓存,key 是 Plugin 数据的 ID,value 是 PluginData
SELECTOR_MAP JVM 进程内插件选择器数据的缓存,key 是 Selector 归属插件的名称, value 是 SelectorData
RULE_MAP JVM 进程内插件选择器规则数据的缓存,key 是 SelectorID, value 是 RuleData 集合
AUTH_MAP JVM 进程内认证信息数据的缓存,key 是 AppAuth 的 appKey, value 是 AppAuthData
META_DATA JVM 进程内元数据信息的缓存,key 是 MetaData 的 path,value 是 MetaData
SELECTOR_DATA_COMPARATOR 插件数据的排序比较器,用于对 SelectorData 集合做排序处理
RULE_DATA_COMPARATOR 规则数据的排序比较器,用于对 RuleData 集合做排序处理

Group 是 Nacos 中一组配置集,是组织配置的维度之一,通过一个有意义的字符串对配置集进行分组,从而区分 Data ID 相同的配置集; DataID 是组织划分配置的维度之一, 通常用于组织划分系统的配置集, 一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。

ConfigServicenacos-client 中提供的和 nacos server 之间通讯的类,通过这个类,我们可以推送配置到 nacos server,注册数据变化监听器到 nacos 中的 DataID 中。NacosDataChangedListener 中同步数据变更事件的响应代码中的处理流程如下:

  1. 从 Nacos Server 中获取变更数据所属 DataID 的配置信息,更新到 JVM 进程内缓存中
  2. 根据不同的 eventType,对 JVM 进程内的缓存信息进行操作
  3. 同步操作的 JVM 进程内信息到远程的 Nacos Server 的 DataID 配置中

soul data sync nacos 模块数据初始化与变更监听

soul data sync nacos 模块作为被依赖的组件,在 soul bootstrap 中承担的功能性的职责有:

  1. 启动的时候主动从配置中心拉取配置信息
  2. 当 Nacos 配置中心对应的 DataID 中的配置发生变更的时候,感知到相应的变更

上述的职责的实现代码在 org.dromara.soul.sync.data.nacos.NacosSyncDataService 中实现的,它继承自 org.dromara.soul.sync.data.nacos.handler.NacosCacheHandler,实现了
org.dromara.soul.sync.data.api.SyncDataService 接口;NacosCacheHandler 中定义了将数据变更的信息传递给相应的 subscriber, 提供获取 DataID 配置并且注册变更监视器的方法定义数据变更监听函数式接口;
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 方法定义在了父类 NacosCacheHandler 中,

@SneakyThrows
private String getConfigAndSignListener(final String dataId, final Listener listener) {
String config = configService.getConfigAndSignListener(dataId, GROUP, 6000, listener);
if (config == null) {
config = "{}";
}
return config;
}

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.getOrDefault(dataId, new ArrayList<>()).add(listener);
}

protected interface OnChange {

void change(String changeData);
}

这样,在 NacosSyncDataService 初始化一个实例的时候,就完成了数据初始化与变更监听器的注册,获取配置信息和注册配置变更监听器到 DataID 最终会委托给 com.alibaba.nacos.client.config.NacosConfigService

总结

今天的学习了 soul 网关使用 nacos 配置中心同步配置数据功能处理流程的源码,在这个过程中看到了 http 长轮询在 Nacos 配置中心的 client 端的使用。学习了 soul 网关所有的数据同步方式的源码之后,让我对 soul 网关数据同步流程有了一个清晰的认识,这里面使用了很多的设计模式,也有性能、并发、锁方面设计的考量,希望这些知识能对今后的开发工作有所启发。

文章作者: David Liu
文章链接: https://davidliu.now.sh/2021/01/25/soul_data_sync_nacos/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 David Liu's Blog