Soul网关源码分析-websocket数据同步

1. 背景介绍

1.1 为什么会有数据同步?
原因:由于网关是流量请求的入口,在微服务架构中承担了非常重要的角色,网关高可用的重要性不言而喻。在使用网关的过程中,为了满足业务诉求,经常需要变更配置,比如流控规则、路由规则等等。因此,网关动态配置是保障网关高可用的重要因素。那么,Soul 网关又是如何支持动态配置的呢?soul作为网关,为了提供更高的响应速度,所有的配置都缓存在JVM的Hashmap中,每次请求都走的本地缓存,速度非常快,目前soul支持websocket、zookeeper、http、nacos四种数据同步方式。【摘自】soul官网介绍

2. 数据同步原理图

2.1 数据同步流程

下图展示了 Soul 数据同步的流程,Soul 网关在启动时,会从从配置服务同步配置数据,并且支持推拉模式获取配置变更信息,并且更新本地缓存。而管理员在管理后台,变更用户、规则、插件、流量配置,通过推拉模式将变更信息同步给 Soul 网关,具体是 push 模式,还是 pull 模式取决于配置。关于配置同步模块,其实是一个简版的配置中心。【摘自】soul官网介绍

2.2 数据同步流程细化图

如下图所示,soul-admin 在用户发生配置变更之后,会通过 ApplicationEventPublisher 发出配置变更通知,由 DataChangedEventDispatcher 处理该变更通知,然后根据配置的同步策略(http、weboscket、zookeeper),将配置发送给对应的事件处理器,如果是 websocket 同步策略,则将变更后的数据主动推送给 soul-web,并且在网关层,会有对应的 WebsocketCacheHandler 处理器处理来处 admin 的数据推送。【摘自】soul官网介绍
数据同步处理流程

3. 示例运行

3.1 启动数据库

mysqld --console

3.2 Soul-Admin加上数据同步配置

soul:
  database:
    dialect: mysql
    init_script: "META-INF/schema.sql"
  #打开websocket数据同步
  sync:
    websocket:
      enabled: true

3.3 运行Soul-Admin

运行SoulAdminBootstrap

3.4 Soul-Bootstrap加上websocket同步方式配置

soul :
    file:
      enabled: true
    corss:
      enabled: true
    dubbo :
      parameter: multi
    #打开websocket数据同步
    sync:
        websocket :
             urls: ws://localhost:9095/websocket

3.5 运行Soul-Bootstrap

运行SoulBootstrapApplication

3.6 运行Soul-Example-http

运行Soul-Example-http工程,在启动时往soul-admin注册选择器规则数据,为后续源码分析铺底数据。

4. 源码分析

4.1 回顾请求转发处理核心类

在之前分析http、dubbo、springcloud等不同渠道接入soul后,请求转发都会经过AbstractSoulPlugin类进行选择器的选择以及选择器规则的匹配,主要代码如下:

public abstract class AbstractSoulPlugin implements SoulPlugin {
    ......
    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
        String pluginName = named();
        // 获取插件配置
        final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
        if (pluginData != null && pluginData.getEnabled()) {
            // 获取选择器
            final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
            .......
            // 获取规则
            final SelectorData selectorData = matchSelector(exchange, selectors);
            .......
        }
        return chain.execute(exchange);
    }
    ......
}

4.2 找出缓存数据操作核心类

从上述代码可以看出,Soul-Bootstrap在处理请求转发时,有三部分数据是从缓存中获取的:插件配置数据、选择器数据、规则数据,这些数据都是从 BaseDataCache中获取的,下面看看其核心代码:

public final class BaseDataCache {
    
    private static final BaseDataCache INSTANCE = new BaseDataCache();
    
    /**
     * pluginName -> PluginData.
     */
    private static final ConcurrentMap<String, PluginData> PLUGIN_MAP = Maps.newConcurrentMap();
    
    /**
     * pluginName -> SelectorData.
     */
    private static final ConcurrentMap<String, List<SelectorData>> SELECTOR_MAP = Maps.newConcurrentMap();
    
    /**
     * selectorId -> RuleData.
     */
    private static final ConcurrentMap<String, List<RuleData>> RULE_MAP = Maps.newConcurrentMap();

