数据同步之nacos
安装nacos
先从官网下载安装包 https://github.com/alibaba/nacos/releases,我是windows环境,选的是这个
下载完成之后解压压缩包,修改 nacos\conf\application.properties 的配置,改成自己本地的数据库配置,数据库是用来持久化数据的。
然后双击 bin 目录下的 startup.cmd 启动服务。
发现启动失败,启动日志如下。
查资料后发现,是启动模式不对,修改 startup.cmd 里的内容,
把 MODE 的值改为 standalone ,再次双击 startup.cmd ,发现启动成功。
然后访问 http://localhost:8848/nacos ,添加一个名称为 soul 的命名空间
启动 admin
1.确保 pom 文件里引入了 acos
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.0</version>
</dependency>
2.修改同步配置
soul:
sync:
nacos:
url: localhost:8848
namespace: 1d879d75-f257-46dc-a16f-78dc710cbd5a # 这里配置的是在上面新建的命名空间的id
3.启动服务
可以看到 nacos 上面有数据了
启动bootstrap
1.pom 文件引入以下依赖
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
<version>${project.version}</version>
</dependency>
2.修改同步配置
soul:
sync:
nacos:
url: localhost:8848
namespace: 1d879d75-f257-46dc-a16f-78dc710cbd5a # 这里配置的是在上面新建的命名空间的id
3.启动服务
看到下面的启动日志,说明我们配置的 nacos 同步数据生效了。
you use nacos sync soul data.......
admin初始化数据
admin 启动之后,会先判断 nacos 里面有没有数据,如果没有数据就初始化
//NacosDataInit.java
public void run(final String... args) {
...
if (dataIdNotExist(pluginDataId) && dataIdNotExist(authDataId) && dataIdNotExist(metaDataId)) {
syncDataService.syncAll(DataEventTypeEnum.REFRESH);
}
}
syncAll 会发布 DataChangedEvent ,然后 DataChangedEventDispatcher 会监听到这个时间,执行 onPluginChanged() , 里面会更新本地缓存,然后把数据发布到 nacos 上。
public void onPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) {
updatePluginMap(getConfig(NacosPathConstants.PLUGIN_DATA_ID));
...
changed.forEach(plugin -> PLUGIN_MAP.put(plugin.getName(), plugin));
publishConfig(NacosPathConstants.PLUGIN_DATA_ID, PLUGIN_MAP);
}
这边初始化数据的流程和其他几种数据同步的方式的初始化是差不多的,都是通过 DataChangedEvent 事件发布监听机制来处理数据。
bootstrap初始化数据
然后我们找到上面 bootstrap 启动日志打印的地方
// NacosSyncDataConfiguration.java
@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));
}
接着往下找到 NacosSyncDataService.java
这个类初始化的时候会调用 start()
里面会去从 nacos 拉取数据
// NacosSyncDataService.java
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);
}
上面可以看用的是 java8 推出来的 Lambda 表达式。
watcherData(PLUGIN_DATA_ID, this::updatePluginMap);
相当于
watcherData(PLUGIN_DATA_ID, new OnChange() {
@Override
public void change(final String changeData) {
updatePluginMap(changeData);
}
});
我们先看下 watcherData 里面的代码
// NacosCacheHandler.java
protected void watcherData(final String dataId, final OnChange oc) {
// 注册一个nacos的监听器
Listener listener = new Listener() {
@Override
public void receiveConfigInfo(final String configInfo) {
oc.change(configInfo);
}
...
};
//这里会调用具体的实现类 例如 updatePluginMap() 去更新本地缓存
oc.change(getConfigAndSignListener(dataId, listener));
LISTENERS.computeIfAbsent(dataId, key -> new ArrayList<>()).add(listener);
}
// 从nacos获取配置并绑定监听器
private String getConfigAndSignListener(final String dataId, final Listener listener) {
String config = configService.getConfigAndSignListener(dataId, GROUP, 6000, listener);
...
return config;
}
我们以 updatePluginMap() 为例,看下它是怎么更新数据的。
// 把从nacos获取的json字符串转换为List<PluginData>
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);
}));
我们接着往下看里面调用的 unSubscribe 和 onSubscribe
这两个方法被 CommonPluginDataSubscriber 重写
// 删除缓存
public void unSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.DELETE);
}
// 更新缓存
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
他们调用的都是 subscribeDataHandler() ,然后根据type去区分到底是删除还是更新数据。
以上就是 bootstrap 启动之后初始化数据的流程。
我们再看下 admin 更改数据之后,bootstrap 是怎么更新数据的。
admin与bootstrap同步数据
从 admin 后台点击修改一个插件的开启状态,然后开始debug调用流程。
admin 这边是会先修改数据库,然后发布数据更新事件,后面和初始化数据的流程一样,把数据发布到nacos。
botostrap 启动初始化数据之后,会注册一个监听器,admin 更新完数据,这边会监听到,然后会走到receiveConfigInfo() 里,里面会调用 change() ,后面的流程和初始化的时候也是一样的。
函数调用流程图如下(以插件数据为例):
总结
nacos 数据同步的流程,是 admin 启动先初始化数据到 nacos ,然后 bootstrap 启动后从 nacos 拉取数据 ,并初测监听器。
后面 admin 更新数据,bootstrap 就是通过监听 ncaos 的数据更新来全量更新数据。