Soul网关源码学习(13)- Apache Dubbo 代理转发流程细解

前言

在前面第 8 ~ 12章中,我们深入分析了 Soul 关于 Http 的代理转发的底层实现,本篇我们开始分析 Apache Dubbo 的代理转发的是如何实现的。由于在前面的文章,我们已经分析了一些公有插件的实现,这里就不重复了,这里只针对 Apache Dubbo 代理的相关插件的实现进行分析。如果有小伙伴不明白不同协议代理之间一些流程上的共性,可以回去参考第6章第7章

BodyParamPlugin

和前面 Http 的流程分析一样,我们先忽略一些非功能性插件,因此,dubbo 请求经过 GlobalPlugin 的解析后,就会流入 BodyParamPlugin。BodyParamPlugin 顾名思义就是用来处理参数体的,我来简单看一下其代码:

    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
    	...
        if (Objects.nonNull(soulContext) && RpcTypeEnum.DUBBO.getName().equals(soulContext.getRpcType())) {
        	...
        	//处理 json
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
                return body(exchange, serverRequest, chain);
            }
            //处理表单
            if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
                return formData(exchange, serverRequest, chain);
            }
            //处理 get 请求参数
            return query(exchange, serverRequest, chain);
        }
        return chain.execute(exchange);
    }

从上面代码可以看到,dubbo 代理分别支持三种 Http 参数请求:Json、表单和 Get 参数。如果是 Json 体,会被直接存进上下文中,如果是其他两个呢?我们跟踪代码后发现,它们都会先被转换成 Map,然后再把 Map 转换成 Json 字符串存储到上下文中。这方面具体的代码我就不贴出来了,实现逻辑很简单,有兴趣的同学自己跟踪一下就 OK 了!

ApacheDubboPlugin

BodyParamPlugin 处理完参数之后,就进入到 ApacheDubboPlugin 插件,后者将负载真正发起 dubbo 的请求。ApacheDubboPlugin 继承于模板类 AbstractSoulPlugin,所以直接看其 doExecute 方法:

protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
    String body = exchange.getAttribute(Constants.DUBBO_PARAMS);
    SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
    assert soulContext != null;
    MetaData metaData = exchange.getAttribute(Constants.META_DATA);
    //省略了一些错误处理
    ...
    // 发起对 dubbo 服务的泛化请求
    final Mono<Object> result = dubboProxyService.genericInvoker(body, metaData, exchange);
    return result.then(chain.execute(exchange));
}

dubboProxyService

上面方法的逻辑主要是通过 dubboProxyService 对象发起了对 dubbo 服务的泛化请求,所以接下来我们再来分析一下 DubboProxyService#genericInvoker 方法。

public Mono<Object> genericInvoker(final String body, final MetaData metaData, final ServerWebExchange exchange) throws SoulException {
	... 
	//引用远程服务,该实例很重量,里面封装了所有与注册中心及服务提供方连接,所以一般都是缓存起来的
	//所以下面代码也能很清晰看到其是从缓存里面取的。
    ReferenceConfig<GenericService> reference = ApplicationConfigCache.getInstance().get(metaData.getPath());
    if (Objects.isNull(reference) || StringUtils.isEmpty(reference.getInterface())) {
        ApplicationConfigCache.getInstance().invalidate(metaData.getPath());
        // 如果 reference 为空,则初始化一个
        reference = ApplicationConfigCache.getInstance().initRef(metaData);
    }
    ...
    GenericService genericService = reference.get();
    //构建泛化调用的参数参数
    Pair<String[], Object[]> pair;
    if (ParamCheckUtils.dubboBodyIsEmpty(body)) {
        pair = new ImmutablePair<>(new String[]{}, new Object[]{});
    } else {
        pair = dubboParamResolveService.buildParameter(body, metaData.getParameterTypes());
    }
    //发起泛化调用
    CompletableFuture<Object> future = genericService.$invokeAsync(metaData.getMethodName(), pair.getLeft(), pair.getRight());
    return Mono.fromFuture(future.thenApply(ret -> {
		//处理 dubbo 调用的返回结果
		...
        return ret;
    })).onErrorMap(exception -> exception instanceof GenericException ? new SoulException(((GenericException) exception).getExceptionMessage()) : new SoulException(exception));
}

上面的方法主要是发起对 dubbo 服务的泛化调用,如果小伙伴对 Dubbo 泛化调用有什么疑问的话,可以先去参考一下官方文档

从上面第一步可以看到,如果 ReferenceConfig 对象的缓存为空,则初始化一个,那除了这里还有其他地方初始化么?我们查询一下该方法的调用,发现还有一处:

//ApacheDubboMetaDataSubscriber#onSubscribe
 public void onSubscribe(final MetaData metaData) {
        if (RpcTypeEnum.DUBBO.getName().equals(metaData.getRpcType())) {
            MetaData exist = META_DATA.get(metaData.getPath());
            if (Objects.isNull(META_DATA.get(metaData.getPath())) || Objects.isNull(ApplicationConfigCache.getInstance().get(metaData.getPath()))) {
                // The first initialization
                ApplicationConfigCache.getInstance().initRef(metaData);
            } else {
                ...
            }
            META_DATA.put(metaData.getPath(), metaData);
        }
    }

上面代码是订阅了 admin 的数据推送,如果其上面的 Meta Data 发送变化了,就会触发上面的回调,ReferenceConfig 会再这里进行初始化,所以我们知道 ReferenceConfig 对象首先会在这里进行初始化,然后在 dubboProxyService#genericInvoker 还会做第二次检测,如果为空则再进行初始化。

dubboParamResolveService

获取了 ReferenceConfig 后,然后就会对泛化调用参数的构建:

//第一个参数是请求体,也就是包含参数的值
//第二个参数是请求参数类型数组,它通过 MetaData 获取
pair = dubboParamResolveService.buildParameter(body, metaData.getParameterTypes());

我们主要看一下参数类型来自哪里的?
在 Soul Admin 就能找到它的身影,它主要是 soul Admin 通过 dubbo 的注册中心获取的,然后再同步到 Soul 网关。
在这里插入图片描述
参数构建完毕后,会通过一个 pair 返回,主要是把相应的参数类型和参数值按照调用接口的参数顺序一一对应起来。
有了 pair 之后,就是直接发起泛化调用,并将请求结果存储到请求上下文 ServerWebExchange 中。

DubboResponsePlugin

由于 dubbo 服务返回的结果一般都是一个 POJO,所以 Soul 网关还需将其包装成 rsetful 格式才能返回给客户端。其代码逻辑非常简单,这里就不多做分析了:

public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
    return chain.execute(exchange).then(Mono.defer(() -> {
        final Object result = exchange.getAttribute(Constants.DUBBO_RPC_RESULT);
        if (Objects.isNull(result)) {
            Object error = SoulResultWrap.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
            return WebFluxResultUtils.result(exchange, error);
        }
        Object success = SoulResultWrap.success(SoulResultEnum.SUCCESS.getCode(), SoulResultEnum.SUCCESS.getMsg(), JsonUtils.removeClass(result));
        return WebFluxResultUtils.result(exchange, success);
    }));
}

总结

本篇文章,我们分析了 Soul 网关如何通过泛化调用实现 dubbo 服务的代理的,后面会接着分析 sofa 和 tars 的代理实现。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值