前言
在前面第 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 的代理实现。