• Soul 网关 Nacos 数据同步源码解析


    学习目标:

    学习Soul 网关 Nacos 数据同步源码解析

    学习内容:

    1. 环境配置
    2. Soul 网关 Nacos 数据同步基本概念
    3. 源码分析

    学习时间:2020年1月28号 早7点

    学习产出:

    环境配置:

    1. 引入依赖
      soul-bootstrap 项目的 pom.xml 文件中引入了 soul-spring-boot-starter-sync-data-nacos 这个 starter 。
      <dependency>
              <groupId>org.dromara</groupId>
               <artifactId>soul-spring-boot-starter-sync-data-nacos</artifactId>
               <version>${last.version}</version>
         </dependency>
    
    1. 环境配置

    soul-bootstrap 项目的 application-local.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地址,集群环境请使用(,)分隔。
    		
    

    在 soul-admin 项目中的 application.yml 文件中配置 nacos 同步的相关配置如下:

    soul:
      sync:
        nacos:
          url: localhost:8848
          namespace: 7aba82c9-426b-4d72-89c9-060d9af63e14
           # 此处为前文中提到的命名空间ID或命名空间名称 soul
    

    Nacos 的关键特性
    服务发现和服务健康监测
    动态配置服务
    动态 DNS 服务
    服务及其元数据管理
    Nacos 中的几个概念
    命名空间(Namespace): 不同环境的配置隔离
    配置分组(Group): 不同的服务可以归类到同一分组。一般将一个项目的配置分到一组
    配置集(Data ID) : 一个配置文件通常就是一个配置集

    1. nacos同步策略-源码追踪
      从 soul-bootstrap 开始追踪
      NacosSyncDataConfiguration 这个类加载了 soul.sync.nacos 这段配置。该类创建了 nacosSyncDataService 。
    @Bean
    public SyncDataService nacosSyncDataService(final ObjectProvider<ConfigService> configService, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
                                                final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
        log.info("you use nacos sync soul data.......");
        return new NacosSyncDataService(configService.getIfAvailable(), pluginSubscriber.getIfAvailable(),
                                    metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
    }
    
    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);
    }
    

    进入 watcherData 方法,此时我们会进入一个重要的类 NacosCacheHandler 。

    // 即便对 lambda 不熟悉,我们也可以大致推测,这里的功能应该是通过调用updateXxxMap方法,对元数据、插件、选择器等进行监听
    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;
            }
        };
        oc.change(getConfigAndSignListener(dataId, listener));
        LISTENERS.computeIfAbsent(dataId, key -> new ArrayList<>()).add(listener);
    }
    
    protected interface OnChange {
        void change(String changeData);
    }
    

    updatePluginMap 方法:

    protected void updatePluginMap(final String configInfo) {
        try {
            // Fix bug #656(https://github.com/dromara/soul/issues/656) 此处解决了一个 nacos data sync error 的 bug
            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);
        }
    }
    

    追踪一下 unSubscribe(pluginData) 和 onSubscribe(pluginData) ,进入 CommonPluginDataSubscriber 中:

    @Override
    public void onSubscribe(final PluginData pluginData) {
        subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
    }
    
    @Override
    public void unSubscribe(final PluginData pluginData) {
        subscribeDataHandler(pluginData, DataEventTypeEnum.DELETE);
    }
    
    // 根据传入的数据类型ID,和数据处理类型,调用相应的处理函数,看具体是执行update更新还是delete删除
    private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
        Optional.ofNullable(classData).ifPresent(data -> {
            if (data instanceof PluginData) {
                PluginData pluginData = (PluginData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cachePluginData(pluginData);
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removePluginData(pluginData);
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.removePlugin(pluginData));
                }
            } else if (data instanceof SelectorData) {
                SelectorData selectorData = (SelectorData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.removeSelector(selectorData));
                }
            } else if (data instanceof RuleData) {
                RuleData ruleData = (RuleData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheRuleData(ruleData);
                    Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeRuleData(ruleData);
                    Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.removeRule(ruleData));
                }
            }
        });
    }
    

    admin追踪:
    是 DataSyncConfiguration 这个类加载了 soul.sync.nacos 这段配置。该类创建了 NacosDataChangedListener 。

    /**
     * The type Nacos listener.
     */
    @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);
        }
    
        /**
             * Nacos data init zookeeper data init.
             *
             * @param configService the config service
             * @param syncDataService the sync data service
             * @return the nacos data init
             */
        @Bean
        @ConditionalOnMissingBean(NacosDataInit.class)
        public NacosDataInit nacosDataInit(final ConfigService configService, final SyncDataService syncDataService) {
            return new NacosDataInit(configService, syncDataService);
        }
    }
    
    @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;
        if (dataIdNotExist(pluginDataId) && dataIdNotExist(authDataId) && dataIdNotExist(metaDataId)) {
            syncDataService.syncAll(DataEventTypeEnum.REFRESH);
        }
    }
    
    // type 是 REFRESH
    @Override
    public boolean syncAll(final DataEventTypeEnum type) {
        appAuthService.syncData();
        List<PluginData> pluginDataList = pluginService.listAll();
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
        List<SelectorData> selectorDataList = selectorService.listAll();
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, type, selectorDataList));
        List<RuleData> ruleDataList = ruleService.listAll();
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, type, ruleDataList));
        metaDataService.syncData();
        return true;
    }
    
    public static void send(final String message, final DataEventTypeEnum type) {
        if (StringUtils.isNotBlank(message)) {
            if (DataEventTypeEnum.MYSELF == type) {
                Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
                if (session != null) {
                    sendMessageBySession(session, message);
                }
            } else {
                SESSION_SET.forEach(session -> sendMessageBySession(session, message));
            }
        }
    }
  • 相关阅读:
    利用拦截器与自定义注解实现未登录拦截
    消息中间件activeMQ
    linux中redis的安装配置,后门漏洞修复及其攻击方法整合
    hibernate二级缓存
    初级程序员实战面试宝典(二)
    spring定时任务之-quartz调度器
    【微信小程序推广营销】教你微信小程序SEO优化,让你的小程序快人一步抢占先机
    ajax——CORS跨域调用REST API 的常见问题以及前后端的设置
    后台管理UI的选择
    微信小程序如何引入外部字体库iconfont的图标
  • 原文地址:https://www.cnblogs.com/qishun/p/14337879.html
Copyright © 2020-2023  润新知