一、目的
使用websocket同步数据到网关并了解其运作原理
二、内容
2.1 背景
Soul
网关又是如何支持动态配置的呢?Soul目前支持四种数据同步模式:HTTP长轮询、Zookeeper、WebSocket、Nacos。今天我们主要是针对WebSocket同步策略进行分析。同步策略是在下图里的位置配置的
同时还要修改soul-admin的同步策略
修改之后记得重启两个服务;
下图展示了 Soul
数据同步的流程,Soul
网关在启动时,会从从配置服务同步配置数据,并且支持推拉模式获取配置变更信息,并且更新本地缓存。而管理员在管理后台,变更用户、规则、插件、流量配置,通过推拉模式将变更信息同步给 Soul
网关,具体是 push
模式,还是 pull
模式取决于配置。
如下图所示,soul-admin
在用户发生配置变更之后,会通过 EventPublisher
发出配置变更通知,由 EventDispatcher
处理该变更通知,然后根据配置的同步策略(http、weboscket、zookeeper),将配置发送给对应的事件处理器
如果是 websocket
同步策略,则将变更后的数据主动推送给 soul-web
,并且在网关层,会有对应的 WebsocketCacheHandler
处理器处理来处 admin
的数据推送;
我们来看一下DataChangedEventDispatcher类,它实现了springframework里面的ApplicationListener接口,重写了接口里面的onApplicationEvent()方法
调用时序图如下所示,其实是通过一个Switch进行匹配,匹配到DataChangedListenter里面相应的方法进行处理
public void onApplicationEvent(final DataChangedEvent event) {
for (DataChangedListener listener : listeners) {
switch (event.getGroupKey()) {
case APP_AUTH:
listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
break;
...
case META_DATA:
listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType());
break;
default:
throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
}
}
}
DataChangedListener是一个接口,接口里面定义了接收到不同类型数据改变的处理接口
public interface DataChangedListener {
//处理AppAuth数据修改
default void onAppAuthChanged(List<AppAuthData> changed, DataEventTypeEnum eventType) {
}
//处理Plugin数据修改
default void onPluginChanged(List<PluginData> changed, DataEventTypeEnum eventType) {
}
...
}
我们这里以onPluginChanged为例,继续进行分析,这里调用的是WebsocketDataChangedListener里面的onPluginChanged()方法:
//接收到15个插件的数据列表,和相应的eventType(DELETE、CREATE、UPDATE、REFRESH、MYSELF)进行处理
public void onPluginChanged(final List<PluginData> pluginDataList, final DataEventTypeEnum eventType) {
//组装取得的数据转成json格式并进行发送
WebsocketData<PluginData> websocketData =
new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
}
下一步走到WebsocketCollector.send()方法
public static void send(final String message, final DataEventTypeEnum type) {
if (StringUtils.isNotBlank(message)) {
//进行事件类型匹配,根据sessionKey从Session里面取得数据并进行发送
if (DataEventTypeEnum.MYSELF == type) {
try {
Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
if (session != null) {
session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("websocket send result is exception: ", e);
}
return;
}
for (Session session : SESSION_SET) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("websocket send result is exception: ", e);
}
}
}
}
通过ApplicationEventPublisher里面的publishEvent()方法发出配置变更通知,通知所有匹配的监听者
public void syncData() {
List<MetaDataDO> all = metaDataMapper.findAll();
if (CollectionUtils.isNotEmpty(all)) {
//publishEvent()方法发出配置变更通知,通知所有匹配的监听者
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, DataEventTypeEnum.REFRESH, MetaDataTransfer.INSTANCE.mapToDataAll(all)));
}
}
接下来数据就从Soul-admin就来到了soul-sync-data-websocket里面进行处理,AbstractDataHandler方法如下,它继承了DataHandler接口,重写了handle方法,匹配对应的事件并调相应的方法进行处理:
public abstract class AbstractDataHandler<T> implements DataHandler {
//对list进行转换
protected abstract List<T> convert(String json);
//刷新数据
protected abstract void doRefresh(List<T> dataList);
//更新数据
protected abstract void doUpdate(List<T> dataList);
//删除数据
protected abstract void doDelete(List<T> dataList);
@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;
...
default:
break;
}
}
}
接下来到了WebsocketDataHandler的executor()方法,根据事件类型匹配并调相应方法进行处理
public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
ENUM_MAP.get(type).handle(json, eventType);
}
接下来来到了SoulWebsocketClient的handleResult()方法
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);
}
然后由websocket客户端把数据写入缓存,通知用户等处理;
三、总结
今天我们总体看了一下websocket数据同步的主要流程,里面涉及了很多很好的设计模式和思想,很值得我们花时间去学习。