ShenYu网关源码阅读(十九)Nacos数据同步初始化修复-Admin端

1,117 阅读5分钟

简介

    本篇文章记录一次Nacos数据同步解析中发现问题,并提交PR,成功合并,解决Soul-Admin的Nacos数据同步初始化问题

代码版本(Version)

commit b60fb246977ff36fa58ed0a7a9ce22d57dd30323 (upstream/master)
Author: cottom <11937539+cottom@users.noreply.github.com>
Date:   Sun Jan 24 22:06:34 2021 +0800

问题简述(Problem Description)

    1.在配置后Nacos,并在Nacos中新建对应namespace后,启动Admin,发现在Nacos中并无数据,需要在Admin管理后台页面中收到点击同步数据,数据才会同步到Nacos中

    2.在上一步同步中,rule相应的Id会建立在Nacos中,但其中并没有数据,导致访问失败

问题详情及解决方案探索

1.Nacos启动无法初始化问题

相关代码

    在同步配置类中,Nacos的配置只是简单启动了Nacos监听,没有类似Zookeeper的还有一个初始化的类,代码大致如下:

@Configuration
public class DataSyncConfiguration {

    // 这里只启动了一个监听的,并且在NacosDataChangedListener中,并没有发现任何的初始化操作
    @Configuration
    @ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
    @Import(NacosConfiguration.class)
    static class NacosListener {

        @Bean
        @ConditionalOnMissingBean(NacosDataChangedListener.class)
        public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
            return new NacosDataChangedListener(configService);
        }
    }
}

解决方案探索

    可以解决Zookeeper的代码,新建一个初始化数据的类,判断其数据节点是否存在,如果不存在,则进行从数据库中读取所有数据,发布事件,触发Nacos的初始化

public class DataSyncConfiguration {
    @Configuration
    @ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
    @Import(ZookeeperConfiguration.class)
    static class ZookeeperListener {

        @Bean
        @ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
        public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
            return new ZookeeperDataChangedListener(zkClient);
        }

        @Bean
        @ConditionalOnMissingBean(ZookeeperDataInit.class)
        public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
            return new ZookeeperDataInit(zkClient, syncDataService);
        }
    }
}

    初步写了一个Nacos初始化触发的类,模仿的ZookeeperDataInit,在run函数中,首先判断插件、元数据、认证数据的Id数据是否存在,如果不存在,则想Zookeeper一样触发同步所有数据的事件

public class NacosDataInit implements CommandLineRunner {

    private static final String GROUP = "DEFAULT_GROUP";

    private final ConfigService configService;
    private final SyncDataService syncDataService;

    public NacosDataInit(final ConfigService configService, final SyncDataService syncDataService) {
        this.configService = configService;
        this.syncDataService = syncDataService;
    }

    @Override
    public void run(String... args) throws Exception {
        String pluginId = NacosDataIdConstants.PLUGIN_DATA_ID;
        String authId = NacosDataIdConstants.AUTH_DATA_ID;
        String metaDataId = NacosDataIdConstants.META_DATA_ID;
        if (!isExists(pluginId) && !isExists(authId) && !isExists(metaDataId)) {
            syncDataService.syncAll(DataEventTypeEnum.REFRESH);
        }
    }

    @SneakyThrows
    private boolean isExists(String id) {
        return configService.getConfig(id, GROUP, 3000) != null;
    }
}

    经过初步测试,上面的初始化类,在Nacos为空时,重启Admin,能初步同步数据到Nacos中

    但还是存在下面的第二个问题:rule规则为空

2.同步插件数据后,rule规则没有进行同步,仍为空

相关代码

    如下代码所示,在进行插件同步(触发条件为在Admin管理后台界面点击同步自定义xxx插件),根据代码大意:会进行触发插件数据的更新和其选择器和规则,但调试发现,并不能成功更新规则数据,原因是下一段代码:

public class SyncDataServiceImpl implements SyncDataService {

    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;
    }
}

    从上面的函数事件发布后,进行规则的更新,来到下面的Nacos规则数据更新代码

    事件为REFRESH,在其处理逻辑中,我们发现其并能添加进去新的规则:因为ls的取值逻辑会一直为空,本身RULE_MAP就是为空的,取不到值

public void onRuleChanged(final List<RuleData> changed, final DataEventTypeEnum eventType) {
        updateRuleMap(getConfig(RULE_DATA_ID));
        switch (eventType) {
                .......
            case REFRESH:
            // 在Zookeeper的初始化触发MYSELF,那Nacos应该也是一样
            // 在初始化的时候,下面的ls中始终为空,rule无法添加进去
            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());
                    // 可以直接加进去
                    ls.add(rule);
                    RULE_MAP.put(rule.getSelectorId(), ls);
                });
                RULE_MAP.keySet().removeAll(set);
                break;
            default:
                ......
                break;
        }

        publishConfig(RULE_DATA_ID, RULE_MAP);
    }

解决方案探索

    在REFRESH的逻辑处理中出现了错误,REFRESH的处理逻辑参照zookeeper应该是:

  • 1.清空本地的缓存数据
  • 2.添加传入的新的数据

    但在下面原理的逻辑代码中,逻辑不是很清晰,将起来也比较费劲,我们直接修改为下面的代码:

public void onRuleChanged(final List<RuleData> changed, final DataEventTypeEnum eventType) {
        updateRuleMap(getConfig(RULE_DATA_ID));
        switch (eventType) {
                .......
            case REFRESH:
            case MYSELF:
                // 清空本地缓存数据
                SELECTOR_MAP.keySet().removeAll(SELECTOR_MAP.keySet());
                changed.forEach(rule -> {
                    // 根据当前rule对应的selectorid,得到rule所在的列表
                    // 如果为空,则新建一个列表
                    // 如果之前有,那取出来
                    List<RuleData> ls = RULE_MAP
                            .getOrDefault(rule.getSelectorId(), new ArrayList<>())
                            .stream()
                            .sorted(RULE_DATA_COMPARATOR)
                            .collect(Collectors.toList());
                    // 将新的规则添加到selector的列表中
                    ls.add(rule);
                    RULE_MAP.put(rule.getSelectorId(), ls);
                });
                break;
            default:
                ......
                break;
        }

        publishConfig(RULE_DATA_ID, RULE_MAP);
    }

    核心逻辑就是先清空缓存;然后逐个将rule添加到对应的selector的列表中

    经过初步测试,能解决初始化的时候rule没有数据和同步自定义插件没有更新rule的问题,并且请求访问正常

总结

    在前面的分析中,我们就遇到了Nacos的问题,然后我们先解析了Admin的Zookeeper的同步数据逻辑,以此来对应上我们这次的Nacos同步逻辑。并顺利的找到其相应的bug,并成功修改提交PR,成功合并!

    运用所学,解决了一个问题,真滴开心!

参考链接

Soul网关源码分析文章列表

掘金