1. 在【soul-admin】中开启nacos同步
soul:
sync:
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
acm:
enabled: false
endpoint: acm.aliyun.com
namespace:
accessKey:
secretKey:
2. 在【soul-bootstrap】中添加nacos数据同步依赖
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
<version>${project.version}</version>
</dependency>
3.在【soul-bootstrap】中开启nacos同步
soul:
sync:
nacos:
url: localhost:8848
namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
acm:
enabled: false
endpoint: acm.aliyun.com
namespace:
accessKey:
secretKey:
4. 这样启动 【soul-admin】和 【soul-bootstrap】,他们之间的数据就完成同步了。
带着问题读源码之【soul-admin】如何完成数据推送
上述第一步打开如下配置:
@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);
}
}
@EnableConfigurationProperties(NacosProperties.class)
public class NacosConfiguration {
/**
* register configService in spring ioc.
*
* @param nacosProp the nacos configuration
* @return ConfigService {@linkplain ConfigService}
* @throws Exception the exception
*/
@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);
}
}
注册了数据变动监听器
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;
}
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);
}
private void updatePluginMap(final String configInfo) {
JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
Set<String> set = new HashSet<>(PLUGIN_MAP.keySet());
for (Entry<String, JsonElement> e : jo.entrySet()) {
set.remove(e.getKey());
PLUGIN_MAP.put(e.getKey(), GsonUtils.getInstance().fromJson(e.getValue(), PluginData.class));
}
PLUGIN_MAP.keySet().removeAll(set);
}
private void updateSelectorMap(final String configInfo) {
JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
Set<String> set = new HashSet<>(SELECTOR_MAP.keySet());
for (Entry<String, JsonElement> e : jo.entrySet()) {
set.remove(e.getKey());
List<SelectorData> ls = new ArrayList<>();
e.getValue().getAsJsonArray().forEach(je -> ls.add(GsonUtils.getInstance().fromJson(je, SelectorData.class)));
SELECTOR_MAP.put(e.getKey(), ls);
}
SELECTOR_MAP.keySet().removeAll(set);
}
private void updateMetaDataMap(final String configInfo) {
JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
Set<String> set = new HashSet<>(META_DATA.keySet());
for (Entry<String, JsonElement> e : jo.entrySet()) {
set.remove(e.getKey());
META_DATA.put(e.getKey(), GsonUtils.getInstance().fromJson(e.getValue(), MetaData.class));
}
META_DATA.keySet().removeAll(set);
}
private void updateRuleMap(final String configInfo) {
JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
Set<String> set = new HashSet<>(RULE_MAP.keySet());
for (Entry<String, JsonElement> e : jo.entrySet()) {
set.remove(e.getKey());
List<RuleData> ls = new ArrayList<>();
e.getValue().getAsJsonArray().forEach(je -> ls.add(GsonUtils.getInstance().fromJson(je, RuleData.class)));
RULE_MAP.put(e.getKey(), ls);
}
RULE_MAP.keySet().removeAll(set);
}
@SneakyThrows
private String getConfig(final String dataId) {
String config = configService.getConfig(dataId, GROUP, 6000);
return StringUtils.hasLength(config) ? config : EMPTY_CONFIG_DEFAULT_VALUE;
}
@SneakyThrows
private void publishConfig(final String dataId, final Object data) {
configService.publishConfig(dataId, GROUP, GsonUtils.getInstance().toJson(data));
}
@Override
@SneakyThrows
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);
}
@Override
public void onPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) {
updatePluginMap(getConfig(PLUGIN_DATA_ID));
switch (eventType) {
case DELETE:
changed.forEach(plugin -> PLUGIN_MAP.remove(plugin.getName()));
break;
case REFRESH:
case MYSELF:
Set<String> set = new HashSet<>(PLUGIN_MAP.keySet());
changed.forEach(plugin -> {
set.remove(plugin.getName());
PLUGIN_MAP.put(plugin.getName(), plugin);
});
PLUGIN_MAP.keySet().removeAll(set);
break;
default:
changed.forEach(plugin -> PLUGIN_MAP.put(plugin.getName(), plugin));
break;
}
publishConfig(PLUGIN_DATA_ID, PLUGIN_MAP);
}
@Override
public void onSelectorChanged(final List<SelectorData> changed, final DataEventTypeEnum eventType) {
updateSelectorMap(getConfig(SELECTOR_DATA_ID));
switch (eventType) {
case DELETE:
changed.forEach(selector -> {
List<SelectorData> ls = SELECTOR_MAP
.getOrDefault(selector.getPluginName(), new ArrayList<>())
.stream()
.filter(s -> !s.getId().equals(selector.getId()))
.sorted(SELECTOR_DATA_COMPARATOR)
.collect(Collectors.toList());
SELECTOR_MAP.put(selector.getPluginName(), ls);
});
break;
case REFRESH:
case MYSELF:
// 将原来的数据删除并加入新的数据进去,全量更新
Set<String> set = new HashSet<>(SELECTOR_MAP.keySet());
changed.forEach(selector -> {
set.remove(selector.getPluginName());
List<SelectorData> ls = SELECTOR_MAP
.getOrDefault(selector.getPluginName(), new ArrayList<>())
.stream()
.sorted(SELECTOR_DATA_COMPARATOR)
.collect(Collectors.toList());
SELECTOR_MAP.put(selector.getPluginName(), ls);
});
SELECTOR_MAP.keySet().removeAll(set);
break;
default:
// 增量更新
changed.forEach(selector -> {
List<SelectorData> ls = SELECTOR_MAP
.getOrDefault(selector.getPluginName(), new ArrayList<>())
.stream()
.filter(s -> !s.getId().equals(selector.getId()))
.sorted(SELECTOR_DATA_COMPARATOR)
.collect(Collectors.toList());
ls.add(selector);
SELECTOR_MAP.put(selector.getPluginName(), ls);
});
break;
}
publishConfig(SELECTOR_DATA_ID, SELECTOR_MAP);
}
@Override
public void onMetaDataChanged(final List<MetaData> changed, final DataEventTypeEnum eventType) {
updateMetaDataMap(getConfig(META_DATA_ID));
switch (eventType) {
case DELETE:
changed.forEach(meta -> META_DATA.remove(meta.getPath()));
break;
case REFRESH:
case MYSELF:
Set<String> set = new HashSet<>(META_DATA.keySet());
changed.forEach(meta -> {
set.remove(meta.getPath());
META_DATA.put(meta.getPath(), meta);
});
META_DATA.keySet().removeAll(set);
break;
default:
changed.forEach(meta -> {
META_DATA
.values()
.stream()
.filter(md -> Objects.equals(md.getId(), meta.getId()))
.forEach(md -> META_DATA.remove(md.getPath()));
META_DATA.put(meta.getPath(), meta);
});
break;
}
publishConfig(META_DATA_ID, META_DATA);
}
@Override
public void onRuleChanged(final List<RuleData> changed, final DataEventTypeEnum eventType) {
updateRuleMap(getConfig(RULE_DATA_ID));
switch (eventType) {
case DELETE:
changed.forEach(rule -> {
List<RuleData> ls = RULE_MAP
.getOrDefault(rule.getSelectorId(), new ArrayList<>())
.stream()
.filter(s -> !s.getId().equals(rule.getId()))
.sorted(RULE_DATA_COMPARATOR)
.collect(Collectors.toList());
RULE_MAP.put(rule.getSelectorId(), ls);
});
break;
case REFRESH:
case MYSELF:
Set<String> set = new HashSet<>(RULE_MAP.keySet());
changed.forEach(rule -> {
set.remove(rule.getSelectorId());
List<RuleData> ls = RULE_MAP
.getOrDefault(rule.getSelectorId(), new ArrayList<>())
.stream()
.sorted(RULE_DATA_COMPARATOR)
.collect(Collectors.toList());
RULE_MAP.put(rule.getSelectorId(), ls);
});
RULE_MAP.keySet().removeAll(set);
break;
default:
changed.forEach(rule -> {
List<RuleData> ls = RULE_MAP
.getOrDefault(rule.getSelectorId(), new ArrayList<>())
.stream()
.filter(s -> !s.getId().equals(rule.getSelectorId()))
.sorted(RULE_DATA_COMPARATOR)
.collect(Collectors.toList());
ls.add(rule);
RULE_MAP.put(rule.getSelectorId(), ls);
});
break;
}
publishConfig(RULE_DATA_ID, RULE_MAP);
}
}
可见,nacos数据同步并没有直接【soul-bootstrap】通信,而是将数据更新到nacos后,就不管了,相当于异步的更新。