Soul网关(十一)反应式编程&插件链调用源码分析

Soul 中的反应式编程

soul-bootstrap 引入了 webflux 依赖来实现反应式编程:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

soul-bootstrap通过soul-spring-boot-starter-gateway这个starter来启动网关,soul-spring-boot-starter-gateway依赖soul-websoul-web的自动配置类:SoulConfiguration中初始化了 webHandler, dispatcherHandler, pluginDataSubscriber

Soul-Bootstrap中初始化了一个 反应式 web 服务器:

		/**
     * 反应式web 服务器
     * @return the netty reactive web server factory
     */
    @Bean
    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
        NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();
        webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer());
        return webServerFactory;
    }

soul-web 从bean工厂加载所有插件并初始化 SoulWebHandler:

/**
 * 从bean工厂加载插件并初始化 SoulWebHandler.
 */
@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()));
    // 初始化 SoulWebHandler 并将插件加载到本地内存
    return new SoulWebHandler(soulPlugins);
}


/**
 * SoulWebHandler 构造函数
 */
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();
    }
}

SoulWebHandler 实现了 WebHandler 类的 handler() 方法,来处理 web 请求:

@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());
    // 从堆内存加载已初始化的插件,来处理web请求
    return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
            .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}

Soul 插件链调用源码分析

接着上面的handler()方法handler()方法从堆内存中加载出来的插件链 List 如下:

[
org.dromara.soul.plugin.global.GlobalPlugin@1cc8416a, org.dromara.soul.plugin.sign.SignPlugin@3bc69ce9, org.dromara.soul.plugin.waf.WafPlugin@6eb089e6, org.dromara.soul.plugin.ratelimiter.RateLimiterPlugin@58324c9f, org.dromara.soul.plugin.hystrix.HystrixPlugin@5d7d8613, org.dromara.soul.plugin.resilience4j.Resilience4JPlugin@abad89c, org.dromara.soul.plugin.divide.DividePlugin@331ff3ac, org.dromara.soul.plugin.springcloud.SpringCloudPlugin@35d60381, org.dromara.soul.plugin.httpclient.WebClientPlugin@2785db06, org.dromara.soul.plugin.divide.websocket.WebSocketPlugin@2e5e6fc4, org.dromara.soul.plugin.alibaba.dubbo.param.BodyParamPlugin@6cae2e4d, org.dromara.soul.plugin.sofa.SofaPlugin@834e986, org.dromara.soul.plugin.alibaba.dubbo.AlibabaDubboPlugin@68ab6ab0, org.dromara.soul.plugin.monitor.MonitorPlugin@715a70e9, org.dromara.soul.plugin.sofa.response.SofaResponsePlugin@604d23fa, org.dromara.soul.plugin.httpclient.response.WebClientResponsePlugin@79980d8d, org.dromara.soul.plugin.alibaba.dubbo.response.DubboResponsePlugin@61ffd148
]

web请求有下面的execute()方法处理,该方法会从插件链中匹配出插件来处理对应的 web 请求:

@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
    return Mono.defer(() -> {
        // 遍历插件链
        if (this.index < plugins.size()) {
            SoulPlugin plugin = plugins.get(this.index++);
            // 判断是否跳过此插件。所有插件都实现了SoulPlugin接口
            // SoulPlugin接口的default方法skip()默认返回false表示不跳过
           
            Boolean skip = plugin.skip(exchange);
            if (skip) {
                return this.execute(exchange);
            }
            // 执行未跳过的插件的execute()方法
            return plugin.execute(exchange, this);
        }
        return Mono.empty();
    });
}

很多插件都覆写了SoulPlugin接口中的skip()方法,如 DividePlugin:

/**
 * 通过请求上下文中的 RPC 类型来判断web请求是否跳过此Plugin
 */
@Override
public Boolean skip(final ServerWebExchange exchange) {
    // 从ServerWebExchange中取出上下文context
    final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
    // 判断 rpc 类型 是否为当前插件的 rpc 类型
    return !Objects.equals(Objects.requireNonNull(soulContext).getRpcType(), RpcTypeEnum.HTTP.getName());
}

当我们发起HTTP请求:curl http://localhost:9195/http/order/findById?id=111 时,可以在此处断点看到上下文信息如下,其中 rpcType=http,正好对应 DividePlugin 的RPC类型,因此不会跳过DividePlugin,而会跳过其他 RPC 类型的插件,比如说 SpringCloudPlugin。

SoulContext(module=/http, method=/order/findById, rpcType=http, httpMethod=GET, sign=null, timestamp=null, appKey=null, path=/http/order/findById, contextPath=/http, realUrl=/order/findById, dubboParams=null, startDateTime=2021-01-27T00:43:57.447)

因此,通过在 Boolean skip = plugin.skip(exchange);处断点,可以得出 HTTP 请求(RPC类型为:http)会跳过/不跳过的插件如下:

GlobalPlugin 				(默认不跳过) --- 有execute()方法
SignPlugin 					(默认不跳过)
WafPlugin 					(默认不跳过)
RateLimiterPlugin 	(默认不跳过)
HystrixPlugin 			(默认不跳过)
Resilience4JPlugin	(默认不跳过)

DividePlugin				(只匹配RPC类型为 http 的请求)  ---  有execute()方法
SpringCloudPlugin		(【跳过】因为RPC类型不匹配,此插件的rpc类型=springcloud)
WebClientPlugin		 (只匹配RPC类型为 http 或 springcloud 的请求)  ---  有execute()方法
WebSocketPlugin		 (【跳过】因为RPC类型不匹配,此插件的rpc类型=websocket)
BodyParamPlugin		  (【跳过】只有dubbo、sofa、tars 这几个Plugin里有用到)
SofaPlugin					(【跳过】因为RPC类型不匹配,此插件的rpc类型=sofa)
AlibabaDubboPlugin	(【跳过】因为RPC类型不匹配,此插件的rpc类型=dubbo)
MonitorPlugin				(默认不跳过)
SofaResponsePlugin	(【跳过】因为RPC类型不匹配,此插件的rpc类型=sofa)---有execute()方法
WebClientResponsePlugin(只匹配RPC类型为 http 或 springcloud 的请求)---有execute()方法
DubboResponsePlugin	(【跳过】因为RPC类型不匹配,此插件的rpc类型=dubbo)---有execute()方法

由于很多插件都没有覆写 execute()方法,所以 HTTP 请求实际经过的插件链如下:

GlobalPlugin ----> DividePlugin ----> WebClientPlugin ----> WebClientResponsePlugin

Divide 插件源码分析

DividePlugin 处理 HTTP 请求的关键代码如下:

@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
    // 获取请求上下文
    final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
    assert soulContext != null;
    // 获取规则的负载配置(负载均衡策略,重试次数)
    final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
    // 根据选择器获取请求转发配置
    final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
    if (CollectionUtils.isEmpty(upstreamList)) {
        log.error("divide upstream configuration error: {}", rule.toString());
        Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
        return WebFluxResultUtils.result(exchange, error);
    }
  
    // 目的IP
    final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
  
    // 针对目的IP的负载配置(请求协议,请求地址,权重等配置)
    DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
    if (Objects.isNull(divideUpstream)) {
        log.error("divide has no upstream");
        Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
        return WebFluxResultUtils.result(exchange, error);
    }
    // 设置请求目的IP,端口
    String domain = buildDomain(divideUpstream);
    // 拼装请求的接口URL
    String realURL = buildRealURL(domain, soulContext, exchange);
    exchange.getAttributes().put(Constants.HTTP_URL, realURL);
    // 设置超时时间
    exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
    exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
    return chain.execute(exchange);
}
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页