Dubbo请求示例
详情请看官方文档 文档地址dubbo接入soul网关
在官方文档中已经详细的介绍了Dubbo的详细接入步骤,这里不做过多赘述。
需要注意的是在使用dubbo时,需要在soul中配置对应的dubbo的注册中心。
这里有个小小的疑问,问什么需要配置注册中心?
在整体dubbo服务注册完成后,会看到对应页面数据变更情况,如下图:
插件列表-Dubbo
系统管理-元数据管理
测试一次请求:
Dubbo服务注册
重点关注SoulDubboClient注解、AlibabaDubboServiceBeanPostProcessor类
/**
* 路径
* @return
*/
String path();
/**
* 规则名称
* @return
*/
String ruleName() default "";
/**
* 介绍
*/
String desc() default "";
/**
* 是否启用
*/
boolean enabled() default true;
SoulDubboClient注解
之前已经有了阅读SpringMvc接口注册的经验,所以得知该类为标识需要注册的接口配置类,此处不做过多赘述。
/**
* 路径
* @return
*/
String path();
/**
* 规则名称
* @return
*/
String ruleName() default "";
/**
* 介绍
*/
String desc() default "";
/**
* 是否启用
*/
boolean enabled() default true;
AlibabaDubboServiceBeanPostProcessor
该类实现了ApplicationListener的ContextRefreshedEvent事件,所以该类的入口在其实现的onApplicationEvent方法,代码如下。
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
if (Objects.nonNull(contextRefreshedEvent.getApplicationContext().getParent())) {
return;
}
/**
* 获取对应的ServiceBean对象
*/
Map<String, ServiceBean> serviceBean = contextRefreshedEvent.getApplicationContext().getBeansOfType(ServiceBean.class);
for (Map.Entry<String, ServiceBean> entry : serviceBean.entrySet()) {
/**
* 异步提交给线程池处理
*/
executorService.execute(() -> handler(entry.getValue()));
}
}
可以看出,在这段代码中,最主要的就是获取了Dubbo的ServiceBean对象。
ps: Dubbo的ServiceBean是dubbo的核心对象,包含了元数据及一些相关的信息,你可以理解成对dubbo服务的所有操作都会映射到该实体。
接下来的逻辑和SpringMvc的注册逻辑基本一致,扫包拿对应的SoulDubboClient注解,将SoulDubboClient注解的属性值和对应的ServiceBean的属性值进行拼装,进行注册。 核心代码为buildJsonParams方法
private String buildJsonParams(final ServiceBean<?> serviceBean, final SoulDubboClient soulDubboClient, final Method method) {
// ........省略
MetaDataDTO metaDataDTO = MetaDataDTO.builder()
.appName(appName)
.serviceName(serviceName)
.methodName(methodName)
.contextPath(dubboConfig.getContextPath())
.path(path)
.ruleName(ruleName)
.pathDesc(desc)
.parameterTypes(parameterTypes)
.rpcExt(buildRpcExt(serviceBean))
.rpcType("dubbo")
.enabled(soulDubboClient.enabled())
.build();
return OkHttpTools.getInstance().getGson().toJson(metaDataDTO);
}
相对于SpringMvc注册而言,最主要的不同在于其注册传递的是MetaDataDTO对象,注册地址为soul-client/dubbo-register。
至此dubbo服务的注册的starter代码逻辑已经分析完成。
Dubbo数据接收
在admin端进行数据接收主要关注dubbo-register,在controller层,会注意到关于RPC相关的调用都是传递的MetaDataDTO对象
在Service层对象处理逻辑,RPC相关类型的处理逻辑大体一致,主要还是保存元数据信息,发布元数据事件,处理对应RPC的规则,发布规则相关事件。
dubbo示例如下:
public String registerDubbo(final MetaDataDTO dto) {
MetaDataDO exist = metaDataMapper.findByPath(dto.getPath());
saveOrUpdateMetaData(exist, dto);
String selectorId = handlerDubboSelector(dto);
handlerDubboRule(selectorId, dto);
return SoulResultMessage.SUCCESS;
}
Dubbo相关的数据同步
在之前的数据同步模块总结文章,已经大体明白了数据同步的整体逻辑,接下来追踪针对于dubbo的数据逻辑处理。
二次回顾一下,数据处理除了对内存内的数据进行更改外,还对各个插件实现的MetaDataSubscriber、PluginDataSubscriber、AuthDataSubscriber这三个接口进行了数据下发操作
handlePlugin实现
public void handlerPlugin(final PluginData pluginData) {
if (null != pluginData && pluginData.getEnabled()) {
DubboRegisterConfig dubboRegisterConfig = GsonUtils.getInstance().fromJson(pluginData.getConfig(), DubboRegisterConfig.class);
DubboRegisterConfig exist = Singleton.INST.get(DubboRegisterConfig.class);
if (Objects.isNull(dubboRegisterConfig)) {
return;
}
//判断是否需要重新加载配置缓存
if (Objects.isNull(exist) || !dubboRegisterConfig.equals(exist)) {
// If it is null, initialize it
ApplicationConfigCache.getInstance().init(dubboRegisterConfig);
ApplicationConfigCache.getInstance().invalidateAll();
}
Singleton.INST.single(DubboRegisterConfig.class, dubboRegisterConfig);
}
}
这段代码最为核心的部分在于ApplicationConfigCache相关方法的使用,需要暂时透漏一点的是对于RPC的调用,就是将HTTP协议转换成对应的RPC协议,而在SOUL网关调用Dubbo时,是将HTTP转化成Dubbo协议,具体是怎么转换的,就涉及到了Dubbo的泛化调用相关的代码知识,若想了解推荐阅读Dubbo的GenericService简介和基本使用 .
ApplicationConfigCache dubbo调用配置相关缓存类
参数
- ApplicationConfig 应用配置
- 在这里你可以理解成soul通过dubbo提供的代码伪装成了一个dubbo服务的调用方的配置 在init方法初始化
//设置了应用的名称
applicationConfig = new ApplicationConfig("soul_proxy");
- RegistryConfig 注册配置
- 设置注册配置,相关的API就是这么使用的,目前还没有追Dubbo的源码 在init方法初始化,大概对这个类的理解就是在进行泛化调用时,底层可能是要从注册中心拿点东西
if (registryConfig == null) {
registryConfig = new RegistryConfig();
registryConfig.setProtocol(dubboRegisterConfig.getProtocol());
registryConfig.setId("soul_proxy");
registryConfig.setRegister(false);
registryConfig.setAddress(dubboRegisterConfig.getRegister());
Optional.ofNullable(dubboRegisterConfig.getGroup()).ifPresent(registryConfig::setGroup);
}
- LoadingCache<String, ReferenceConfig> cache
- 个人理解:这里面放的是dubbo调用的具体实例?
方法
- init 进行初始化ApplicationConfig、RegistryConfig
- initRef 根据元数据获取具体调用的配置,返回ReferenceConfig对象
- build 构建调用的参考配置,并放入cache中
- get 通过方法名获取对应的参考配置。
onSubscribe MetaData实现
在订阅元数据信息后,dubbo调用了ApplicationConfigCache.build方法,进行了初始化 对应的调用接口的配置。
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 {
if (!metaData.getServiceName().equals(exist.getServiceName())
|| !metaData.getRpcExt().equals(exist.getRpcExt())
|| !metaData.getParameterTypes().equals(exist.getParameterTypes())
|| !metaData.getMethodName().equals(exist.getMethodName())) {
ApplicationConfigCache.getInstance().build(metaData);
}
}
META_DATA.put(metaData.getPath(), metaData);
}
}
ps: 看到这里可能都比较迷惑,原因在于对dubbo的泛化调用机制不熟悉,当初本来以为soul实现的泛化调用,是自己写了一套代码实现的,但实际看代码发现是直接用的dubbo包对应的类,上面的逻辑如果没通不要紧,看完调用逻辑那块可能会有一定理解。
Dubbo服务调用
接下来看一下在初始化完成上述的实例信息后,是如何进行的dubbo的泛化调用。
查看调用的插件
由于之前在追溯Http调用的时候有了一定的经验,所以我们我们直接看调用插件有哪些。
未跳过的插件为global.GlobalPlugin
未跳过的插件为sign.SignPlugin
未跳过的插件为waf.WafPlugin
未跳过的插件为ratelimiter.RateLimiterPlugin
未跳过的插件为hystrix.HystrixPlugin
未跳过的插件为resilience4j.Resilience4JPlugin
未跳过的插件为alibaba.dubbo.param.BodyParamPlugin
未跳过的插件为alibaba.dubbo.AlibabaDubboPlugin
未跳过的插件为monitor.MonitorPlugin
未跳过的插件为alibaba.dubbo.response.DubboResponsePlugin
前置插件全部忽略,最后保留的插件只有BodyParamPlugin、AlibabaDubboPlugin、DubboResponsePlugin
之前在追溯Http调用时,http调用的插件主要是WebClientPlugin、WebClientResponse,由此可以得知涉及dubbo的三个主要插件AlibabaDubboPlugin主要是调用插件,DubboResponsePlugin主要是处理返回结果插件,那么BodyParamPlugin插件到底是什么作用呢?
答案就是针对于不同的RPC调用,都要实现其对应的参数封装。
BodyParamPlugin
此处就是常规的参数封装,不过多赘述。
AlibabaDubboPlugin
针对于调用Dubbo来说,在下面核心的方法只有alibabaDubboProxyService.genericInvoker(body, metaData);
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);
//校验元数据
if (!checkMetaData(metaData)) {
assert metaData != null;
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.META_DATA_ERROR.getCode(), SoulResultEnum.META_DATA_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
//校验参数格式
if (StringUtils.isNoneBlank(metaData.getParameterTypes()) && StringUtils.isBlank(body)) {
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.DUBBO_HAVE_BODY_PARAM.getCode(), SoulResultEnum.DUBBO_HAVE_BODY_PARAM.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
//进行dubbo调用
Object result = alibabaDubboProxyService.genericInvoker(body, metaData);
//将调用结果放入上下文
if (Objects.nonNull(result)) {
exchange.getAttributes().put(Constants.DUBBO_RPC_RESULT, result);
} else {
exchange.getAttributes().put(Constants.DUBBO_RPC_RESULT, Constants.DUBBO_RPC_RESULT_EMPTY);
}
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
return chain.execute(exchange);
}
关于genericInvoker方法就是发起了dubbo的泛化调用
//省略代码
reference = ApplicationConfigCache.getInstance().initRef(metaData);
/**
* 第一个参数是方法名。
* 第二个参数是一个字符串数组,这是接口方法每个参数类型的全路径。
* 第三个参数是Object数组,是传给方法的具体参数列表。
*/
return genericService.$invoke(metaData.getMethodName(), pair.getLeft(), pair.getRight());
DubboResponsePlugin
处理结果的逻辑也比较简单,就是将DubboPlugin放入的结果取出来,然后交给WebFlux进行返回
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);
try {
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);
} catch (SoulException e) {
return Mono.empty();
}
}));
}
总结
其实通篇来看,常规的http代理和dubbo代理不一样的就是调用的协议以及参数封装,从dubbo的调用以管窥豹来看也能预见其他RPC调用的方式也与这一致,本篇文章内容并没有什么实用性干货,但还是学到了dubbo泛化调用叫为核心的基础知识。