【Soul源码阅读】15.soul-admin 与 soul-bootstrap 同步机制之 nacos 解析(下)

目录

1.准备

2.页面操作

3.跟踪源码

3.1 soul-admin 端

3.2 soul-bootstrap 端


上一篇讲到使用 nacos 同步策略,在项目启动时的同步机制,今天来看下在页面操作时,是怎么同步的。

1.准备

按照昨天的帖子(【Soul源码阅读】14.soul-admin 与 soul-bootstrap 同步机制之 nacos 解析(上)),把项目启动起来,soul-admin、soul-bootstrap 和 soul-examples-http 三个项目,当然还有依赖的 MySQL 和 nacos。

 

2.页面操作

我们以修改 selector 为例。

修改前,在 nacos 里看下 Data Id 为 "soul.selector.json" 的数据如下:

{"divide":[{"id":"1354790660314062848","pluginId":"5","pluginName":"divide","name":"/http","matchMode":0,"type":1,"sort":1,"enabled":true,"loged":true,"continued":true,"handle":"[{\"upstreamHost\":\"localhost\",\"protocol\":\"http://\",\"upstreamUrl\":\"10.0.0.5:8188\",\"weight\":50,\"status\":true,\"timestamp\":0,\"warmup\":0}]","conditionList":[{"paramType":"uri","operator":"match","paramName":"/","paramValue":"/http/**"}]}]}

格式化如下:(可以看到 divide 插件里有一个 http 的 selector)

{
    "divide": [
        {
            "id": "1354790660314062848",
            "pluginId": "5",
            "pluginName": "divide",
            "name": "/http",
            "matchMode": 0,
            "type": 1,
            "sort": 1,
            "enabled": true,
            "loged": true,
            "continued": true,
            "handle": "[{\"upstreamHost\":\"localhost\",\"protocol\":\"http://\",\"upstreamUrl\":\"10.0.0.5:8188\",\"weight\":50,\"status\":true,\"timestamp\":0,\"warmup\":0}]",
            "conditionList": [
                {
                    "paramType": "uri",
                    "operator": "match",
                    "paramName": "/",
                    "paramValue": "/http/**"
                }
            ]
        }
    ]
}

在 handle 字段中,有具体的业务系统信息,我们把权重 weight 从50改到80。

【Soul源码阅读】15.soul-admin 与 soul-bootstrap 同步机制之 nacos 解析(下)_第1张图片

再次查看 nacos 中的配置数据:

{"divide":[{"id":"1354790660314062848","pluginId":"5","pluginName":"divide","name":"/http","matchMode":0,"type":1,"sort":1,"enabled":true,"loged":true,"continued":true,"handle":"[{\"upstreamHost\":\"localhost\",\"protocol\":\"http://\",\"upstreamUrl\":\"10.0.0.5:8188\",\"weight\":\"80\",\"status\":true,\"timestamp\":\"0\",\"warmup\":\"0\"}]","conditionList":[{"paramType":"uri","operator":"match","paramName":"/","paramValue":"/http/**"}]}]}

很显然,weight 改成了 80。

下面我们看下是如何实现的。

3.跟踪源码

3.1 soul-admin 端

F12 可以看到修改 selector 时发送了一个 PUT 请求 http://localhost:9095/selector/1354790660314062848

很容易就找到了 SelectorController:

// SelectorController.java
    /**
     * update Selector.
     *
     * @param id          primary key.
     * @param selectorDTO selector.
     * @return {@linkplain SoulAdminResult}
     */
    @PutMapping("/{id}")
    public SoulAdminResult updateSelector(@PathVariable("id") final String id, @RequestBody final SelectorDTO selectorDTO) {
        Objects.requireNonNull(selectorDTO);
        selectorDTO.setId(id);
        Integer updateCount = selectorService.createOrUpdate(selectorDTO);
        return SoulAdminResult.success(SoulResultMessage.UPDATE_SUCCESS, updateCount);
    }

逻辑都在 createOrUpdate 方法里:

