使用websocket同步数据到网关
一、配置环境
1、soul-admin
soul-admin/src/main/resources/application.yml
soul:
sync:
websocket:
enabled: true
2、soul-bootstrap
soul-bootstrap/src/main/resources/application-local.yml
soul:
sync:
websocket :
urls: ws://localhost:9095/websocket,ws://localhost:9096/websocket
soul-bootstrap/pom.xml
<!--soul data sync start use websocket-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-sync-data-websocket</artifactId>
<version>${project.version}</version>
</dependency>
3、启动服务
1、soul-admin,端口9095
2、soul-admin,端口9096
3、soul-bootstrap,端口9195
4、soul-bootstrap,端口9196
二、soul-admin 端
1、soul-admin 启动会注入配置类 DataSyncConfiguration,像容器中注入 WebsocketListener。
org.dromara.soul.admin.config.DataSyncConfiguration
@Configuration
@ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(WebsocketSyncProperties.class)
static class WebsocketListener {
// 负责监听数据变化
@Bean
@ConditionalOnMissingBean(WebsocketDataChangedListener.class)
public DataChangedListener websocketDataChangedListener() {
return new WebsocketDataChangedListener();
}
// 负责维护websocket连接通信
@Bean
@ConditionalOnMissingBean(WebsocketCollector.class)
public WebsocketCollector websocketCollector() {
return new WebsocketCollector();
}
@Bean
@ConditionalOnMissingBean(ServerEndpointExporter.class)
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebsocketDataChangedListener 负责监听网关数据变化的处理
org.dromara.soul.admin.listener.websocket.WebsocketDataChangedListener
通过 WebsocketCollector.send 发送给websocket客户端(soul-bootstrap)
public class WebsocketDataChangedListener implements DataChangedListener {
@Override
public void onPluginChanged(final List<PluginData> pluginDataList, final DataEventTypeEnum eventType) {
WebsocketData<PluginData> websocketData =
new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
}
......
}
WebsocketCollector 负责打开websocket等待客户端连接
org.dromara.soul.admin.listener.websocket.WebsocketCollector
Set SESSION_SET 存放客户端session,用来与多个soul-bootstrap同步网关数据
@ServerEndpoint("/websocket")
public class WebsocketCollector {
private static final Set<Session> SESSION_SET = new CopyOnWriteArraySet<>();
private static final String SESSION_KEY = "sessionKey";
// 打开websocket连接
@OnOpen
public void onOpen(final Session session) {
log.info("websocket on open successful....");
SESSION_SET.add(session);
}
// 监听接收客户单消息
@OnMessage
public void onMessage(final String message, final Session session) {
if (message.equals(DataEventTypeEnum.MYSELF.name())) {
try {
ThreadLocalUtil.put(SESSION_KEY, session);
SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
} finally {
ThreadLocalUtil.clear();
}
}
}
......
// 发送数据,会遍历所有的session发送消息
public static void send(final String message, final DataEventTypeEnum type) {
if (StringUtils.isNotBlank(message)) {
if (DataEventTypeEnum.MYSELF == type) {
Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
if (session != null) {
sendMessageBySession(session, message);
}
} else {
SESSION_SET.forEach(session -> sendMessageBySession(session, message));
}
}
}
......
}
2、soul-admin 监听到soul-bootstrap连接会打印如下信息
并存入 SESSION_SET.add(session);
2021-01-27 17:45:55.878 INFO 15932 --- [0.0-9096-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 86 ms
2021-01-27 17:45:59.361 INFO 15932 --- [0.0-9096-exec-1] o.d.s.a.l.websocket.WebsocketCollector : websocket on open successful....
三、soul-bootstrap
1、soul-bootstrap 启动
加入了依赖 soul-spring-boot-starter-sync-data-websocket,启动后自动注入WebsocketSyncDataConfiguration
org.dromara.soul.spring.boot.starter.sync.data.websocket.WebsocketSyncDataConfiguration
向容器中注入 WebsocketSyncDataService
@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));
}
2、WebsocketSyncDataService
org.dromara.soul.plugin.sync.data.websocket.WebsocketSyncDataService
初始化会根据配置的websocket urls,建立websocket连接
1、如果配置多个url,也就是soul-admin 有多个服务,会循环建立多个SoulWebsocketClient
2、client.connectBlocking(3000, TimeUnit.MILLISECONDS) 可以链接超时判断
3、executor.scheduleAtFixedRate 连接失败后的,负责定时重连
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);
}
}
3、SoulWebsocketClient
org.dromara.soul.plugin.sync.data.websocket.client.SoulWebsocketClient
建立连接后,发送DataEventTypeEnum.MYSELF消息,用来初始化网关数据
@Override
public void onOpen(final ServerHandshake serverHandshake) {
if (!alreadySync) {
send(DataEventTypeEnum.MYSELF.name());
alreadySync = true;
}
}
4、soul-bootstrap 启动成功后,如果多个soul-admin中有连接失败的情况,会定时重连
2021-01-27 17:45:50.085 INFO 15959 --- [ main] b.s.s.d.w.WebsocketSyncDataConfiguration : you use websocket sync soul data.......
2021-01-27 17:45:55.687 ERROR 15959 --- [ main] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket connection is error.....
2021-01-27 18:00:53.430 ERROR 15959 --- [ main] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket connection is error.....
2021-01-27 18:00:54.950 INFO 15959 --- [ocket-connect-1] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket reconnect is successful.....
2021-01-27 18:00:55.164 INFO 15959 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-01-27 18:00:56.691 INFO 15959 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 9196
2021-01-27 18:00:56.695 INFO 15959 --- [ main] o.d.s.b.SoulBootstrapApplication : Started SoulBootstrapApplication in 909.641 seconds (JVM running for 910.725)
2021-01-27 18:01:05.709 INFO 15959 --- [ocket-connect-2] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket reconnect is successful.....
2021-01-27 18:02:03.454 INFO 15959 --- [ocket-connect-1] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket reconnect is successful.....
2021-01-27 18:02:05.705 ERROR 15959 --- [ocket-connect-2] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket reconnection is error.....
2021-01-27 18:02:35.704 ERROR 15959 --- [ocket-connect-2] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket reconnection is error.....
四、总结
1、soul-admin 启动后通过 WebsocketCollector --> onOpen 监听 soul-bootstrap 连接,并保存 Session
2、soul-bootstrap 启动后初始化 WebsocketSyncDataService 创建 SoulWebsocketClient,如果有多个 soul-admin ,会创建多个SoulWebsocketClient,并创建定时任务进行失败重连
3、SoulWebsocketClient --> onOpen 打开连接后会发送消息 DataEventTypeEnum.MYSEL,用来初始化网关数据
4、WebsocketCollector --> onMessage 接收判断并判断消息 DataEventTypeEnum.MYSEL
5、SyncDataServiceImpl --> syncAll 负责处理 DataEventTypeEnum.MYSEL 向soul-admin同步全部的网关数据