    public void cachePluginData(final PluginData pluginData) {
        Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.put(data.getName(), data));
    }

    public void removePluginData(final PluginData pluginData) {
        Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.remove(data.getName()));
    }
    
    public PluginData obtainPluginData(final String pluginName) {
        return PLUGIN_MAP.get(pluginName);
    }

    public void cacheSelectData(final SelectorData selectorData) {
        Optional.ofNullable(selectorData).ifPresent(this::selectorAccept);
    }

    public void removeSelectData(final SelectorData selectorData) {
        Optional.ofNullable(selectorData).ifPresent(data -> {
            final List<SelectorData> selectorDataList = SELECTOR_MAP.get(data.getPluginName());
            Optional.ofNullable(selectorDataList).ifPresent(list -> list.removeIf(e -> e.getId().equals(data.getId())));
        });
    }
    
    public List<SelectorData> obtainSelectorData(final String pluginName) {
        return SELECTOR_MAP.get(pluginName);
    }

    public void cacheRuleData(final RuleData ruleData) {
        Optional.ofNullable(ruleData).ifPresent(this::ruleAccept);
    }
    
    public void removeRuleData(final RuleData ruleData) {
        Optional.ofNullable(ruleData).ifPresent(data -> {
            final List<RuleData> ruleDataList = RULE_MAP.get(data.getSelectorId());
            Optional.ofNullable(ruleDataList).ifPresent(list -> list.removeIf(rule -> rule.getId().equals(data.getId())));
        });
    }

    public List<RuleData> obtainRuleData(final String selectorId) {
        return RULE_MAP.get(selectorId);
    }
}

4.3 进一步找出数据同步入口程序

从BaseDataCache的代码中可以得知以下信息:

  • 方法的命名可以大致看出基本都是对插件配置数据、选择器配置数据、规则配置数据进行的增删改查操作;
  • cacheSelectData和removeRuleData应该就是对应的更新和删除操作

下面来看下,cacheSelectData和removeRuleData方法到底在哪些地方被调用,使用IDEA快捷键(Alt+F7)查看用哪些地方进行了使用,对结果信息筛选后,排除了BaseDataCache类本身的调用和一些Junit测试类的调用,只有CommonPluginDataSubscriber这个类进行了调用,如下图所示:
调用关系1
这个类应该就是数据同步的核心类了,下面看看这个类的核心代码,如下:

public class CommonPluginDataSubscriber implements PluginDataSubscriber {
    .......
   
    @Override
    public void onSubscribe(final PluginData pluginData) {
        subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);
    }
    
    @Override
    public void unSubscribe(final PluginData pluginData) {
        subscribeDataHandler(pluginData, DataEventTypeEnum.DELETE);
    }

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

    @Override
    public void onRuleSubscribe(final RuleData ruleData) {
        subscribeDataHandler(ruleData, DataEventTypeEnum.UPDATE);
    }
    
    @Override
    public void unRuleSubscribe(final RuleData ruleData) {
        subscribeDataHandler(ruleData, DataEventTypeEnum.DELETE);
    }
    
    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.getInstance().cachePluginData(pluginData);
                    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));
                }
            }
        });
    }
}

可以看到,核心处理方法subscribeDataHandler被自身的onSubscribe、onSelectorSubscribe、onRuleSubscribe等方法调用,所以接着再通过alt+F7看看这些方法是被谁调用的,以onSubscribe为例查看调用关系,如下图:
调用关系2
从图中可以看到,基本上由以下四个模块调用:

  • soul-sync-data-http : PluginDataRefresh
  • soul-sync-data-nacos : NacosCacheHandler
  • soul-sync-data-websocket : PluginDataHandler
  • soul-sync-data-zookeeper : ZookeeperSyncDataService

到这里,可以知道应该重点关注soul-sync-data-websocket模块的PluginDataHandler这个类是如何处理的,包括doRefresh、doUpdate和doDelete方法。

5. 总结

从以上的分析可以得知,从源码的角度分析websocket数据同步,需要深入了解一下几个方面:

  • BaseDataCache : 配置缓存,提供更新、删除、查询;
  • CommonPluginDataSubscriber : 对BaseDataCache的封装,提供更新和删除;
  • soul-sync-data-websocket模块PluginDataHandler类是如何对;CommonPluginDataSubscriber这个类进行调用从而实现数据同步的。

参考资料

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值