目标:
admin
,全量更新一次数据到Nacos
,监听admin
的CURD
操作。bootstrap
,从Nacos
拉取一次全量数据更新到本地缓存并启动监听。admin
做CURD
操作,触发监听,推送全量数据到Nacos
。bootstrap
触发监听,从Nacos
拉取全量数据更新到本地缓存。admin
,加载事件分发器DataChangedEventDispatcher
。DataChangedListener
的实现类NacosDataChangedListener
,开始监听admin
数据的变化。NacosDataInit
,初始化数据到Nacos
。bootstrap
,加载Nacos
数据同步类NacosSyncDataService
。Nacos
客户端会全量从Nacos
拉取一次数据更新到缓存中。Nacos
客户端开始监听Nacos
,若有变化,则拉取全量的数据更新到缓存中。admin
和bootstrap
都启动之后,修改admin
的数据,会触发事件监听,admin
会把数据推送到Nacos
,客户端监听到数据变化,拉取全量的数据更新到缓存中。soul-bootstrap
的application.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-bootstrap
的pom.xml
增加Nacos同步数据依赖。
<dependency>
<groupId>org.dromaragroupId>
<artifactId>soul-spring-boot-starter-sync-data-nacosartifactId>
<version>${project.version}version>
dependency>
soul-admin
的application.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
打开Nacos
管理界面:http://localhost:8848/nacos,账号密码:nacos/nacos,根据上面配置的namespace创建一个命名空间。
启动soul-admin
启动soul-bootstrap
查看Nacos
配置,可以看到admin
已经把配置都推送到Nacos
了。
还是从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
方法,根据dataId
和GROUP
去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
。
可以根据老套路,在日志中(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
方法,启动一个监听listener
。receiveConfigInfo
方法从监听中回调获取配置信息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可以增量更新。