Soul网关源码阅读(九)插件配置加载初探
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网关源码阅读(九)插件配置加载初探相关推荐
- Soul网关源码阅读(八)路由匹配初探
Soul网关源码阅读(八)路由匹配初探 简介 今日看看路由的匹配相关代码,查看HTTP的DividePlugin匹配 示例运行 使用HTTP的示例,运行Soul-Admin,Sou ...
- Soul网关源码阅读(十)自定义简单插件编写
Soul网关源码阅读(十)自定义简单插件编写 简介 综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长 编写准备 首先我们先探究一下,一个P ...
- Soul网关源码阅读(七)限流插件初探
Soul网关源码阅读(七)限流插件初探 简介 前面的文章中对处理流程探索的差不多了,今天来探索下限流插件:resilience4j 示例运行 环境配置 启动下MySQL和redis d ...
- Soul网关源码阅读番外篇(一) HTTP参数请求错误
Soul网关源码阅读番外篇(一) HTTP参数请求错误 共同作者:石立 萧 * 简介 在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索 ...
- Soul 网关源码阅读(四)Dubbo请求概览
Soul 网关源码阅读(四)Dubbo请求概览 简介 本次启动一个dubbo服务示例,初步探索Soul网关源码的Dubbo请求处理流程 示例运行 环境配置 在Soul源码clone下来 ...
- Soul 网关源码阅读(六)Sofa请求处理概览
Soul 网关源码阅读(六)Sofa请求处理概览 简介 今天来探索一下Sofa请求处理流程,看看和前面的HTTP.Dubbo有什么异同 Sofa示例运行 PS:如果请求加上参数运行不成功,请更 ...
- Soul网关源码阅读(六)请求类型探索
Soul网关源码阅读(六)请求类型探索 简介 在上几篇文章中分析了请求的处理流程,HTTP和RPC请求处理是互斥的,通过请求类型来判断,这篇文章来探索下请求类型的前世今生 源码分析 通 ...
- Soul 网关源码阅读(三)请求处理概览
Soul 源码阅读(三)请求处理概览 简介 基于上篇:Soul 源码阅读(二)代码初步运行的配置,这次debug下请求处理的大致路径,验证网关模型的路径 详细流程记录 查看运行日志,寻找切入点 ...
- Soul 网关源码阅读(一) 概览
Soul 源码阅读(一) 概览 简介 阅读soul的官方文档,大致了解soul的功能和相关概念 心得 需要对网关的功能有个大致的了解,把soul官方文档读两遍(第一遍通读,能看懂多少是 ...
最新文章
- 递归c语言字符串最小编辑距离,算法设计与分析-编辑距离问题
- Thumbnailator-图片处理的Google开源Java类库
- 一文带你领略JS中原型链的精妙设计!
- C语言经典算法100例-002-数轴的使用
- Tomcat8.5.40启动后一直卡在日志At least one JAR was scanned for TLDs yet contained no TLDs
- 由System.getProperty(user.dir)引发的联想
- 技术人的未来(三)——红海与蓝海
- navicat导入.mdf文件到远程sqlserver数据库
- 强力推荐一款游戏十分好玩
- [鼠标指针][仅需1步]宝藏的猫咪Cat老师[win10/11][点击看更多免费]......
- spark.reducer.maxReqsInFlight和spark.reducer.maxBlocksInFlightPerAddress
- 一级消防工程师【技术实务】(爆炸)
- android按键静音键功能实现
- API接口管理平台eoLinker-AMS V3.2.0
- python绘制多个散点图_绘制多个散点图熊猫
- 通过JS代码简单实现九九乘法表
- 计算机组成原理简单模型机实验,CPU 与简单模型机设计实验
- 前端 查看页面在不同分辨率下效果
- weixuan -奥利给turtle
- mysql单机qps能到多少,单机Qps上限是多少?
热门文章
- HTML5系列(2)--ol列表的新属性
- ComponentArt.web.ui中文帮助之Grid(六)
- VS2012解决方案的设置
- 如何解决“There is no locally stored library”的问题
- mysql 1053错误,无法启动的解决方法
- 【报告分享】2021年中国互联网保险消费者洞察报告.pdf(附下载链接)
- SIGIR 2020 | 知识图谱上推荐推理的模仿学习框架
- 综述!信息检索中的花式预训练
- php sql慢查询,一个用户SQL慢查询分析,原因及优化_MySQL
- spring boot 邮件端口_springboot集成qq邮件发送功能