(八)soul源码的websocket同步数据源码分析(下)

(八) soul源码的websocket同步数据源码分析(下)

目标

第七小节主要分析了soul-admin通过websocket发送数据
本小节主要分析soul-bootstrap怎么处理websocket发来的数据

soul-sync-data-websocket包

如下图
在这里插入图片描述

  • SoulWebsocketClient
  • WebsocketConfig
  • DataHandler的实现类和抽象类
  • WebsocketSyncDataService

websocket接收发来的数据

websocketDataHandler接收发来的消息onMessage,再传递到handleResult方法,

    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);
    }

websocketDataHandler

默认的构造函数把AbstractDataHandler继承类初始化存在EnumMap对象中

  • AbstractDataHandler的继承类:PluginDataHandler 插件处理器, SelectorDataHandler选择器处理器, RuleDataHandler 规则处理器,AuthDataHandler权限处理器 ,MetaDataHandler
  • ENUM_MAP.get(type).handle(json, eventType):根据ENUM_MAP类型去调用不同的处理器,
    private static final EnumMap<ConfigGroupEnum, DataHandler> ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);

    /**
     * Instantiates a new Websocket data handler.
     *
     * @param pluginDataSubscriber the plugin data subscriber
     * @param metaDataSubscribers  the meta data subscribers
     * @param authDataSubscribers  the auth data subscribers
     */
    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));
    }
 
   //根据type具体哪个模板方法
   public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
        ENUM_MAP.get(type).handle(json, eventType);
    }

接着上一步分析:我们走到AbstractDataHandler类的handler方法,由于AbstractDataHandlerdo是个抽象类,具体调用的它的继承类处理方式(doRefresh,doUpdate,doDelete),这里巧妙的使用模板设计模式

  • doRefresh,缓存初始化或更新
  • doUpdate, 更新缓存或具体协议处理
  • doDelete 清空缓存
    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;
            }
        }
    }

这里我们分析:doUpdate(dataList)

SelectorDataHandler

    @Override
    protected void doUpdate(final List<SelectorData> dataList) {
        dataList.forEach(pluginDataSubscriber::onSelectorSubscribe);
    }

pluginDataSubscriber::onSelectorSubscribe
CommonPluginDataSubscriber的onSelectorSubscribe


  @Override
     public void onSelectorSubscribe(final SelectorData selectorData) {
         subscribeDataHandler(selectorData, DataEventTypeEnum.UPDATE);
     }

CommonPluginDataSubscriber的subscribeDataHandler
subscribeDataHandler方法是核心: 修改缓存 BaseDataCache: Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.put(data.getName(), data));
BaseDataCache: 中存放了三个ConcurrentMap,缓存了注册上来的插件、选择器、规则的数据,这就是为什么soul高性能的其中一个重要优化,配置不从数据库、redis、其他配置中心拉去数据,
配置基于JVM缓存操作,性能非常快

    private static final ConcurrentMap<String, PluginData> PLUGIN_MAP = Maps.newConcurrentMap();
    
    private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();
    
    private static final ConcurrentMap<String, List<RuleData>> RULE_MAP = Maps.newConcurrentMap();

根据插件名称,找到插件处理器 PluginDataHandler 处理选择器的更新数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-764CB2p0-1611398167199)(…/soul/png/handlePlugin.png “handlePlugin”)]


    private <T> void subscribeDataHandler(final T classData, final DataEventTypeEnum dataType) {
        Optional.ofNullable(classData).ifPresent(data -> {
            if (data instanceof PluginData) {
                PluginData pluginData = (PluginData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    // 修改缓存 BaseDataCache: Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.put(data.getName(), data));
                    BaseDataCache.getInstance().cachePluginData(pluginData);
                    // 根据插件名称,找到插件处理器 PluginDataHandler 处理选择器的更新数据
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removePluginData(pluginData);
                    Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.removePlugin(pluginData));
                }
            } else if (data instanceof SelectorData) {
                SelectorData selectorData = (SelectorData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeSelectData(selectorData);
                    Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.removeSelector(selectorData));
                }
            } else if (data instanceof RuleData) {
                RuleData ruleData = (RuleData) data;
                if (dataType == DataEventTypeEnum.UPDATE) {
                    BaseDataCache.getInstance().cacheRuleData(ruleData);
                    Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
                } else if (dataType == DataEventTypeEnum.DELETE) {
                    BaseDataCache.getInstance().removeRuleData(ruleData);
                    Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.removeRule(ruleData));
                }
            }
        });
    }

使用websocket同步的时候,特别要注意断线重连,也叫保持心跳

多个soul-admin实例,初始化多个WebSocketClient,onOpen和onMessage接收websocket发了来的数据,每个soul-admin实例,存在一个调度线程30秒进行一次断线重连

    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));
        //多个soul-admin实例,初始化多个WebSocketClient,onOpen和onMessage接收websocket发了来的数据

        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 {
            //每个soul-admin实例,存在一个调度线程,去进行断线重连
            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.....");
                }
                //使用调度线程池进行断线重连,30秒进行一次
                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);
        }

    }

scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
上面的四个参数进行讲解:
第一个command参数是任务实例,
第二个initialDelay参数是初始化延迟时间,
第三个period参数是间隔时间,
第四个unit参数是时间单元。

