Apache-shenYu源码阅读03-websocket同步数据原理讲解 第二章

331 阅读4分钟

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类做了什么?
  1. 因为继承于WebSocketClient,所以初始化时创建了与server端的连接
  2. 创建WebsocketDataHandler数据处理类
  3. 实现了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,发现所有数据处理方法都是走的抽象类的实现DataHandlerhandle方法,通过方法我们基本已经知晓了在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步(不粘贴了)。