Soul网关源码阅读(七)- 数据同步机制原理及websocket同步源码解读

学习目标:

熟悉 soul 的数据同步机制中的 websocket 同步

学习内容:

1.什么是数据同步:将admin配置数据同步到soul集群中的jvm内存里,是网管高性能的关键;soul支持 websocket 同步(默认方式,推荐)、zookeeper 同步、http 长轮询同步、nacos 同步四种数据同步机制。

本文主要学习websocket 同步原理及策略

学习时间:2021年1月21号

学习产出:

  1. 数据同步原理
    soul网管启动时,会从配置服务同步配置数据,并支持推拉模式配置变更信息,并且更新本地缓存。管理员在管理后台,变更用户、规则、插件、流量配置,通过推拉模式将变更信息同步给soul网关,具体是push模式,还是pull模式取决于配置。
    在这里插入图片描述
    如下图所示,admin在用户配置发生变更时,会通过EventPublisher发出配置变更通知,由EventDispatcher处理该变更通知,然后根据配置的同步策略(http, websocket, zookeeper),将配置发送给对应的事件处理。
    webSocket同步策略,将变更后数据主动推送给soul-web,并且在网管层,会有对应的WebsocketCacheHandler处理器来处理admin的数据推送。
    在这里插入图片描述

  2. websocket同步策略-配置
    在 soul-bootstrap 项目的 pom.xml 文件中引入了 soul-spring-boot-starter-sync-data-websocket 这个 starter 。

<dependency>
    <groupId>org.dromara</groupId>
    <artifactId>soul-spring-boot-starter-sync-data-websocket</artifactId>
    <version>${project.version}</version>
</dependency>

在 soul-bootstrap 项目的 application-local.yml 文件中,配置了

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

soul-admin 配置
在 soul-admin 项目中的 application.yml 文件中默认配置了 websocket.enabled=true 的配置

soul:
  sync:
    websocket:
      enabled: true
  1. websocket同步策略-原理
    从 soul-bootstrap 开始追踪
    WebsocketSyncDataConfiguration 这个类加载了 soul.sync.websocket 这段配置。该类创建了 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));
    }

在 WebsocketSyncDataService 中创建了 webSocket 客户端,并和 soul-admin 创建的 websocket 服务建立了连接。开启一个异步线程每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));
        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);
        }

    }

从 soul-admin 开始追踪
DataSyncConfiguration 这个类加载了soul.sync.websocket.enabled配置;初始化WebsocketDataChangedListener,WebsocketCollector及ServerEndpointExporter 三个bean

DataChangedEventDispatcher 监听事件类型:

@Override
    @SuppressWarnings("unchecked")
    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());
            }
        }
    }

WebsocketDataChangedListener 监听 PluginChange, SelectorChanged, RuleChange
AppAuthChanged,MetaDataChanged 以上五个数据源的变化;
下面追踪onSelectorChanged 数据流转:

public void onSelectorChanged(final List<SelectorData> selectorDataList, final DataEventTypeEnum eventType) {
        WebsocketData<SelectorData> websocketData =
                new WebsocketData<>(ConfigGroupEnum.SELECTOR.name(), eventType.name(), selectorDataList);
        WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
    }

在这里插入图片描述
调用WebsocketCollector.send 方法

WebsocketDataHandler 监听到事件类型

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

触发 PluginDataSubscriber.onSelectorSubscribe事件

  1. BaseDataCache.getInstance().cacheSelectData(selectorData); 写入本地map中
  2. DividePluginDataHandler.handlerSelector selectorData 分别写入本地MAP:UPSTREAM_MAP和UPSTREAM_MAP_TEMP
public void submit(final SelectorData selectorData) {
        final List<DivideUpstream> upstreamList = GsonUtils.getInstance().fromList(selectorData.getHandle(), DivideUpstream.class);
        if (null != upstreamList && upstreamList.size() > 0) {
            UPSTREAM_MAP.put(selectorData.getId(), upstreamList);
            UPSTREAM_MAP_TEMP.put(selectorData.getId(), upstreamList);
        } else {
            UPSTREAM_MAP.remove(selectorData.getId());
            UPSTREAM_MAP_TEMP.remove(selectorData.getId());
        }
    }

admin后台修改数据,规则变更
DataChangedEventDispatcher 监听到数据变化
触发WebsocketDataChangedListener.onRuleChanged
WebsocketCollector.send

for (Session session : SESSION_SET) {
                try {
                    session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    log.error("websocket send result is exception: ", e);
                }
            }

WebsocketDataHandler 监听到事件调用executor 处理更新本地缓存信息

public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
        ENUM_MAP.get(type).handle(json, eventType);
    }

问题点:
UpstreamCacheManager 设计

public void submit(final SelectorData selectorData) {
        final List<DivideUpstream> upstreamList = GsonUtils.getInstance().fromList(selectorData.getHandle(), DivideUpstream.class);
        if (null != upstreamList && upstreamList.size() > 0) {
            UPSTREAM_MAP.put(selectorData.getId(), upstreamList);
            UPSTREAM_MAP_TEMP.put(selectorData.getId(), upstreamList);
        } else {
            UPSTREAM_MAP.remove(selectorData.getId());
            UPSTREAM_MAP_TEMP.remove(selectorData.getId());
        }
    }

为什么要用两个map存储相同的值
仅仅是为了下面的遍历吗:

private void scheduled() {
        if (UPSTREAM_MAP.size() > 0) {
            UPSTREAM_MAP.forEach((k, v) -> {
                List<DivideUpstream> result = check(v);
                if (result.size() > 0) {
                    UPSTREAM_MAP_TEMP.put(k, result);
                } else {
                    UPSTREAM_MAP_TEMP.remove(k);
                }
            });
        }
    }

这样的化UPSTREAM_MAP 里会存储所有的key
删除的时候是不是应该吧这个也删除呢?

 public void removeByKey(final String key) {
        UPSTREAM_MAP_TEMP.remove(key);
    }

猜你喜欢

转载自blog.csdn.net/koutann2015/article/details/112955745