总结

  • soul的websocket接收数据处理,
  • 使用模板设计模式AbstractDataHandler,去处理不同handler
  • 使用了JVM缓存管理配置信息
  • websocket存在一个调度线程30秒进行一次断线重连
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: WebSocket是一种在Web应用程序中实现实时双向通信的技术。它通过一种持久化的连接机制,允许服务器主动将数据推送给客户端,而不需要客户端发起请求。下面是WebSocket源码解析。 WebSocket源码主要分为服务器端和客户端两部分。 在服务器端,WebSocket源码主要包含以下几个核心类: 1. ServerSocket:用于监听并接受客户端的连接请求。它通过创建ServerSocket实例并指定端口来启动WebSocket服务器。 2. Socket:用于与客户端进行通信。当ServerSocket接受到客户端的连接请求后,会创建一个Socket实例来处理与该客户端的通信。 3. WebSocketHandler:负责解析和处理客户端发送的WebSocket请求,以及发送响应给客户端。它会使用WebSocket协议将数据进行封装和解封装。 4. WebSocketFrame:用于表示WebSocket消息帧。它包含了消息类型、消息长度以及消息内容等信息。 在客户端,WebSocket源码主要包含以下几个核心类: 1. WebSocket:用于与服务器端进行通信。它通过创建WebSocket实例来连接服务器,并发送和接收消息。 2. WsClient:负责处理WebSocket连接的建立和断开,以及发送和接收消息的操作。 3. Connection:表示与服务器端的连接。它通过创建Socket实例来建立与服务器的连接,并负责处理与服务器的通信。 在WebSocket源码中,需要注意以下几个要点: 1. WebSocket协议:WebSocket协议是基于HTTP协议的,通过在握手阶段进行协议升级,实现从HTTP到WebSocket的切换。在源码中需要解析和处理WebSocket协议。 2. 数据压缩:WebSocket支持将数据进行压缩,以减少传输的数据量。源码中需要实现数据压缩和解压缩的功能。 3. 安全性:WebSocket可以通过SSL/TLS协议进行加密以保证数据的安全性。在源码中需要支持SSL/TLS的加密和解密操作。 总结:WebSocket源码实现了服务器端和客户端之间的实时双向通信。通过WebSocket协议和各种实用功能的支持,实现了高效的数据传输和安全的通信。 ### 回答2: WebSocket 是一种基于 TCP 协议的全双工通信协议,它可以在客户端和服务器之间建立持久连接,实现实时的双向通信。 WebSocket 协议相较于传统的 HTTP 协议具备以下几个显著特点: 1. 双向通信:WebSocket 具备客户端和服务器之间的全双工通信能力,两者可以同时发送和接收数据。 2. 实时性:WebSocket 的连接一旦建立起来,可以保持长时间的持久连接,避免了 HTTP 协议每次请求都需要重新建立连接的开销。这样就可以实现实时性要求较高的应用场景,比如聊天室、股票行情等。 3. 更少的数据传输:相较于 HTTP 协议,WebSocket数据帧头信息更小,且在数据传输时采用二进制格式,能够更高效地传输数据。 4. 跨域通信:WebSocket 协议支持跨域通信,客户端可以发起到不同域的服务器的连接请求,打破了浏览器同源策略的限制。 WebSocket源码实现可以参考不同编程语言的库或框架提供的实现。比如在 JavaScript 中,可以通过使用浏览器原生提供的 WebSocket API 来实现。 WebSocket 源码的实现主要包括以下几个关键步骤: 1. 建立连接:客户端通过 WebSocket 对象的构造函数创建一个 WebSocket 实例,并指定需要连接的服务器地址。客户端与服务器建立连接后,会进行握手协议,完成连接的建立。 2. 数据传输:连接建立成功后,客户端和服务器可以进行双向的数据传输。客户端可以通过发送消息的方法将数据发送给服务器,而服务器通过监听消息事件来接收客户端发送的数据。服务器也可以向客户端发送消息,客户端通过监听消息事件来接收服务器发送的数据。 3. 处理错误:在 WebSocket 连接过程中,如果发生错误,客户端和服务器都会触发错误事件以及对应的错误处理函数,从而进行错误处理。 4. 断开连接:当连接不再需要时,客户端或服务器可以主动关闭连接,释放相关资源。同时,也可以通过监听关闭事件来处理连接关闭的情况。 通过阅读 WebSocket 源码的实现,可以更深入地了解其中的细节及其底层的实现原理,从而更好地应用和优化 WebSocket 的相关开发。 ### 回答3: WebSocket是一种实时通信协议,用于在客户端和服务器之间建立双向通信的连接。它使用HTTP作为握手协议,并在握手成功后将连接升级为全双工通信通道。 WebSocket源码包括服务器端和客户端的实现。以下是一些常见的WebSocket源码实现细节: 1. 服务器端代码:服务器端的WebSocket源码主要包括HTTP握手和WebSocket连接的建立。服务器接收到客户端的WebSocket升级请求后,会进行一系列握手流程,并在握手成功后将连接升级为WebSocket连接。服务器端的源码通常会包括处理握手请求、解析数据帧、处理连接维护和消息处理等功能。 2. 客户端代码:客户端的WebSocket源码主要包括与服务器进行握手和维护WebSocket连接的工作。客户端建立WebSocket连接时,会向服务器发送HTTP握手请求并解析服务器的握手响应。握手成功后,客户端可以通过WebSocket连接与服务器进行实时通信。客户端的源码通常会包括握手请求发送、握手响应解析、数据帧封装与解析等功能。 3. 数据帧封装与解析:WebSocket数据帧是协议中的最基本通信单位。服务器和客户端在发送和接收数据时,会将数据封装成数据帧格式进行传输。数据帧的格式包括一些固定的头部信息和可变长度的负载数据源码中的数据帧封装和解析部分负责将待发送的数据封装成数据帧格式,以及在接收到数据帧后将其解析成可读的数据。 总结起来,WebSocket源码实现了协议的基本功能,包括协议握手、连接建立、数据帧封装与解析等。通过阅读WebSocket源码,可以更好地理解和学习WebSocket协议的实现原理和工作机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值