【高性能网关soul学习】5. soul-admin 数据配置及Websocket 方式数据同步
本文主要目标:
本篇是对admin后台操作数据以后,同步到网关的流程介绍
- 用户可以在 soul-admin 后台任意修改数据,并马上同步到网关的jvm内存中。
- 同步soul的插件数据,选择器,规则数据,元数据,签名数据等等。
- 所有插件的选择器,规则都是动态配置,立即生效,不需要重启服务。
- 数据配置的流程图如下所示:
打开浏览器的检查选项查看请求,然后访问admin控制台的插件列表,我们可以看到选择器的相关请求都包含 /selector ,到源码项目中全局搜索可以发现,具体的实现为SelectorController.java
类作为入口。在此查看该类所在的目录,我们可以发现admin项目的所有controller的,这里作为crud的入口。
这里主要简单介绍下和 网关服务soul-bootstrap 相关的几个配置项
- ConfigController:处理长轮询的数据同步
- MetaDataController:处理元数据的crud
- PluginController:处理插件的crud
- PluginHandleController:系统管理>>>插件处理管理 的crud
- SelectorController:选择器的crud
- RuleController:选择器下属规则的crud
- SoulClientController:之前分析过的,处理被代理服务器的注册信息
- SoulDictController:字典的crud
MetaDataController、PluginController、SelectorController、RuleController、SoulClientController等进行更新和创建之后,都会发送一个数据变更事件DataChangedEvent
事件。例如:
public class DataChangedEvent extends ApplicationEvent {
// 指定数据变更类型:是插件、选择器、规则还是其他类型数据变更
private ConfigGroupEnum groupKey;
// 指定数据变更的动作:新增还是更新还是删除
private DataEventTypeEnum eventType;
// 父类EventObject中,用来传输具体的变更数据
protected transient Object source;
}
eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.RULE, DataEventTypeEnum.UPDATE,
Collections.singletonList(RuleDO.transFrom(ruleDO, pluginDO.getName(), conditionDataList))));
全局搜索 ApplicationListener<DataChangedEvent>
我们可以发现,具体的实现类为DataChangedEventDispatcher
public class DataChangedEventDispatcher implements ApplicationListener<DataChangedEvent>, InitializingBean {
private List<DataChangedListener> listeners;
@Override
@SuppressWarnings("unchecked")
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 PLUGIN:
listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType());
break;
case RULE:
listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
break;
case SELECTOR:
listener.onSelectorChanged((List<SelectorData>) 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
的实现类有四种,默认情况下是使用的WebsocketDataChangedListener进行数据同步
- WebsocketDataChangedListener
- HttpLongPollingDataChangedListener
- NacosDataChangedListener
- ZookeeperDataChangedListener
soul
sync:
websocket:
enabled: true
# zookeeper:
# url: localhost:2181
# sessionTimeout: 5000
# connectionTimeout: 2000
# http:
# enabled: true
# nacos:
# url: localhost:8848
# namespace: 1c10d748-af86-43b9-8265-75f487d20c6c
# acm:
# enabled: false
# endpoint: acm.aliyun.com
# namespace:
# accessKey:
# secretKey:
下面对于默认的Websocket为例,简单介绍下
WebsocketDataChangedListener
- 以选择器数据变更事件为例,处理的时候首先将json转换成
WebsocketData
的内部封装数据,然后调用WebsocketCollector
发送更新消息 - 一般的更新事件(除MYSELF类型之外),会遍历注册在
SESSION_SET
中的 Websocket 的 session,然后send消息,同步到 soul-bootstrap
listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
@Override
public void onSelectorChanged(final List<SelectorData> selectorDataList, final DataEventTypeEnum eventType) {
WebsocketData<SelectorData> websocketData =
new WebsocketData<>(ConfigGroupEnum.SELECTOR.name(), eventType.name(), selectorDataList);
WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
}
public class WebsocketCollector {
if (StringUtils.isNotBlank(message)) {
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);
}
}
}
}
- soul-bootstrap 即soul网关这边会启动则会启动
WebsocketSyncDataService
来处理数据同步的服务- 每隔30秒会进行一次 websocket 的心跳检测,如果断开了,则进行重连
executor.scheduleAtFixedRate(() -> {
try {
if (client.isClosed()) {
boolean reconnectSuccess = client.reconnectBlocking();
if (reconnectSuccess) {
log.info("websocket reconnect is successful.....");
} else {
log.error("websocket reconnection is error.....");
}
}
} catch (InterruptedException e) {
log.error("websocket connect is error :{}", e.getMessage());
}
}, 10, 30, TimeUnit.SECONDS);
}
-
soul-bootstrap端的 其中 client 类型为
SoulWebsocketClient
,实际上收到event时就是进行反序列化,然后让websocketDataHandler进行处理- websocketDataHandler实际上就是一个枚举工厂,根据数据的groupEnum类型,选择实际的
DataHandler
- websocketDataHandler 实例化的时候还把 DataSubscriber 注入到了DataHandler中,当DataHandler处理事件时,回调DataSubscriber来进行实际的处理
- websocketDataHandler实际上就是一个枚举工厂,根据数据的groupEnum类型,选择实际的
@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);
}
public class WebsocketDataHandler {
private static final EnumMap<ConfigGroupEnum, DataHandler> ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);
public WebsocketDataHandler(final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers,
final List<AuthDataSubscriber> authDataSubscribers) {
ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataHandler(pluginDataSubscriber));
ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataHandler(pluginDataSubscriber));
ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataHandler(pluginDataSubscriber));
ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AuthDataHandler(authDataSubscribers));
ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataHandler(metaDataSubscribers));
}
public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
ENUM_MAP.get(type).handle(json, eventType);
}
}
-
Datahandler 这里使用了模板方法模式,抽象类AbstractDataHandler定义了根据事件类型处理的框架,然后 PluginDataHandler、SelectorDataHandler、RuleDataHandler、AuthDataHandler、MetaDataHandler 只需要是实现 doRefresh、doUpdate、doDelette等方法即可
public abstract class AbstractDataHandler<T> implements DataHandler { 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; case UPDATE: case CREATE: doUpdate(dataList); break; case DELETE: doDelete(dataList); break; default: break; } } } }
-
以SelectorDataHandler的都doUpdate为例继续分析,进入到
SelectorDataHandler
,可以看到该类主要就是对pluginDataSubscriber
进行的回调操作@Override protected void doUpdate(final List<SelectorData> dataList) { dataList.forEach(pluginDataSubscriber::onSelectorSubscribe); }
-
这里进入到 PluginDataSubscriber,查看源码可以看到实际上是在
CommonPluginDataSubscriber
中处理的
// CommonPluginDataSubscriber.java
private final Map<String, PluginDataHandler> handlerMap;
private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
Optional.ofNullable(classData).ifPresent(data -> {
// 先忽略别的逻辑,只看选择器数据的操作
if (data instanceof SelectorData) {
SelectorData selectorData = (SelectorData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
// 修改了缓存 BaseDataCache
BaseDataCache.getInstance().cacheSelectData(selectorData);
// 然后根据插件名称,找到插件处理器 PluginDataHandler 处理选择器的更新数据
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));
}
}
});
}
总结
本文简单介绍了以下几点:
- admin 部分 controller的功能
- 然后跟踪了在websocket 配置下的 soul-admin 和 soul-bootstrap 的数据同步机制