简介
此篇文章,我们来探索下Websocket数据同步具体细节
示例运行
启动数据库:
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latest
首先运行Soul-Admin
配置Soul-Bootstrap,配置websocket同步方式,运行Soul-Bootstrap,大致如下:
soul :
file:
enabled: true
corss:
enabled: true
dubbo :
parameter: multi
# 把 websocket 数据同步打开
sync:
websocket :
urls: ws://localhost:9095/websocket
运行Soul-Example-HTTP,注册一些数据用于Debug测试
源码Debug
在上篇的分析中,我们知道了数据更新的核心类及其核心代码如下:
public class CommonPluginDataSubscriber implements PluginDataSubscriber {
.......
// 下面这类函数又进行了一次封装,我们可以看到这里有那么一点小瑕疵,命名有点不统一;onSubscribe这个函数取的不是太好,不能见名识意,有点宽泛了
@Override
public void onSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
}
@Override
public void unSubscribe(final PluginData pluginData) {
subscribeDataHandler(pluginData, DataEventTypeEnum.DELETE);
}
public void onSelectorSubscribe(final SelectorData selectorData) {
subscribeDataHandler(selectorData, DataEventTypeEnum.UPDATE);
}
@Override
public void unSelectorSubscribe(final SelectorData selectorData) {
subscribeDataHandler(selectorData, DataEventTypeEnum.DELETE);
}
@Override
public void onRuleSubscribe(final RuleData ruleData) {
subscribeDataHandler(ruleData, DataEventTypeEnum.UPDATE);
}
@Override
public void unRuleSubscribe(final RuleData ruleData) {
subscribeDataHandler(ruleData, DataEventTypeEnum.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));
}
}
});
}
}
我们先拉看一手插件配置数据,在上面的:onSubscribe 和 unSubscribe 打上断点,重启Soul-Bootstrap
public class PluginDataHandler extends AbstractDataHandler<PluginData> {
// 恢复,启动的时候执行
@Override
protected void doRefresh(final List<PluginData> dataList) {
// 清空缓存数据
pluginDataSubscriber.refreshPluginDataSelf(dataList);
dataList.forEach(pluginDataSubscriber::onSubscribe);
}
// 数据更新(增改)
@Override
protected void doUpdate(final List<PluginData> dataList) {
dataList.forEach(pluginDataSubscriber::onSubscribe);
}
// 数据删除
@Override
protected void doDelete(final List<PluginData> dataList) {
dataList.forEach(pluginDataSubscriber::unSubscribe);
}
}
这个类就是websocket同步模块中唯一调用CommonPluginDataSubscriber相关接口的,可以看到它就三个接口:恢复、更新、删除
datalist我们查看值,可以发现全是我们插件相关的数据,很重要,我们继续跟踪其来源
我们接着查看调用栈,来到下面的函数,看到它对应调用了前面函数的三个接口
public abstract class AbstractDataHandler<T> implements DataHandler {
// 对应上面的三个操作:恢复、更新、删除
@Override
public void handle(final String json, final String eventType) {
List<T> dataList = convert(json);
if (CollectionUtils.isNotEmpty(dataList)) {
DataEventTypeEnum eventTypeEnum = DataEventTypeEnum.acquireByName(eventType);
switch (eventTypeEnum) {
case REFRESH:
case MYSELF:
doRefresh(dataList);
break;
case UPDATE:
case CREATE:
doUpdate(dataList);
break;
case DELETE:
doDelete(dataList);
break;
default:
break;
}
}
}
}
我们查看下DataEventTypeEnum:DELETE/CREATE/UPDATE/REFRESH/MYSELF,基本能对应上下面的SWITH的case
而且看到json转成的dataList,我们继续跟
public class WebsocketDataHandler {
public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
ENUM_MAP.get(type).handle(json, eventType);
}
}
这个函数type,和evenTtype稍微注意下,看一看ConfigGroupEnum:APP_AUTH/PLUGIN/RULE/SELECTOR/META_DATA,找了一些前面我们忽略的同步数据,发现除了插件配置数据、选择器数据、规则数据,还有APP_AUTH数据和META_DATA(感觉和RPC相关),后面我们补上测一测
继续跟,来到属性的Websocket,和前面分析文章中的基本一致,都是通过起一个websocket客户端,接收到消息后触发调用
@Slf4j
public final class SoulWebsocketClient extends WebSocketClient {
@Override
public void onMessage(final String result) {
handleResult(result);
}
@SuppressWarnings("ALL")
private void handleResult(final String result) {
WebsocketData websocketData = GsonUtils.getInstance().fromJson(result, WebsocketData.class);
ConfigGroupEnum groupEnum = ConfigGroupEnum.acquireByName(websocketData.getGroupType());
String eventType = websocketData.getEventType();
String json = GsonUtils.getInstance().toJson(websocketData.getData());
websocketDataHandler.executor(groupEnum, json, eventType);
}
}
基本上一个初始化的流程是更新完毕了
后面我们测试下修改,在后面管理界面,把rate_limiter状态给关了,修改后立马进入了debug,收到的数据如下:
{"groupType":"PLUGIN","eventType":"UPDATE","data":[{"id":"4","name":"rate_limiter","config":"{\"mode\":\"standalone\",\"master\":\"mymaster\",\"url\":\"127.0.0.1:6379\"}","role":1,"enabled":false}]}
可以看到比较关键的数据:groupType、eventType、data等等
后面进行了删除的测试,流程基本一致,类型变了而已
后面进行其他类型的测试:PLUGIN/RULE/SELECTOR/META_DATA,APP_AUTH不太去确定,就没有测
总体而言还是比较简单清晰,那就直接总结一波
总结
总体来说Websocket同步还是比较简单的,可能让人疑惑的是Websocket的使用吧。由于在工作中还是比较常使用websocket,所以理解读数据那块还是比较轻松的,如果大家有疑惑的话,可以搜索Spring Websocket的使用教程,自己动手玩一玩,估计就可以了
总结今天的Websocket同步如下图:
如上图所示,数据更新的流程如下:
- 1.SoulWebsocketClient :从Soul-Admin接收数据,进行更新,里面有数据类型、操作类型还有具体信息
- 2.WebsocketDataHandler :根据这ConfigGroupEnum(数据类型),调用相应的subscribe
- 3.subscribe :具体subscribe调用相应的类型的DataCache进行数据更新和删除