Soul网关源码学习【第九篇】-数据同步(nacos)

本文介绍了Soul网关与Nacos的集成,详细讲述了如何配置项目、启动网关,并通过代码跟踪理解数据同步的过程。当在soul-admin中配置并重启后,网关通过监听Nacos的变更事件,更新自身缓存并通知Nacos更新数据。文中重点解析了数据在网关中的变更处理,包括删除和插入操作,但Nacos的具体工作原理则建议读者参考官方文档深入学习。
摘要由CSDN通过智能技术生成

什么是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官网。
      
  • 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);
        }
    
  • 然后分别调用NacosDataChangedListenerupdatePluginMappublishConfig

    • 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));
          }
      
  • 接下来就是通过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的数据为例,进来之后就是unSubscribeonSubscribe的操作。这两个操作就是更新网关自己的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相关的部分,感兴趣的同学还是推荐去官网学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值