Soul网关源码阅读(九)插件配置加载初探


简介

    今日来探索一下插件的初始化,及相关的配置的加载

源码Debug

插件初始化

    首先来到我们非常熟悉的插件链调用的类: SoulWebHandler ,在其中的 DefaultSoulPluginChain ,我们看到plugins是通过构造函数传入的

    我们来跟踪一下是谁传入的,在SoulWebHandler的public那一行打上断点,重启后跟踪调用栈

public final class SoulWebHandler implements WebHandler {private final List<SoulPlugin> plugins;// 在这里也看到plugins是通过构造函数传入的,在public这一行打上断点,然后重启public SoulWebHandler(final List<SoulPlugin> plugins) {this.plugins = plugins;String schedulerType = System.getProperty("soul.scheduler.type", "fixed");if (Objects.equals(schedulerType, "fixed")) {int threads = Integer.parseInt(System.getProperty("soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));scheduler = Schedulers.newParallel("soul-work-threads", threads);} else {scheduler = Schedulers.elastic();}}@Overridepublic Mono<Void> handle(@NonNull final ServerWebExchange exchange) {MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());// 传入 plugins 到 DefaultSoulPluginChainreturn new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler).doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));}private static class DefaultSoulPluginChain implements SoulPluginChain {private int index;private final List<SoulPlugin> plugins;// 通过构造函数得到 pluginsDefaultSoulPluginChain(final List<SoulPlugin> plugins) {this.plugins = plugins;}@Overridepublic Mono<Void> execute(final ServerWebExchange exchange) {return Mono.defer(() -> {if (this.index < plugins.size()) {SoulPlugin plugin = plugins.get(this.index++);Boolean skip = plugin.skip(exchange);if (skip) {return this.execute(exchange);}return plugin.execute(exchange, this);}return Mono.empty();});}}
}

    我这有点慢,重启等以后后,我们查看上一个调用,来到了 SoulConfiguration

    通过类的大致信息可以看到是Spring之类的加载机制。通过前面文章的分析中,大致应该就是配置了 auto configuration ,然后启动的时候Spring进行加载的

public class SoulConfiguration {@Bean("webHandler")public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);final List<SoulPlugin> soulPlugins = pluginList.stream().sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));return new SoulWebHandler(soulPlugins);}
}

    大致就知道是Spring自动加载的,通过AutoConfiguration机制,下面就是Spring相关了,这里就不继续跟踪了

配置初始化

    在上一篇的文章分析中,我们分析了路由匹配的大致细节,需要选择器和规则的配置,下面我们来跟踪一下这些配置的初始化

    首先进入路由匹配核心类: AbstractSoulPlugin ,其中进行 pluginData 、selectorData、rules的获取,如下面代码注释:

public abstract class AbstractSoulPlugin implements SoulPlugin {@Overridepublic 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);if (CollectionUtils.isEmpty(selectors)) {return handleSelectorIsNull(pluginName, exchange, chain);}final SelectorData selectorData = matchSelector(exchange, selectors);if (Objects.isNull(selectorData)) {return handleSelectorIsNull(pluginName, exchange, chain);}selectorLog(selectorData, pluginName);// 根据选择器的id,获取其规则数据final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());if (CollectionUtils.isEmpty(rules)) {return handleRuleIsNull(pluginName, exchange, chain);}RuleData rule;if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {rule = rules.get(rules.size() - 1);} else {rule = matchRule(exchange, rules);}if (Objects.isNull(rule)) {return handleRuleIsNull(pluginName, exchange, chain);}ruleLog(rule, pluginName);return doExecute(exchange, chain, selectorData, rule);}return chain.execute(exchange);}
}

    我们到这个类: BaseDataCache 看一下,看到它有两个数据的Map存储:插件数据、选择器数据、规则数据

public final class 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 final class BaseDataCache {public void cachePluginData(final PluginData pluginData) {Optional.ofNullable(pluginData).ifPresent(data -> PLUGIN_MAP.put(data.getName(), data));}
}

    在public这一行打上断点,重启,顺利进入上面的函数,我们查看下 pluginData ,还挺简单,大致如下

