之前对这块只知道个大概,今天把这里串一下,为后续分析插件做一些准备
一、看之前的一些印象
- 项目启动会初始化加载可以加载的所有的插件(pom.xml配置了starter的插件)
- SoulWebHandler、SoulConfiguration、DefaultSoulPluginChain 是关键的几个类/接口
- 责任链模式,挨个对加载的插件进行逻辑处理
- 每个插件可以从管理web端设置开关,而且是即时生效的
- 按之前几篇的分析,相关的服务注册配置信息(plugin、auth、metadata)已经缓存至各个map、类的属性值,供插件使用
- 请求网关的入口在soul-web里的SoulWebHandler
二、插件的初始化流程
- 从日志看起
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[global] [org.dromara.soul.plugin.global.GlobalPlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[sign] [org.dromara.soul.plugin.sign.SignPlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[waf] [org.dromara.soul.plugin.waf.WafPlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[rate_limiter] [org.dromara.soul.plugin.ratelimiter.RateLimiterPlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[hystrix] [org.dromara.soul.plugin.hystrix.HystrixPlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[resilience4j] [org.dromara.soul.plugin.resilience4j.Resilience4JPlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[divide] [org.dromara.soul.plugin.divide.DividePlugin]
2021-01-29 00:09:53.366 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[springCloud] [org.dromara.soul.plugin.springcloud.SpringCloudPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[webClient] [org.dromara.soul.plugin.httpclient.WebClientPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[divide] [org.dromara.soul.plugin.divide.websocket.WebSocketPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[alibaba-dubbo-body-param] [org.dromara.soul.plugin.alibaba.dubbo.param.BodyParamPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[sofa] [org.dromara.soul.plugin.sofa.SofaPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[dubbo] [org.dromara.soul.plugin.alibaba.dubbo.AlibabaDubboPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[monitor] [org.dromara.soul.plugin.monitor.MonitorPlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[response] [org.dromara.soul.plugin.httpclient.response.WebClientResponsePlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[response] [org.dromara.soul.plugin.sofa.response.SofaResponsePlugin]
2021-01-29 00:09:53.367 INFO 8636 --- [ main] o.d.s.w.configuration.SoulConfiguration : loader plugin:[response] [org.dromara.soul.plugin.alibaba.dubbo.response.DubboResponsePlugin]
- 找到日志打印处,打个断点
@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("loader plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
return new SoulWebHandler(soulPlugins);
}
- ObjectProvider说明,之前没用过
A variant of {@link ObjectFactory} designed specifically for injection points,allowing for programmatic optionality and lenient not-unique handling.
一个ObjectFactory的变体,专门用来对注入点进行处理,允许一些编程的可选择型和宽松非一致性处理。
查了一下,可以理解为对ObjectFactory接口的扩展,有一些常用的方法,例如getIfAvailable,返回被object factory管理的实例。
- SoulPlugin::getOrder 再看一下这个根据order进行排序,插件的执行从功能上来说是要有顺序的,功能上猜测比如先鉴权/流控等,再重写/转发等,最后才是协议类插件,也就是我之前总结的那张结构图
各个XxxxSoulPlugin中重写了getOrder()方法
@Override
public int getOrder() {
return PluginEnum.DIVIDE.getCode();
}
具体枚举的值为,也对应下面加载的顺序
public enum PluginEnum {
GLOBAL(1, 0, "global"),
SIGN(2, 0, "sign"),
WAF(10, 0, "waf"),
RATE_LIMITER(20, 0, "rate_limiter"),
REWRITE(30, 0, "rewrite"),
REDIRECT(40, 0, "redirect"),
HYSTRIX(45, 0, "hystrix"),
SENTINEL(45, 0, "sentinel"),
RESILIENCE4J(45, 0, "resilience4j"),
DIVIDE(50, 0, "divide"),
SPRING_CLOUD(50, 0, "springCloud"),
WEB_SOCKET(55, 0, "webSocket"),
DUBBO(60, 0, "dubbo"),
SOFA(60, 0, "sofa"),
MONITOR(80, 0, "monitor"),
RESPONSE(100, 0, "response");
}
- 具体到new SoulWebHandler(soulPlugins)
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();
}
}
中间有个Runtime.getRuntime().availableProcessors(),返回值为jvm虚拟机可用核心数,根据机器的配置进行动态调整固定线程数的大小,为了保证性能,启动最少16个固定线程。
我的电脑是8核的,这里8*2+1 =17个线程数、如果是服务器的话这块的线程数会变得多些。
- 如下所示,共计初始化了17个插件
找一个熟悉的插件进行分析,DividePlugin, 实现了SoulPlugin的接口
public class DividePlugin extends AbstractSoulPlugin implements SoulPlugin{}
- DividePlugin如何被注入的,来源于DividePluginConfiguration
@Configuration
public class DividePluginConfiguration {
/**
* init dividePlugin.
*
* @return {@linkplain DividePlugin}
*/
@Bean
public SoulPlugin dividePlugin() {
return new DividePlugin();
}
}
- DividePluginConfiguration 又来源于soul-spring-boot-starter-plugin-divide里的spring.factories
其它插件类似,不做阐述
三、HTTP请求的处理流程
- 引入divide的starter,开启divide插件(默认具备)
- 启动SoulTestHttpApplicatioin服务
- Postman发起一个HTTP请求
- 在SoulWebHandler里的handle方法处打断点
@Override
public 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就是上面初始化的17个插件,和固定线程池
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
因为handle的方法是重写的,这个方法的定义来源于 spring-web里
public interface WebHandler {
/**
* Handle the web server exchange.
* 处理的web服务的请求
*/
Mono<Void> handle(ServerWebExchange exchange);
}
- new DefaultSoulPluginChain(plugins)-初始化插件链,
- 使用初始化的固定线程池去执行execute方法
@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
//webflux的循环执行
return Mono.defer(() -> {
if (this.index < plugins.size()) {
SoulPlugin plugin = plugins.get(this.index++);
// 如果skip为ture就不会被执行
Boolean skip = plugin.skip(exchange);
if (skip) {
return this.execute(exchange);
}
return plugin.execute(exchange, this);
}
return Mono.empty();
});
}
default Boolean skip(ServerWebExchange exchange) {
return false;
}
skip()方法判断请求的的exchane的协议,根据协议过滤到一些不必要的插件excute
- 执行到了AbstractSoulPlugin的execute里
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
//BaseDataCache获取之前同步的插件的缓存数据
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 CheckUtils.checkSelector(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
if (PluginEnum.WAF.getName().equals(pluginName)) {
return doExecute(exchange, chain, null, null);
}
return CheckUtils.checkSelector(pluginName, exchange, chain);
}
if (selectorData.getLoged()) {
log.info("{} selector success match , selector name :{}", pluginName, selectorData.getName());
}
//拿到所有的rule
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
if (PluginEnum.WAF.getName().equals(pluginName)) {
return doExecute(exchange, chain, null, null);
}
return CheckUtils.checkRule(pluginName, exchange, chain);
}
//匹配rule
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return CheckUtils.checkRule(pluginName, exchange, chain);
}
if (rule.getLoged()) {
log.info("{} rule success match ,rule name :{}", pluginName, rule.getName());
}
//根据上面的一些列匹配出的数据,执行具体的doExcecute方法
return doExecute(exchange, chain, selectorData, rule);
}
// 执行下一个excute方法
return chain.execute(exchange);
}
后面就是具体的插件的执行逻辑了
四、小结
-
插件初始化
- pom文件中引入相关的插件starter
- 在每个插件的PluginConfiguration里进行注入
- soulWebHandler中使用ObjectProvider的方法获取注入的所有PluginConfiguration bean
- 线程池固定线程数有一些优化
- 根据order顺序加载插件,待请求时处理
-
数据请求流程
- 引入divide的starter,开启divide插件(默认具备)
- 启动SoulTestHttpApplicatioin服务
- Postman发起一个HTTP请求
- SoulWebHandler里的handle为入口
- 初始化插件链
- 使用初始化的固定线程池去执行excute方法,可有skip一些插件
- 插件链循环执行AbstractSoulPlugin的execute,判断开关,然后匹配selector、rule,分发到具体的插件进行处理