soul网关数据同步之websocket同步方式探究

  昨天在跑springCloud测试用例过程中,后面有一些疑惑,就是元数据那边已触发元数据更新,但是bootstrap没有同步过来,元数据中没有看到springCloud的,不过今天重新测试,又没有复现出该问题,今天的主要目的是想了解soul的配置数据从admin同步到bootstrap的过程。
  按照官网介绍,soul配置数据有三种同步方式,分别是websocket、zookeeper以及http长轮询,本文仅研究websocket同步。过程如下:
分别启动admin和bootstrap,bootstrap后台发现如下日志:

 INFO 45220 --- [           main] b.s.s.d.w.WebsocketSyncDataConfiguration : you use websocket sync soul data.......
 INFO 45220 --- [           main] o.d.s.p.s.d.w.WebsocketSyncDataService   : websocket connection is successful.....

admin后台发现如下日志:

INFO 44624 --- [0.0-9095-exec-3] o.d.s.a.l.websocket.WebsocketCollector   : websocket on open successful....

表示admin和bootstrap的websoket链接已建立。admin是作为配置数据提供方,应该是websocket服务端,先从admin分析,然后再分析客户端bootstrap。

admin端分析(websocket服务端)

  1. 根据上述admin日志提示,可以找到WebsocketCollector类,这个类用@ServerEndpoint("/websocket")注解成websocket服务端,他对应配置类为DataSyncConfiguration,配置如下:
 /**
     * The WebsocketListener(default strategy).
     */
    @Configuration
    @ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
    @EnableConfigurationProperties(WebsocketSyncProperties.class)
    static class WebsocketListener {

        /**
         * Config event listener data changed listener.
         *
         * @return the data changed listener
         */
        @Bean
        @ConditionalOnMissingBean(WebsocketDataChangedListener.class)
        public DataChangedListener websocketDataChangedListener() {
            return new WebsocketDataChangedListener();
        }

        /**
         * Websocket collector websocket collector.
         *
         * @return the websocket collector
         */
        @Bean
        @ConditionalOnMissingBean(WebsocketCollector.class)
        public WebsocketCollector websocketCollector() {
            return new WebsocketCollector();
        }

        /**
         * Server endpoint exporter server endpoint exporter.
         *
         * @return the server endpoint exporter
         */
        @Bean
        @ConditionalOnMissingBean(ServerEndpointExporter.class)
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }

可以通过yml来配置是否启用websocket同步。

  1. WebsocketCollector推送配置数据给bootstrap,是通过如下的send函数进行同步:
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);
                }
            }
        }
    }

调试的时候,可以通过添加日志,打印出server端推送了什么内容给客户端。

  1. 追溯send函数上游的调用情况,可以找到DataChangedEventDispatcher类的onApplicationEvent函数这里监听了各种配置数据的变化并进行通告,该函数具体如下:
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());
            }
        }
    }
  1. 对应的变化通告发送方是类型为ApplicationEventPublisher的一个publisher,根据元数据操作的不同,在不同地方发送通告,比如启用/禁用元数据,对应在MetaDataServiceImpll类的enabled函数中(可断点验证),对应代码如下:
public String enabled(final List<String> ids, final Boolean enabled) {
        List<MetaData> metaDataList = Lists.newArrayList();
        for (String id : ids) {
            MetaDataDO metaDataDO = metaDataMapper.selectById(id);
            if (Objects.isNull(metaDataDO)) {
                return AdminConstants.ID_NOT_EXIST;
            }
            metaDataDO.setEnabled(enabled);
            metaDataMapper.updateEnable(metaDataDO);
            metaDataList.add(MetaDataTransfer.INSTANCE.mapToData(metaDataDO));
        }
        // publish change event.
        eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, DataEventTypeEnum.UPDATE,
                metaDataList));
        return StringUtils.EMPTY;
    }

通告eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.META_DATA, DataEventTypeEnum.UPDATE, metaDataList))函数,将元数据启用/禁用的事件通告出去。

bootstrap分析(websocket客户端)

  1. 在WebsocketSyncDataService类中,可以找到websocket客户端启动的初始化情况,代码如下:
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);
        }

    }

这里根据不同的url启动不同的客户端,url可以在yml配置文件中进行设置,本地测试环境中,yml配置如下:

soul :
    sync:
        websocket :
             urls: ws://localhost:9095/websocket

这里的url对应的就是websocket服务端的url(多个url可以逗号分隔)。

  1. 从上一步初始化代码中,我们知道websoket客户端类为 SoulWebsocketClient,进入该类,发现如下接收函数:
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);
    }

在这里接收服务端推送的配置信息
3. 在上述excutor打断点不断往下走,最后走到MetaDataCache缓存类,调用如下函数更新缓存:

public void cache(final MetaData data) {
        META_DATA_MAP.put(data.getPath(), data);
    }

后记

本篇仅针对配置数据的websocket同步通道进行跟踪和分析,另外一些同步的具体策略还没有具体分析,比如websocket断掉如何再次同步等,后续有待继续研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值