PluginData(id=1, name=sign, config=null, role=1, enabled=false)

    我们跟踪其调用栈,其调用链的类和函数如下,类在调用栈的顺序也是对应的

public class CommonPluginDataSubscriber implements PluginDataSubscriber {// 在这个里面触发 subscribeDataHandler , subscribeDataHandler 触发 DaseDataCache的调用@Overridepublic void onSubscribe(final PluginData pluginData) {subscribeDataHandler(pluginData, DataEventTypeEnum.UPDATE);}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));}}});}
}// 这个类中触发了上面单个插件的配置,并且这个类拿到了所有的插件数据
public class PluginDataHandler extends AbstractDataHandler<PluginData> {@Overrideprotected void doRefresh(final List<PluginData> dataList) {// 触发上面的调用pluginDataSubscriber.refreshPluginDataSelf(dataList);dataList.forEach(pluginDataSubscriber::onSubscribe);}
}// 继续跟下去,看看datalist的来源
public abstract class AbstractDataHandler<T> implements DataHandler {@Overridepublic 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;}}}
}// 来到了websocket,进行跟
public class WebsocketDataHandler {public void executor(final ConfigGroupEnum type, final String json, final String eventType) {ENUM_MAP.get(type).handle(json, eventType);}
}// 在这个类中,看到插件数据列表是从websocket来的,那就是从Soul-Admin那边获取来到的数据
public final class SoulWebsocketClient extends WebSocketClient {public void onMessage(final String result) {handleResult(result);}  @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);}
}

    看下result的大致内容:也就是一些简单的配置

{"groupType": "PLUGIN","eventType": "MYSELF","data": [{"id": "1","name": "sign","role": 1,"enabled": false}, {"id": "9","name": "hystrix","role": 0,"enabled": false}]
}

    结合路由匹配的关键函数,好像它的作用就是用来判断是否开启和根据name获得选择器

public abstract class AbstractSoulPlugin implements SoulPlugin {@Overridepublic Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {String pluginName = named();final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);// 用于判断是否开启if (pluginData != null && pluginData.getEnabled()) {.......}}
}

    插件数据这一块的流程定位下来比较轻松,毕竟前面流程也梳理差不多了

    选择器和规则数据的初始化流程,这里就不进行定位了,逻辑和插件数据基本一样,这里就不赘述了

总结

    今天分析了插件的初始化和相关配置的初始化

    插件的初始化是使用Spring的自动配置加载机制进行实现了,结合使用插件都需要引入哪些start的依赖

    插件数据、选择器数据、规则数据的初始化,都是从Websocket(或者同步通信模块)那边过来的,接收到数据以后,进行配置加载到本地

    我们还看到一些删除和更新相关的操作,这些属于后面的数据同步分析了,这里先放一放

Soul网关源码分析文章列表

Github

  • Soul源码阅读(一) 概览

  • Soul源码阅读(二)代码初步运行

  • Soul源码阅读(三)HTTP请求处理概览

  • Soul网关源码阅读(四)Dubbo请求概览

  • Soul网关源码阅读(五)请求类型探索

  • Soul网关源码阅读(六)Sofa请求处理概览

  • Soul网关源码阅读(七)限流插件初探

  • Soul网关源码阅读(八)路由匹配初探

  • Soul网关源码阅读番外篇(一) HTTP参数请求错误

掘金

  • Soul网关源码阅读(一) 概览

  • Soul网关源码阅读(二)代码初步运行

  • Soul网关源码阅读(三)请求处理概览

  • Soul网关源码阅读(四)Dubbo请求概览

  • Soul网关源码阅读(五)请求类型探索

  • Soul网关源码阅读(六)Sofa请求处理概览

  • Soul网关源码阅读(七)限流插件初探

  • Soul网关源码阅读(八)路由匹配初探

  • Soul网关源码阅读番外篇(一) HTTP参数请求错误

