soul源码解读(五)
数据同步之websocket
今天我们来看下 soul admin 与 bootstrap 之间数据同步的源码。
官网链接如下:https://dromara.org/zh-cn/docs/soul/dataSync.html
先来张流程图
从第一天的启动日志我们发现它们是通过 websocket 来进行通信的。
看 admin 的配置发现,soul 数据同步有4种方式,分别是 websocket 、zookeeper 、http 、 nacos。
bootstrap 的配置和 admin 一样,只是多配置了一个 websocket 的地址
sync:
websocket :
urls: ws://localhost:9095/websocket
我们找到 admin 模块 org.dromara.soul.admin.listener.websocket 包下的 WebsocketCollector
@ServerEndpoint("/websocket")
public class WebsocketCollector {
可以看到类上注解了一个端点 websocket ,这就是上面 bootstrap 里的配置urls的地址来源。
接着看这个类里的代码,
@OnOpen
public void onOpen(final Session session) {
log.info("websocket on open successful....");
SESSION_SET.add(session);
}
我们看到 admin 在开启 websocket 连接之后,会把当前 session 存到一个 set 里。
有消息过来之后(比如 bootstrap 启动之后会发送连接消息过来,admin 就会把数据推送过去),会判断一下类型,把 session 存到当前线程的 ThreadLocal里,
@OnMessage
public void onMessage(final String message, final Session session) {
if (message.equals(DataEventTypeEnum.MYSELF.name())) {
try {
ThreadLocalUtil.put(SESSION_KEY, session);
SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
} finally {
ThreadLocalUtil.clear();
}
}
}
然后通过 sping 获取 SyncDataService 的 bean ,调用 syncAll(),里面会同步索引的权限、插件、选择器、规则、元数据等。
@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;
}
接下来,我们看看数据是怎么同步的。
以插件数据为例。
List<PluginData> pluginDataList = pluginService.listAll();
先获取所有的插件,通过 mapper 去数据库查询数据,然后经过 stream 转成 list 。
@Override
public List<PluginData> listAll() {
return pluginMapper.selectAll().stream()
.map(PluginTransfer.INSTANCE::mapToData)
.collect(Collectors.toList());
}
中间我们看到有个属性转换,点进去之后发现 PluginTransfer 的实现类是自动生成的。
我去搜索了下 mapstruct ,发现它是一个属性映射工具,只需要定义一个 Mapper 接口,MapStruct 就会自动实现这个映射接口,避免了复杂繁琐的映射实现。MapStruct官网地址: http://mapstruct.org/
今天又学到了一新的知识点。
然后我们往回看,获取到所以插件列表之后,执行了下面这段代码
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, type, pluginDataList));
我们又发现了新东西, ApplicationEventPublisher ,查资料发现这是一个 spring 官方推出的事件发布器,用来给程序解耦。
流程图如下
在 admin 后台更新一个插件的状态,然后 debug 跟踪之后搞清楚了调用链。
先更新数据库,然后发布事件,DataChangedEventDispatcher 监听到事件后,调用具体的WebsocketDataChangedListener ,然后通过 session 推送消息。
admin 发送消息的调用栈如下:
发布消息流程
最终推送消息是 WebsocketCollector 的 send()
public static void send(final String message, final DataEventTypeEnum type) {
//其他代码省略
session.getBasicRemote().sendText(message);
}
bootstrap 端接收调用栈如下:
接收流程
最终接收推送后,执行更新数据是 CommonPluginDataSubscriber 的 subscribeDataHandler()
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));
}
}
//其他代码省略
});
}
总结
今天学到了两个新的知识点:
1.mapstruct ,用来做各种 POJO 之间的属性转换
2.事件发布监听机制,可以解耦程序。