什么是nacos?
nacos是一款发现、配置和管理微服务的开源框架,详细介绍可以参考Nacos 文档。
项目启动
- 网关配置(记得重启)
- 首先在 pom.xml 文件中 引入以下依赖:
<!--soul data sync start use nacos--> <dependency> <groupId>org.dromara</groupId> <artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId> <version>${last.version}</version> </dependency>
- 在 springboot的 yml 文件中进行如下配置:
soul : sync: nacos: url: localhost:8848 namespace: 1c10d748-af86-43b9-8265-75f487d20c6c acm: enabled: false endpoint: acm.aliyun.com namespace: accessKey: secretKey: #url: 配置成你的nacos地址,集群环境请使用(,)分隔。 # 其他参数配置,请参考naocs官网。
- 首先在 pom.xml 文件中 引入以下依赖:
- soul-admin 配置, 或在 soul-admin 启动参数中使用 – 的方式一个一个传值。
soul : sync: nacos: url: localhost:8848 namespace: 1c10d748-af86-43b9-8265-75f487d20c6c acm: enabled: false endpoint: acm.aliyun.com namespace: accessKey: secretKey:
- 配置完成后就可以启动我们的项目了。
代码跟踪
-
程序从入口
PluginController.syncPluginData
方法开始,这是我们插件页面点击刷新的时候调进来的方法。然后就是刷新数据,刷新成功则返回成功,刷新失败则返回失败。/** * Sync plugin data. * * @param id the id * @return the mono */ @PutMapping("/syncPluginData/{id}") public SoulAdminResult syncPluginData(@PathVariable("id") final String id) { boolean success = syncDataService.syncPluginData(id); if (success) { return SoulAdminResult.success(SoulResultMessage.SYNC_SUCCESS); } else { return SoulAdminResult.error(SoulResultMessage.SYNC_FAIL); } }
-
上面调用
SyncDataServiceImpl.syncPluginData
,找到插件,使用ApplicationEventPublisher.publishEvent
将事件发送出去。@Override public boolean syncPluginData(final String pluginId) { PluginVO pluginVO = pluginService.findById(pluginId); eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, DataEventTypeEnum.UPDATE, Collections.singletonList(PluginTransfer.INSTANCE.mapDataTOVO(pluginVO)))); List<SelectorData> selectorDataList = selectorService.findByPluginId(pluginId); if (CollectionUtils.isNotEmpty(selectorDataList)) { eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.REFRESH, selectorDataList)); List<RuleData> allRuleDataList = new ArrayList<>(); for (SelectorData selectData : selectorDataList) { List<RuleData> ruleDataList = ruleService.findBySelectorId(selectData.getId()); allRuleDataList.addAll(ruleDataList); } eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, DataEventTypeEnum.REFRESH, allRuleDataList)); } return true; }
-
在
DataChangedEventDispatcher.onApplicationEvent
进行监听,实现插件分选,对不同的数据进行不同的change处理。@Override @SuppressWarnings("unchecked") public void onApplicationEvent(final DataChangedEvent event) { for (DataChangedListener listener : listeners) { switch (event.getGroupKey()) { case APP_AUTH: listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType()); break; case PLUGIN: listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType()); break; case RULE: listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType()); break; case SELECTOR: listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType()); break; case META_DATA: listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType()); break; default: throw new IllegalStateException("Unexpected value: " + event.getGroupKey()); } } }
-
对于PLUGIN的change事件来说,因为启用的是nacos,所以进入到
NacosDataChangedListener.onPluginChanged
@Override public void onPluginChanged(final List<PluginData> changed, final DataEventTypeEnum eventType) { updatePluginMap(getConfig(NacosPathConstants.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(NacosPathConstants.PLUGIN_DATA_ID, PLUGIN_MAP); }
-
然后分别调用
NacosDataChangedListener
的updatePluginMap
和publishConfig
- updatePluginMap是更新自己JVM中缓存的数据
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); }
- publishConfig是通知nacos更新数据。
@SneakyThrows private void publishConfig(final String dataId, final Object data) { configService.publishConfig(dataId, NacosPathConstants.GROUP, GsonUtils.getInstance().toJson(data)); }
- updatePluginMap是更新自己JVM中缓存的数据
-
接下来就是通过nacos的机制通知我们的网关
-
我们网关在启动的时候会启动多个watch程序进行监听,这里当有数据变动的时候就会执行相应的方法。
/** * 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); }
-
还是以PLUGIN的数据为例,进来之后就是
unSubscribe
和onSubscribe
的操作。这两个操作就是更新网关自己的map,先把map里的这个插件数据删除掉,然后再put进去。这个在前面刚接触数据同步的时候已经说明过,这里就不再赘述了。protected void updatePluginMap(final String configInfo) { try { // Fix bug #656(https://github.com/dromara/soul/issues/656) 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); } }
小结
本篇文章主要针对Soul网关从配置项目,到整个数据流的代码跟踪做了说明。因为自己对Nacos的一些东西也不是很懂,所以也是跳过了Nacos相关的部分,感兴趣的同学还是推荐去官网学习。