Soul网关-websocket同步数据原理讲解 第二章
在上一节已经提到了,数据最终交给了DataChangedListener的各个子类去实现了不同规则的同步逻辑,接下来接着看在websocket同步模式下,数据如何同步的。
- 按照上一章的逻辑 websocket的DataChangedListener实现在最终同步数据时是通过 WebsocketCollector.send方法执行同步的,接下来让我们走进WebsocketCollector,看一看这个类大概做了些什么?
Server 端
- 如下面代码所示,我们可以看到有很多websocket的东西,在这里需要先补充一点知识。@ServerEndpoint 代表是websocket的服务端 @OnOpen 代表客户端与其建立连接后要做的事 @OnMessage代表接收到客户端的消息后要做的事 @OnClose代表关闭连接要做的事 @OnError代表出现异常时要做的事,
@ServerEndpoint("/websocket")
public class WebsocketCollector {
//多client 长度大于1
private static final Set<Session> SESSION_SET = new CopyOnWriteArraySet<>();
private static Session session;
@OnOpen
public void onOpen(final Session session) {
log.info("websocket on open successful.... {}",session.getRequestURI());
SESSION_SET.add(session);
}
@OnMessage
public void onMessage(final String message, final Session session) {
log.info(" websocket onMessage 内容为{}",message);
if (message.equals(DataEventTypeEnum.MYSELF.name())) {
WebsocketCollector.session = session;
SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
}
}
@OnClose
public void onClose(final Session session) {
SESSION_SET.remove(session);
WebsocketCollector.session = null;
}
@OnError
public void onError(final Session session, final Throwable error) {
SESSION_SET.remove(session);
WebsocketCollector.session = null;
log.error("websocket collection error: ", error);
}
public static void send(final String message, final DataEventTypeEnum type) {
log.info(" websocket 接收到信息 内容为{}",message);
if (StringUtils.isNotBlank(message)) {
if (DataEventTypeEnum.MYSELF == type) {
try {
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);
}
}
}
}
}
-
在这里可以理解为WebsocketCollector就是一个单独的server,当其他的client和它进行连接的时候,它定义了一系列“事件”的方法,在这里我们只需要重点关注onMessage和send方法即可。
-
查看onMessage方法作用,在该方法中只有对传递的类型是MYSELF的处理,这里主要指的是当客户端与服务端建立连接后,客户端会发送一条MYSELF(指同步所有)的消息,这时服务端会向客户端发送要同步的消息。
@OnMessage
public void onMessage(final String message, final Session session) {
log.info(" websocket onMessage 内容为{}",message);
if (message.equals(DataEventTypeEnum.MYSELF.name())) {
WebsocketCollector.session = session;
//同步所有数据 具体底层逻辑就是查表 发布事件给client端
SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
}
}
- 在看一眼 send方法道理做了什么,可以看出send方法的大体逻辑就是向client端发送了此次变更的数据
public static void send(final String message, final DataEventTypeEnum type) {
log.info(" websocket 接收到信息 内容为{}",message);
if (StringUtils.isNotBlank(message)) {
if (DataEventTypeEnum.MYSELF == type) {
try {
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);
}
}
}
}
- 到此整个websocket同步数据的Server端逻辑基本已经梳理的很清楚了
Client
- client端连接是如何建立的?
根据bootstrap的配置文件找到WebsocketSyncDataConfiguration类,会发现在下面的代码中创建了WebsocketSyncDataService的bean(传递的XXXSubscriber先不管没看到那)
@Bean
public SyncDataService websocketSyncDataService(final ObjectProvider<WebsocketConfig> websocketConfig, final ObjectProvider<PluginDataSubscriber> pluginSubscriber,
final ObjectProvider<List<MetaDataSubscriber>> metaSubscribers, final ObjectProvider<List<AuthDataSubscriber>> authSubscribers) {
log.info("you use websocket sync soul data.......");
websocketConfig.getIfAvailable();
return new WebsocketSyncDataService(websocketConfig.getIfAvailable(WebsocketConfig::new), pluginSubscriber.getIfAvailable(),
metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
- 这个类WebsocketSyncDataService在创建时又做了哪些事呢?
1.切割URL 建立client与server连接(ps:多URL目的是为了多admin部署保持数据一致)
2.创建定时任务线程池 达到心跳保活
public WebsocketSyncDataService(final WebsocketConfig websocketConfig,
final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers,
final List<AuthDataSubscriber> authDataSubscribers) {
String[] urls = StringUtils.split(websocketConfig.getUrls(), ",");
//创建定时线程池
executor = new ScheduledThreadPoolExecutor(urls.length,
SoulThreadFactory.create("websocket-connect", true));
for (String url : urls) {
try {
clients.add(new SoulWebsocketClient(new URI(url), Objects.requireNonNull(pluginDataSubscriber), metaDataSubscribers, authDataSubscribers));
} catch (URISyntaxException e) {
log.error("websocket url({}) is error", url, e);
}
}
try {
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);
}
} catch (InterruptedException e) {
log.info("websocket connection...exception....", e);
}
}
- 我们再来看一下SoulWebsocketClient类做了什么?
- 因为继承于WebSocketClient,所以初始化时创建了与server端的连接
- 创建WebsocketDataHandler数据处理类
- 实现了WebSocketClient的方法,开启连接,关闭连接,收到消息,在这里我们可以看到这里与Server端是一一对应的,在初始化时向server端发送了一条MYSELF的消息,server端在收到后,又向client端同步了所有的消息,此时走的就是client端对应的onMessage方法
public final class SoulWebsocketClient extends WebSocketClient {
private final WebsocketDataHandler websocketDataHandler;
/**
* 初始化websocket客户端
*
* @param serverUri the server uri
* @param pluginDataSubscriber the plugin data subscriber
* @param metaDataSubscribers the meta data subscribers
* @param authDataSubscribers the auth data subscribers
*/
public SoulWebsocketClient(final URI serverUri, final PluginDataSubscriber pluginDataSubscriber,
final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) {
//初始化连接
super(serverUri);
this.websocketDataHandler = new WebsocketDataHandler(pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
}
private volatile boolean alreadySync = Boolean.FALSE;
//初始化同步数据
@Override
public void onOpen(final ServerHandshake serverHandshake) {
if (!alreadySync) {
send(DataEventTypeEnum.MYSELF.name());
alreadySync = true;
}
}
//处理server端的消息
@Override
public void onMessage(final String result) {
handleResult(result);
}
@Override
public void onClose(final int i, final String s, final boolean b) {
this.close();
}
@Override
public void onError(final Exception e) {
this.close();
}
//处理消息方法
@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);
}
}
- 那么在handleResult中它又是如何处理数据的呢?
很明显 在其内部定义了各个模块子类的数据处理实现,通过简单工厂+策略模式实现了代码的解耦。
// 定义简单工厂
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));
}
/**
* Executor.
*
* @param type the type
* @param json the json
* @param eventType the event type
*/
public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
//策略模式实现获取
ENUM_MAP.get(type).handle(json, eventType);
}
- 接下来就是看DataHandler子类的具体实现,在这里又发现了AbstractDataHandler,发现所有数据处理方法都是走的抽象类的实现DataHandler的handle方法,通过方法我们基本已经知晓了在server端多个操作类型针对于client端的处理可能是一个操作。如下代码所示
@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;
}
}
}
- 在往下走看具体逻辑 大体都是相似的,都是通过操作的类型修改了这个模块对应的ConcurrentHashMap的值,这里不做过多赘述,有兴趣的可以往下面点一点,还有还有多态的应用(解耦的真棒)。
至此整体的websocket数据同步逻辑已经梳理清楚了
初始化连接建立
1.client端与server建立连接
2.client端向server端发送MYSELF信息
3.server端接收到MYSELF消息进行查询所有数据
4.server端将查询到的数据通过Spring的事件机制再次通知给client
5.client端接收数据后查找对应的模块,找到对应的处理方法
6.进行相关逻辑的修改ConcurrentHashMap操作。
7.数据同步完成
常规数据同步
1.对应逻辑发生数据变更
2.接着上面的初始化连接建立的3 4 5 6 7步(不粘贴了)。