目录
问题开头
(1)admin跟bootstrap websocket创建连接后,如果在收发消息过程中消息丢失,如何知晓,且通过补救措施,保证消息一致?
答:没看到admin端有自动重试的功能啥的,但想起页面有同步功能,那么会不会是页面同步做了这么个补救措施?查看源码如下:
果然每次同步都会去发送消息给到soul网关,那么疑问先到此为止,继续看soul网关看看,还有没有其它的机制来保障消息同步的事情。
bootstrap同步
以上是bootstrap端处理的流程图。
@Configuration
@ConditionalOnClass(WebsocketSyncDataService.class)
@ConditionalOnProperty(prefix = "soul.sync.websocket", name = "urls")
@Slf4j
public class WebsocketSyncDataConfiguration {
@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.......");
return new WebsocketSyncDataService(websocketConfig.getIfAvailable(WebsocketConfig::new), pluginSubscriber.getIfAvailable(),
metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
}
@Bean
@ConfigurationProperties(prefix = "soul.sync.websocket")
public WebsocketConfig websocketConfig() {
return new WebsocketConfig();
}
}
soul-spring-boot-starter-sync-data-websocket的WebsocketSyncDataConfiguration会在网关启动的时候去读取配置文件的soul.sync.websocket.urls属性,然后注册websocketSyncDataService对象到spring 容器中。
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);
}
/* client.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxyaddress", 80)));*/
} catch (InterruptedException e) {
log.info("websocket connection...exception....", e);
}
}
在new WebsocketSyncDataService初始化逻辑中,可以看到会拿到url创建soulWebSocketClient,同时会启动一个线程池,扫描并对断线的client进行断线重连。
接下来就来到了核心类SoulWebsocketClient,每次创建连接后会进入onOpen方法,在这个方法里边有个参数alreadySync,默认给的是false,为false情况下,会去请求admin端做一次全量同步,否则不会同步。而这个位置笔者观察到只有首次启动的时候才是false。如果是断线重连,参数也会为true,也就说明断线重连并不会请求全量同步。(模拟断线重连,admin进程关掉,然后再重启,网关就会进行重连)。
@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);
}
onMessage方法为收到admin端发送的消息后就会调用此方法进行消息的消费处理,该方法李调用了hadleResult方法,此方法调用WebsocketDataHandler.executor。
该方法做的事情就是交给对应的handle处理。这里面又用了一个设计模式,叫抽象模板模式。
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;
}
}
}
AbstractDataHandler只做handle的类型判断,具体的doxxxx方法不做任何实现,有继承它的子类实现,也就是RuleDataHandler等xxxxHandler做最终的逻辑处理。
不难看出xxHandler的处理逻辑是会将data数据缓存到一个map里,同步会进行hadler.handlerxxx处理。
总结
以上介绍了websocket bootstrap同步的整个流程,最简单的跟踪方法就是可以再soul-admin页面打开和观察一个插件开关,然后断点调试查看,这里就不做演示了。
到最后,有一个小疑惑?
在onOpen方法里为啥加了个开关控制?如果由于网络抖动原因,双方连接断开了,admin发送消息的时候将直接丢弃消息,那么网关消息就丢失了,之后重连进入onOpen方法,由于加了开关,并不会进行全量同步,此时只能人工线上手工同步,而这对于线上使用来说是一个大问题,此问题暂时没有答案,不妨往下继续研究,或者官方再找找答案。