Soul网关源码解析(六):websocket同步数据
Soul网关源码解析(六):websocket同步数据Websocket同步数据数据同步配置启动时的同步admin的同步处理小结参考
通过上一节,我们知道在Bootstrap启动流程中,会与soul-admin进行数据同步,那么同步的方式采用的是什么呢?在哪里配置的?过程又是如何?这些都在本小节进行解答。
Websocket同步数据
数据同步配置
首先Bootstrap与soul-admin进行数据同步的方式可以配置,这意味着方式不止一种(Soul就是这么灵活),本小节介绍通过WebSocket的方式进行数据同步,配置的地方在Bootstrap的application.yml中配置,配置方式如下:
soul : sync: websocket : urls: ws://localhost:9095/websocket
启动时的同步
在Bootstrap启动初始化过程中,会构造SyncDataService这个Bean,这个Bean的构建过程会与admin进行数据同步,并专门开启定时任务线程检查连接。怎么找到SyncDataService类的,两种方式,一种是日志定位,在bootstrap启动时,会打印“you use websocket sync soul data.......”这条日志,通过整个项目搜索,能够定位到这个函数;另一种,同步配置定位,因为上面有介绍数据同步的配置是soul.sync.websocket.urls,这里可以使用它为搜索关键字进行搜索,如下所示
下面我们来看一下这个Bean构建时如何进行同步的,首先@Bean注释的方法,如果方法有参数,会先从Spring Bean容器里获取这些对象,如果没有,则尝试找到这些Bean的配置类中进行构造,这里WebsocketConfig对象就是将上面application.yml中配置的数据同步参数封装成对象,为后面连接使用。接着看这个函数体,就干一件事,创建了一个WebsocketSyncDataService对象,那我们进入其中瞧瞧。
#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(); } @Data public class WebsocketConfig { /** * if have more soul admin url,please config like this. * 127.0.0.1:8888,127.0.0.1:8889 */ private String urls; }
WebsocketSyncDataService对象的初始化有点长,一段一段看,首先获取数据同步配置的urls数组,依据数据长度创建定时线程池,作用用于websocket连接,接着遍历这个urls数组,根据每个url创建SoulWebsocketClient,并将它添加到WebsocketSyncDataService类的list数组里,然后遍历这个list对象,依次连接,并开启一个检查任务放到定时线程池中,每3-秒检查一次连接是否被关闭,第一次时会延迟10秒。
public WebsocketSyncDataService(final WebsocketConfig websocketConfig, final PluginDataSubscriber pluginDataSubscriber, final List<MetaDataSubscriber> metaDataSubscribers, final List<AuthDataSubscriber> authDataSubscribers) { # 获取数据同步配置urls 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("webswebsocketConfig.urlsocket url({}) is error", url, e); } } try { for (WebSocketClient client : clients) { // 尝试连接并保持3秒 boolean success = client.connectBlocking(3000, TimeUnit.MILLISECONDS); if (success) { log.info("websocket connection is successful....."); } else { log.error("websocket connection is error....."); } // 每30秒检查一次,初次延迟10秒 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对象,这个对象里保存了一个EnumMap<ConfigGroupEnum, DataHandler> ENUM_MAP
,这里面保存了插件,选择器,规则,认证与元数据的处理器,处理器里存着各自的订阅对象,当订阅对象发生变化时,会触发对应的事件对象处理,目前是在CommonPluginDataSubscriber
类中统一处理
#SoulWebsocketClient 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); } #WebsocketDataHandler 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)); }
admin的同步处理
上面是bootstrap端的流程,在发送同步信息到admin后,admin这边也有处理,它的处理在WebsocketCollector
类里,这里说明一下WebsocketCollector
类上有个@ServerEndpoint("/websocket")
,这个注解的值需要和上面的Bootstrap配置的url一致。这里admin会先调用onOpen函数,连接成功,并记录这个session,然后同步消息,最后发送这个消息。如果发生异常失败了,则会调用onError函数,接着调onClose将这个session从set中移除。
#WebsocketCollector admin#WebsocketCollector @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(); } } } public static void send(final String message, final DataEventTypeEnum type) { 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); } } } } @OnError public void onError(final Session session, final Throwable error) { SESSION_SET.remove(session); ThreadLocalUtil.clear(); log.error("websocket collection error: ", error); } @OnClose public void onClose(final Session session) { SESSION_SET.remove(session); ThreadLocalUtil.clear(); }
小结
本小结以admin与bootstrap同步数据的方式入手,介绍了websock的同步方式,先从bootstrap侧讲述启动时的同步逻辑,主体以定时线程任务保持连接,然后介绍了admin端在收到连接请求后的处理,分正常和异常两种,自此本小节结束。希望能帮到你,初识soul这样一个极致性能的网关项目。