soul-admin 可以使用websocket与soul-bootstrap进行通信
1.配置
引入 jar 包
soul-admin中配置 websocket
soul-bootstrap中配置websokcet的地址
2. soul-admin 配置加载
soul-admin 端会加载一个 WebsocketDataChangedListener
, 该类中使用 WebsocketCollector
连接 websocket 来发送数据给 soul-bootstrap
admin端使用spring的事件注册机制在更改配置时发送事件,将修改的配置发送出去
每个service都会注入 eventPublisher
用来发布配置修改的事件 DataChangedEvent
。
DataChangedEventDispatcher
用来接收 DataChangedEvent
事件,该类会加载容器内配置的 DataChangedListener
public class DataChangedEventDispatcher implements ApplicationListener<DataChangedEvent>, InitializingBean {
private ApplicationContext applicationContext;
private List<DataChangedListener> listeners;
public DataChangedEventDispatcher(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// 接收 DataChangedEvent事件后使用循环DataChangedListener处理事件
@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
@Override
public void afterPropertiesSet() {
Collection<DataChangedListener> listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
}
}
因为我们使用的是 websocket, 我们以 RULE 为例,当接收到DataChangedEvent事件时就会调用 WebsocketDataChangedListener.onRuleChanged()
@Override
public void onRuleChanged(final List<RuleData> ruleDataList, final DataEventTypeEnum eventType) {
WebsocketData<RuleData> configData =
new WebsocketData<>(ConfigGroupEnum.RULE.name(), eventType.name(), ruleDataList);
WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
}
在这里使用 WebsocketCollector
发送数据
3.soul-boostrap配置加载
在项目启动时会在 WebsocketSyncDataConfiguration
中注册
websocketConfig
websocket 配置
pluginSubscriber
用于广播plugin的改动
metaSubscribers
用于广播 metaData的改动
authSubscribers
用于改动 auth的改动
看下 WebsocketSyncDataService
构造方法
public WebsocketSyncDataService(final WebsocketConfig websocketConfig,
final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers,
final List<AuthDataSubscriber> authDataSubscribers) {
// 配置的 soul-admin url,可能为多个
String[] urls = StringUtils.split(websocketConfig.getUrls(), ",");
// 创建一个定时线程池,用于定时重连 soul-admin
executor = new ScheduledThreadPoolExecutor(urls.length, SoulThreadFactory.create("websocket-connect", true));
for (String url : urls) {
try {
// 为 每一个 soul-admin url 创建一个 SoulWebsocketClient
clients.add(new SoulWebsocketClient(new URI(url), Objects.requireNonNull(pluginDataSubscriber), metaDataSubscribers, authDataSubscribers));
} catch (URISyntaxException e) {
log.error("websocket url({}) is error", url, e);
}
}
try {
// 遍历创建的 WebSocketClient 判断是否与 websocket 客户端相连
for (WebSocketClient client : clients) {
boolean success = client.connectBlocking(3000, TimeUnit.MILLISECONDS);
if (success) {
log.info("websocket connection is successful.....");
} else {
log.error("websocket connection is error.....");
}
// 线程池 定时连接客户端,避免掉线
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);
}
/* client.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxyaddress", 80)));*/
} catch (InterruptedException e) {
log.info("websocket connection...exception....", e);
}
}
在
SoulWebsocketClient
中会初始化 WebsocketDataHandler
, 内部缓存 5中数据的DataHandler用来更新客户端修改的数据。
3.使用websocket同步数据
当soul-admin修改数据时就会调用 SoulWebsocketClient.onMessage()
方法
在 WebsocketDataHandler
中根据启动加载缓存的具体 handler 处理数据。我们这里修改的是 rule 类型,所以会调用 RuleDataHandler.handle()
方法
因为是 ruleDate的update 最后调用 CommonPluginDataSubscriber.onRuleSubscribe()
来更新 ruleData
执行 subscribeDataHandler 更新缓存中的 ruleData
private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
Optional.ofNullable(classData).ifPresent(data -> {
if (data instanceof PluginData) {
// 省略
} else if (data instanceof SelectorData) {
// 省略
} else if (data instanceof RuleData) {
RuleData ruleData = (RuleData) data;
if (dataType == DataEventTypeEnum.UPDATE) {
// 更新本地缓存的 ruleData
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));
}
}
});
}
到这里 soul-admin 通过websocket 更新ruleData的流程就结束啦。