目录
1.准备
2.页面操作
3.跟踪源码
3.1 soul-admin 端
3.2 soul-bootstrap 端
上一篇讲到使用 nacos 同步策略,在项目启动时的同步机制,今天来看下在页面操作时,是怎么同步的。
按照昨天的帖子(【Soul源码阅读】14.soul-admin 与 soul-bootstrap 同步机制之 nacos 解析(上)),把项目启动起来,soul-admin、soul-bootstrap 和 soul-examples-http 三个项目,当然还有依赖的 MySQL 和 nacos。
我们以修改 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。
再次查看 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。
下面我们看下是如何实现的。
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 即可。
// 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));
}
这里一旦把数据保存到 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 同步策略大体流程分析完了,同步策略涉及的内容有点儿多,有点儿杂,明天整理一篇整合文章。