Soul网关源码阅读(九)插件配置加载初探相关推荐

  1. Soul网关源码阅读(八)路由匹配初探

    Soul网关源码阅读(八)路由匹配初探 简介      今日看看路由的匹配相关代码,查看HTTP的DividePlugin匹配 示例运行      使用HTTP的示例,运行Soul-Admin,Sou ...

  2. Soul网关源码阅读(十)自定义简单插件编写

    Soul网关源码阅读(十)自定义简单插件编写 简介     综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长 编写准备     首先我们先探究一下,一个P ...

  3. Soul网关源码阅读(七)限流插件初探

    Soul网关源码阅读(七)限流插件初探 简介     前面的文章中对处理流程探索的差不多了,今天来探索下限流插件:resilience4j 示例运行 环境配置     启动下MySQL和redis d ...

  4. Soul网关源码阅读番外篇(一) HTTP参数请求错误

    Soul网关源码阅读番外篇(一) HTTP参数请求错误 共同作者:石立 萧 * 简介     在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索 ...

  5. Soul 网关源码阅读(四)Dubbo请求概览

    Soul 网关源码阅读(四)Dubbo请求概览 简介     本次启动一个dubbo服务示例,初步探索Soul网关源码的Dubbo请求处理流程 示例运行 环境配置     在Soul源码clone下来 ...

  6. Soul 网关源码阅读(六)Sofa请求处理概览

    Soul 网关源码阅读(六)Sofa请求处理概览 简介     今天来探索一下Sofa请求处理流程,看看和前面的HTTP.Dubbo有什么异同 Sofa示例运行 PS:如果请求加上参数运行不成功,请更 ...

  7. Soul网关源码阅读(六)请求类型探索

    Soul网关源码阅读(六)请求类型探索 简介     在上几篇文章中分析了请求的处理流程,HTTP和RPC请求处理是互斥的,通过请求类型来判断,这篇文章来探索下请求类型的前世今生 源码分析     通 ...

  8. Soul 网关源码阅读(三)请求处理概览

    Soul 源码阅读(三)请求处理概览 简介     基于上篇:Soul 源码阅读(二)代码初步运行的配置,这次debug下请求处理的大致路径,验证网关模型的路径 详细流程记录 查看运行日志,寻找切入点 ...

  9. Soul 网关源码阅读(一) 概览

    Soul 源码阅读(一) 概览 简介     阅读soul的官方文档,大致了解soul的功能和相关概念 心得     需要对网关的功能有个大致的了解,把soul官方文档读两遍(第一遍通读,能看懂多少是 ...

最新文章

  1. 递归c语言字符串最小编辑距离,算法设计与分析-编辑距离问题
  2. Thumbnailator-图片处理的Google开源Java类库
  3. 一文带你领略JS中原型链的精妙设计!
  4. C语言经典算法100例-002-数轴的使用
  5. Tomcat8.5.40启动后一直卡在日志At least one JAR was scanned for TLDs yet contained no TLDs
  6. 由System.getProperty(user.dir)引发的联想
  7. 技术人的未来(三)——红海与蓝海
  8. navicat导入.mdf文件到远程sqlserver数据库
  9. 强力推荐一款游戏十分好玩
  10. [鼠标指针][仅需1步]宝藏的猫咪Cat老师[win10/11][点击看更多免费]......
  11. spark.reducer.maxReqsInFlight和spark.reducer.maxBlocksInFlightPerAddress
  12. 一级消防工程师【技术实务】(爆炸)
  13. android按键静音键功能实现
  14. API接口管理平台eoLinker-AMS V3.2.0
  15. python绘制多个散点图_绘制多个散点图熊猫
  16. 通过JS代码简单实现九九乘法表
  17. 计算机组成原理简单模型机实验,CPU 与简单模型机设计实验
  18. 前端 查看页面在不同分辨率下效果
  19. weixuan -奥利给turtle
  20. mysql单机qps能到多少,单机Qps上限是多少?

热门文章

  1. HTML5系列(2)--ol列表的新属性
  2. ComponentArt.web.ui中文帮助之Grid(六)
  3. VS2012解决方案的设置
  4. 如何解决“There is no locally stored library”的问题
  5. mysql 1053错误,无法启动的解决方法
  6. 【报告分享】2021年中国互联网保险消费者洞察报告.pdf(附下载链接)
  7. SIGIR 2020 | 知识图谱上推荐推理的模仿学习框架
  8. 综述!信息检索中的花式预训练
  9. php sql慢查询,一个用户SQL慢查询分析,原因及优化_MySQL
  10. spring boot 邮件端口_springboot集成qq邮件发送功能