// SelectorServiceImpl.java
    /**
     * create or update selector.
     *
     * @param selectorDTO {@linkplain SelectorDTO}
     * @return rows
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public int createOrUpdate(final SelectorDTO selectorDTO) {
        int selectorCount;
        SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
        List selectorConditionDTOs = selectorDTO.getSelectorConditions();
        if (StringUtils.isEmpty(selectorDTO.getId())) {
            // 数据没有 ID,走新增逻辑
            selectorCount = selectorMapper.insertSelective(selectorDO);
            selectorConditionDTOs.forEach(selectorConditionDTO -> {
                selectorConditionDTO.setSelectorId(selectorDO.getId());
                selectorConditionMapper.insertSelective(SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO));
            });
        } else {
            // 数据有 ID,走更新逻辑
            selectorCount = selectorMapper.updateSelective(selectorDO);
            //delete rule condition then add
            selectorConditionMapper.deleteByQuery(new SelectorConditionQuery(selectorDO.getId()));
            selectorConditionDTOs.forEach(selectorConditionDTO -> {
                selectorConditionDTO.setSelectorId(selectorDO.getId());
                SelectorConditionDO selectorConditionDO = SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO);
                selectorConditionMapper.insertSelective(selectorConditionDO);
            });
        }
        publishEvent(selectorDO, selectorConditionDTOs);
        updateDivideUpstream(selectorDO);
        return selectorCount;
    }

前半部分都是数据库操作,就不过多解释了。

publishEvent 方法,发布事件:

// SelectorServiceImpl.java
    private void publishEvent(final SelectorDO selectorDO, final List selectorConditionDTOs) {
        // 从数据库获取 selector 对应的 pulugin 信息
        PluginDO pluginDO = pluginMapper.selectById(selectorDO.getPluginId());
        // 转换数据格式
        List conditionDataList =
                selectorConditionDTOs.stream().map(ConditionTransfer.INSTANCE::mapToSelectorDTO).collect(Collectors.toList());
        // publish change event.
        // 发布事件
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
                Collections.singletonList(SelectorDO.transFrom(selectorDO, pluginDO.getName(), conditionDataList))));
    }

通过发布事件、监听事件,解耦代码调用。

监听事件代码:

// DataChangedEventDispatcher.java
@Override
    @SuppressWarnings("unchecked")
    public void onApplicationEvent(final DataChangedEvent event) {
        for (DataChangedListener listener : listeners) {
            // 根据 groupKey 类型匹配不同的逻辑,我们这里是 SELECTOR
            switch (event.getGroupKey()) {
                case APP_AUTH:
                    listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
                    break;
                case PLUGIN:
                    listener.onPluginChanged((List) event.getSource(), event.getEventType());
                    break;
                case RULE:
                    listener.onRuleChanged((List) event.getSource(), event.getEventType());
                    break;
                case SELECTOR:
                    listener.onSelectorChanged((List) event.getSource(), event.getEventType());
                    break;
                case META_DATA:
                    listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
            }
        }
    }

debug 跟踪到这里时,有2个监听器,我们使用的 nacos 同步策略,关注 NacosDataChangedListener 即可。

【Soul源码阅读】15.soul-admin 与 soul-bootstrap 同步机制之 nacos 解析(下)_第2张图片

// NacosDataChangedListener.java
@Override
    public void onSelectorChanged(final List changed, final DataEventTypeEnum eventType) {
        // 从 nacos 获取配置信息,并更新本地缓存 SELECTOR_MAP 中数据
        updateSelectorMap(getConfig(NacosPathConstants.SELECTOR_DATA_ID));
        // 根据不同事件类型,执行不同逻辑,我们这里类型是 UPDATE,执行 default 逻辑
        switch (eventType) {
            case DELETE:
                changed.forEach(selector -> {
                    List 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:
                SELECTOR_MAP.keySet().removeAll(SELECTOR_MAP.keySet());
                changed.forEach(selector -> {
                    List ls = SELECTOR_MAP
                            .getOrDefault(selector.getPluginName(), new ArrayList<>())
                            .stream()
                            .sorted(SELECTOR_DATA_COMPARATOR)
                            .collect(Collectors.toList());
                    ls.add(selector);
                    SELECTOR_MAP.put(selector.getPluginName(), ls);
                });
                break;
            default:
                // 这里又对 SELECTOR_MAP 更新了一遍,没太搞明白,有谁搞清楚了可以跟我说下吗?
                changed.forEach(selector -> {
                    List 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;
        }
        // 发布配置,将配置信息保存到 nacos
        publishConfig(NacosPathConstants.SELECTOR_DATA_ID, SELECTOR_MAP);
    }


    @SneakyThrows
    private String getConfig(final String dataId) {
        // 从 nacos 获取配置信息
        String config = configService.getConfig(dataId, NacosPathConstants.GROUP, NacosPathConstants.DEFAULT_TIME_OUT);
        return StringUtils.hasLength(config) ? config : NacosPathConstants.EMPTY_CONFIG_DEFAULT_VALUE;
    }

    // 更新本地缓存数据
    private void updateSelectorMap(final String configInfo) {
        JsonObject jo = GsonUtils.getInstance().fromJson(configInfo, JsonObject.class);
        Set set = new HashSet<>(SELECTOR_MAP.keySet());
        for (Entry e : jo.entrySet()) {
            set.remove(e.getKey());
            List 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);
    }

    @SneakyThrows
    private void publishConfig(final String dataId, final Object data) {
        configService.publishConfig(dataId, NacosPathConstants.GROUP, GsonUtils.getInstance().toJson(data));
    }

3.2 soul-bootstrap 端

这里一旦把数据保存到 nacos,就会触发昨天分析的监听器,调用 receiveConfigInfo 方法。

// NacosCacheHandler.java
    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);
    }

调用 oc.change 方法,就回到了上一篇文章中分析的5个方法 updateXxxMap,更新内存数据。

好了,到这里就把 nacos 同步策略大体流程分析完了,同步策略涉及的内容有点儿多,有点儿杂,明天整理一篇整合文章。

 

你可能感兴趣的:(Soul网关,Java,Soul,网关,Java,源码阅读)