后台与网关数据同步 (Nacos篇)
配置 Nacos
研究 Soul 后台与网关同步前, 需要先做好本地环境准备, 重点是启动 Nacos 服务.
通用 docker 使用 Nacos 的网址: https://nacos.io/zh-cn/docs/quick-start-docker.html
-
首先将
nacos-docker
项目 clone 到本地> git clone https://github.com/nacos-group/nacos-docker.git
-
进入
nacos-docker
目录下, 执行docker-compose
> docker-compose -f example/standalone-derby.yaml up
-
登录 http://127.0.0.1:8848/nacos/ 账号密码皆为
nacos
配置后台
后台的配置主要是为了让 DataSyncConfiguration 配置类中的 Nacos 相关 Bean 起作用, 并且让其他同步方式的 Bean 不生效即可
soul:
sync:
websocket:
enabled: false
http:
enabled: false
# zookeeper:
# url: localhost:2181
# sessionTimeout: 5000
# connectionTimeout: 2000
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
acm:
enabled: false
endpoint: acm.aliyun.com
namespace:
accessKey:
secretKey:
配置网关
对于网关端的同步方式切换我们也早已轻车熟路了, 先找到网关端的 Nacos Spring boot 启动项目
看看它的关键配置类 NacosSyncDataConfiguration
@Configuration
@ConditionalOnClass(NacosSyncDataService.class)
@ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
@Slf4j
public class NacosSyncDataConfiguration {
// ...
@Bean
@ConfigurationProperties(prefix = "soul.sync.nacos")
public NacosConfig nacosConfig() {
return new NacosConfig();
}
}
大致知道了它所需要的配置信息, 去配置文件中完成它.
soul:
sync:
# websocket :
# urls: ws://localhost:9095/websocket
# zookeeper:
# url: localhost:2181
# sessionTimeout: 5000
# connectionTimeout: 2000
# http:
# url: http://localhost:9095
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
acm:
enabled: false
endpoint: acm.aliyun.com
namespace:
accessKey:
secretKey:
需要注意的一点是, 确认 pom 中有引入 Nacos Spring boot 启动项目
<!--soul data sync start use nacos-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
<version>${project.version}</version>
</dependency>
Nacos 后台无数据?
打开 Nacos 并且开启网关后, 发现 Nacos 中并没有任何数据, 尝试在网关后台点击同步刷新也依旧没有信息出现.
先检查下启动时的代码:
@Configuration
public class DataSyncConfiguration {
@Configuration
@ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
@Import(NacosConfiguration.class)
static class NacosListener {
@Bean
@ConditionalOnMissingBean(NacosDataChangedListener.class)
public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
// Nacos 监听, 当触发数据变动时会进去此类方法
return new NacosDataChangedListener(configService);
}
@Bean
@ConditionalOnMissingBean(NacosDataInit.class)
public NacosDataInit nacosDataInit(final ConfigService configService, final SyncDataService syncDataService) {
// Nacos 数据初始化, 在启动时会尝试推送数据到 Nacos
return new NacosDataInit(configService, syncDataService);
}
}
}
在启动网关后, Nacos 依然没有任何信息, 我们看看 NacosDataInit
在启动时做了什么事情.
public class NacosDataInit implements CommandLineRunner {
@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;
// 检查数据是否存在, 都不存在则推送数据到 Nacos
if (dataIdNotExist(pluginDataId) && dataIdNotExist(authDataId) && dataIdNotExist(metaDataId)) {
syncDataService.syncAll(DataEventTypeEnum.REFRESH);
}
}
@SneakyThrows
private boolean dataIdNotExist(final String pluginDataId) {
String group = NacosPathConstants.GROUP;
long timeout = NacosPathConstants.DEFAULT_TIME_OUT;
// 从 Nacos 中获得相关数据
final String config = configService.getConfig(pluginDataId, group, timeout);
return config == null;
}
}
这里有个关键接口 CommandLineRunner
, 这是 SpringBoot 的机制, 即启动时执行并仅会执行一次 run()
.
这里我们看到网关后台启动后尝试获取 Nacos 的相关数据, 只要存在任何类型的数据信息, 则不推送信息到 Nacos, 反之则全量推送.
那么, 为什么在同步到 Nacos 后, 却没有看到任何数据信息呢?
首先我们确信后台和 Nacos 通信是畅通的, 且也确实 debug 到推送的信息. 最关键的是, 二次启动时也在 configService.getConfig(pluginDataId, group, timeout);
处获取到了相关数据.
那么我猜测, Nacos 中是存在值的, 仅仅是隐藏了, 注意到 Nacos 是区分 namespace 的, 我们回到一开始后台的 yml 配置:
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
其中的这个 namespace 是对应 Nacos 命名空间的 ID, 我们看到 Nacos 后台中根本没有这个命名空间, 所以也看不到我们同步过去的数据.
那么怎么办呢? 在 Nacos 后台新建对应我们配置的 namespace 即可
接下来就可以在 Nacos 中看到所有 Soul 后台同步过去的数据啦!
PS: 即使不新增命名空间, 也是能正常使用的, 因为通过 API 拿去 Nacos 的信息完全没问题, 要想显式的看还是需要手动新增命名空间的
启动网关验证同步
通过配置类 NacosSyncDataConfiguration, 找到启动网关后监听 Nacos 的 NacosSyncDataService
public class NacosSyncDataService extends NacosCacheHandler implements AutoCloseable, SyncDataService {
// 初始化
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);
}
protected void watcherData(final String dataId, final OnChange oc) {
Listener listener = new Listener() {
@Override
public void receiveConfigInfo(final String configInfo) {
// 当 Nacos 下数据发生变动时, 这里会被触发, 通知数据变化
oc.change(configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
};
oc.change(getConfigAndSignListener(dataId, listener));
}
}
一路追溯到 NacosCacheHandler 中
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);
}
}
最后, 我们在 watcherData()
的 receiveConfigInfo()
回调监听方法处断点, 并变更 Soul 后台的信息, 看看是否走到这里
配置成功~