soul网关系列(十三):插件的初始化和请求处理流程

本文详细介绍了Soul框架中插件的初始化过程,包括通过pom.xml配置加载插件、SoulConfiguration的加载逻辑、SoulWebHandler的插件链构造及线程池配置。在HTTP请求处理流程部分,分析了从接收到请求到通过插件链执行的步骤,涉及选择器和规则匹配等关键环节。通过对DividePlugin的实例分析,揭示了插件如何参与处理请求的过程。
摘要由CSDN通过智能技术生成

之前对这块只知道个大概,今天把这里串一下,为后续分析插件做一些准备

一、看之前的一些印象

  • 项目启动会初始化加载可以加载的所有的插件(pom.xml配置了starter的插件)
  • SoulWebHandler、SoulConfiguration、DefaultSoulPluginChain 是关键的几个类/接口
  • 责任链模式,挨个对加载的插件进行逻辑处理
  • 每个插件可以从管理web端设置开关,而且是即时生效的
  • 按之前几篇的分析,相关的服务注册配置信息(plugin、auth、metadata)已经缓存至各个map、类的属性值,供插件使用
  • 请求网关的入口在soul-web里的SoulWebHandler

二、插件的初始化流程

  1. 从日志看起
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]
  1. 找到日志打印处,打个断点
    @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");
}
  1. 具体到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个线程数、如果是服务器的话这块的线程数会变得多些。
在这里插入图片描述

  1. 如下所示,共计初始化了17个插件

在这里插入图片描述找一个熟悉的插件进行分析,DividePlugin, 实现了SoulPlugin的接口

public class DividePlugin extends AbstractSoulPlugin implements SoulPlugin{}
  1. DividePlugin如何被注入的,来源于DividePluginConfiguration
@Configuration
public class DividePluginConfiguration {

    /**
     * init dividePlugin.
     *
     * @return {@linkplain DividePlugin}
     */
    @Bean
    public SoulPlugin dividePlugin() {
        return new DividePlugin();
    }
}
  1. DividePluginConfiguration 又来源于soul-spring-boot-starter-plugin-divide里的spring.factories

在这里插入图片描述其它插件类似,不做阐述

三、HTTP请求的处理流程

  1. 引入divide的starter,开启divide插件(默认具备)
  2. 启动SoulTestHttpApplicatioin服务
  3. Postman发起一个HTTP请求
  4. 在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);
}
  1. new DefaultSoulPluginChain(plugins)-初始化插件链,
  2. 使用初始化的固定线程池去执行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
在这里插入图片描述

  1. 执行到了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,分发到具体的插件进行处理
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值