Soul网关源码学习(6)- 代理转发流程概览

前言

在前面几篇文章中,我们学习了如何使用 Soul 网关去代理 Http、Dubbo、Sofa 和 Spring Cloud 服务,并且对其进行了一些简单的测试,那接下来我们就要进入源码分析环节了。

不过在进入源码分析之前,先介绍一下我们源码分析的一个大致的流程。我们会以主干向枝叶扩散的方式,对 Soul 进行一个全面的学习和分析,每一部分都是先学习如何使用,再对其进行源码分析。所以即使 Soul 给我们提供了很多非常好用的功能插件,这里我们也不会一上来就对各个功能组件一顿分析。正所谓“擒贼先擒王”,“代理转发”作为一个网关最本职的工作,自然就是我们先要“擒拿”的对象。如何使用代理转发,我们之前已经学习了,接下来就开始源码分析。

责任链

在开始学习源码之前,我们先来简单了解一下责任链设计模式,这种设计模式在 Soul 网关的整个架构中,担任着非常重要的角色。

责任链是 GOF 23 中的一员,你几乎能在任何一个有名的开源框架中找到它的身影。对于其的介绍,这里就不详细展开了,因为这不是讲解设计模式的文章。对于责任链的实现方式,这里有一篇不错的文章《责任链模式实现的三种方式》,这里我就不重复了,我主要是分析一下 Soul 的责任链实现方式。

Soul 网关的责任链实现

首先我们先来看 Soul 网关请求的入口,SoulWebHandler#handle(ServerWebExchange) 方法:

    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());
        return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
                .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
    }

顺藤摸瓜,我们很容易就能找到 SoulWebHandler 的内部类方法 DefaultSoulPluginChain#execute(ServerWebExchange) :

public Mono<Void> execute(final ServerWebExchange exchange) {
    return Mono.defer(() -> {
        if (this.index < plugins.size()) {
        	// 获取当前需要执行的 SoulPlugin 插件,同时index++
            SoulPlugin plugin = plugins.get(this.index++);
            //如果插件需要跳过,则直接递归调用本身
            Boolean skip = plugin.skip(exchange);
            if (skip) {
                return this.execute(exchange);
            }
            //exchange 和 插件链本身做为方法参数,执行当前插件
            return plugin.execute(exchange, this);
        }
        return Mono.empty();
    });
}

上面这个“短小精干”的代码就是我们今天的主角,代码很短,也标志了注释,相信小伙伴理解起来也是不难。简单来说就是插件链实现 DefaultSoulPluginChain 持有所有插件的有序集合,然后通过 index 偏移量来不断取出插件,如果插件可以跳过,execute 方法就通过递归调用本身来取出下一个插件,如果不能被跳过,插件就直接执行。
因此我们了解了插件在可以被跳过的情况下,插件连是如何往下传递的,那插件在被执行的情况下又是如何跳回到 execute 方法从而执行下一个插件的呢?相信小伙伴应该也不难猜到答案。

看下面代码:

//DividePlugin
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
	...
    return chain.execute(exchange);
}

上面是 DividePlugin 插件的 doExecute 方法,其会被父类方法 AbstractSoulPlugin#execute(ServerWebExchange, SoulPluginChain) 调用。我们很容易看到插件链的 execute 方法会在最后被调用,从而继续执行下一个插件。
下面这个图是这种责任链实现方式的一个简单的概括:
在这里插入图片描述

Http 代理跟踪

现在我们以 Http 代理为例子,通过 debug 跟踪一下整个代理流程。
首先我们在 DefaultSoulPluginChain#execute(ServerWebExchange) 方法打个断点,然后通过访问 Http 服务器:
在这里插入图片描述
通过上面的图片可以看到,soul 目前一共有17个插件被装载到链中,这是由于我添加了 Dubbo 和 sofa 相关插件,如果只是Http 代理并不需要这么多,所以这里我仅仅把一部分列出来,其中加粗的是 Http 流程相关的。

插件链:
GlobalPlugin -> SignPlugin -> WafPlugin -> RateLimiterPlugin -> HystrixPlugin -> Resilience4JPlugin -> DividePlugin -> WebClientPlugin -> MonitorPlugin -> WebClientResponsePlugin

在介绍具体的插件之前,我们先来看一下插件的模板类方法 AbstractSoulPlugin#execute(ServerWebExchange,SoulPluginChain) ,该方法会被很多功能插件所继承:

   public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
       String pluginName = named();
       final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
       //我们关注这里的判断
       if (pluginData != null && pluginData.getEnabled()) {
        ...
        return doExecute(exchange, chain, selectorData, rule);
       }
       return chain.execute(exchange);
   }

从上面注释标注的 if 代码中我们可以看到,插件在这里有一个隐形的 skip 动作,这个 skip 和插件链方法 DefaultSoulPluginChain#execute(ServerWebExchange)中的 skip 判断有什么不一样呢?
这里我们挑一个 skip 方法的实现先看一下:

// 这是 AlibabaDubboPlugin 中的实现
public Boolean skip(final ServerWebExchange exchange) {
    final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
    assert soulContext != null;
    return !Objects.equals(soulContext.getRpcType(), RpcTypeEnum.DUBBO.getName());
}

通过上面的代码我们可以看到,这里的 skip 其实是一种写死的代码逻辑,一般情况下是不会变化的。比如上面的实现,就是通过判断请求的协议是否是 dubbo 来选择是否跳过,你是什么协议类型就用什么插件处理,协议已经发出来了就不会有什么变化了。

那上面模板方法中的隐形 skip 又是什么呢?pluginData != null 这句代码很容易理解,没有当前插件需要处理的数据,当然就跳过了,那 pluginData.getEnabled() 呢?其实它就是控制台我们配置的插件是否可用(也就是下面图中标记的部分),pluginData 是插件的配置信息,会被缓存到 soul 网关服务内存中,这部分内容会后面详细讲解。
在这里插入图片描述
所以到这里我们就知道了两种“skip”的区别,一种是静态的判断,一种是通过控制台动态配置的。由于我这里只开启了 divide 插件,所以上面那些没加租的插件都是被隐形 skip 的。

接下来我们再对 Http 流程几个关键插件进行一下简单的介绍,从而让我们对整个代理转发流程有一个总体上的认知,方便后面逐个击破。

  • GlobalPlugin : 构造 SoulContext,SoulContext 是 Soul 网关层面的代理请求的一个上下文,里面有请求相关的信息,比如协议,请求方法等。
  • DividePlugin :处理负载均衡,选择目标主机地址,构造访问的URL。
  • WebClientPlugin :请求目标服务器
        public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
            ...
            //调用 handleRequestBody 发起对目标主机的请求
            return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
        }
    	//真正发起对目标主机的请求,并且将结果传递到下一个插件处理
    	private Mono<Void> handleRequestBody(...) {
            return requestBodySpec.headers(httpHeaders -> {
                httpHeaders.addAll(exchange.getRequest().getHeaders());
                httpHeaders.remove(HttpHeaders.HOST);
            })
                    ...
                    .exchange()
                    ...
                    .flatMap(e -> doNext(e, exchange, chain));
        }
    
  • WebClientResponsePlugin:返回数据给请求客户端,到这里 Http 代理就走完了整个插件链。
       public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
       		//结束整个插件链,并且写入 响应数据返回给客户端
            return chain.execute(exchange).then(Mono.defer(() -> {
                ServerHttpResponse response = exchange.getResponse();
              	...
                return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers()));
            }));
        }
    

最后我们再通过一幅流程图简单总结一下 Http 代理转发的流程:
在这里插入图片描述

总结

这篇文章,我们从总体上介绍了 soul 插件链的整个结构和调用过程,并且通过 Http 的访问流程,大致上了解代理转发的整个主干层次:请求是从何处进入 soul 网关的,又是从何处离开的。后面我们再分析一下 dubbo 和 sofa 的代理转发的总览流程,对比不同协议下转发流程的异同,然后再对单个协议的代理转发流程进行更深层次的了解和研读,包括单个插件的具体实现逻辑分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值