一、插件简介
- spring cloud插件
- 将http协议转成springCloud协议的核心。
- dubbo插件
- 将http协议转换成dubbo协议的插件,也是网关实现dubbo泛化调用的关键。
- dubbo插件需要配合元数据才能实现dubbo的调用。
- apache dubbo 和 alibaba dubbo用户,都是使用该同一插件。
- sofa插件
- sofa插件是将http协议 转换成sofa协议 的插件,也是网关实现sofa泛化调用的关键。
- sofa插件需要配合元数据才能实现dubbo的调用。
二、springcloud插件流程
2.1 半个月之前的一个错误
在半个月前测试srping cloud插件的时候,遇到了一个问题springcloud serviceId 不存在或者配置错误!或者注册中心配置错误!。
当时没有解决,错误依旧存在,随着对流程和代码的熟悉,今天很快的定位出了问题所在。记录一下定位流程
- 看错误信息没提示serviceId不存在或者配置错误,或者注册中心配置错误,感觉是配置中心错了
- 检查配置信息,根据官网配置,之前soul-bootstrap配置了pom文件和application-local.yml的eureka
- 启动了soul-test里自带的soul-test-eureka
- 重启soul-bootstrap服务
- soul-test-springcloud配置eureka注册中心,启动soul-test-springcloud服务
- postman发送http://localhost:9195/springcloud/order/findById?id=1
- 发现还是报springcloud serviceId错误
- debug,根据这几天的学习,断点至SpringCloudPlugin的doExecute()方法,肯定会走到这里。
final ServiceInstance serviceInstance = loadBalancer.choose(selectorHandle.getServiceId());
if (Objects.isNull(serviceInstance)) {
Object error = SoulResultWrap.error(SoulResultEnum.SPRINGCLOUD_SERVICEID_IS_ERROR.getCode(), SoulResultEnum.SPRINGCLOUD_SERVICEID_IS_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
发现这里为空,然后返回了这个错误。还是注册中心负载均衡中心没有找到可用的service id
- 看soulBootstrapApplication的日志,发现居然没有eureka的一点痕迹,肯定是erueka注册中心配置不对。
- 全局eureka搜索soulBootstrap的pom文件,定位到这里默认是屏蔽的
<!-- springCloud if you config register center is nacos please dependency this-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>-->
<!-- <version>2.1.0.RELEASE</version>-->
<!-- </dependency>-->
<!-- springCloud if you config register center is eureka please dependency end-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!-- <version>2.2.0.RELEASE</version>-->
<!-- </dependency>-->
- 发现eureka-client的客户端居然是屏蔽的,大意了,一个低级错误,官网对这块的配置依赖也没说明,打开后,重启后稍等几秒(猜测同步延迟)正常。
2.2 springcloud 插件源码解读
- 初始化、插件链都和divide没有区别,直接进入doExecute进行分析
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
if (Objects.isNull(rule)) {
return Mono.empty();
}
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 获取selector和rule信息
final SpringCloudRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), SpringCloudRuleHandle.class);
final SpringCloudSelectorHandle selectorHandle = GsonUtils.getInstance().fromJson(selector.getHandle(), SpringCloudSelectorHandle.class);
if (StringUtils.isBlank(selectorHandle.getServiceId()) || StringUtils.isBlank(ruleHandle.getPath())) {
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_CONFIG_SPRINGCLOUD_SERVICEID.getCode(), SoulResultEnum.CANNOT_CONFIG_SPRINGCLOUD_SERVICEID.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// loadBalancer springcloud的负载均衡实现,返回一个选择后的实例
final ServiceInstance serviceInstance = loadBalancer.choose(selectorHandle.getServiceId());
if (Objects.isNull(serviceInstance)) {
Object error = SoulResultWrap.error(SoulResultEnum.SPRINGCLOUD_SERVICEID_IS_ERROR.getCode(), SoulResultEnum.SPRINGCLOUD_SERVICEID_IS_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// reconstructURI 返回全路径url,具体为host:port格式类型
final URI uri = loadBalancer.reconstructURI(serviceInstance, URI.create(soulContext.getRealUrl()));
// 构建全路径url,带参数的
String realURL = buildRealURL(uri.toASCIIString(), soulContext.getHttpMethod(), exchange.getRequest().getURI().getQuery());
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
//set time out.
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
return chain.execute(exchange);
}
具体分析一下
-
exchange里的request的真实url=http://localhost:9195/springcloud/order/findById?id=1
-
reconstructURI = http://JH-COMPUTER:8884/order/findById
-
buildRealURL=http://JH-COMPUTER:8884/order/findById?id=1
-
执行后续插件链,然后返回结果
三、dubbo插件流程
- dubbo的接入步骤,参考dubbo 接入soul网关
- 配置完boostrap的pom文件、启动注册中心、web管理端开启插件、启动soul-test-alibaba-dubbo
- postman验证一下
- 直接到核心处理流程
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;
log.error(" path is :{}, meta data have error.... {}", soulContext.getPath(), metaData.toString());
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);
}
// 检查具体参数的参数类型和body值
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);
}
// 核心,泛化调用
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);
}
- 整体处理流程就是检查元数据–检查参数类型–泛化调用
- 详细看一下泛化调用alibabaDubboProxyService.genericInvoker(body, metaData),入参包括body和metaData
dubbo提供了泛化调用,也就是在consumer只知道一个接口全限定名以及入参和返参的情况下,就可以调用provider端的调用,而不需要传统的接口定义这些繁杂的结构.
泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。
- genericInvoker代码流程如下
public Object genericInvoker(final String body, final MetaData metaData) throws SoulException {
// 根据请求路径,从缓存中获取服务注册信息,协议,超时时间,重试次数,负载均衡等信息
ReferenceConfig<GenericService> reference = ApplicationConfigCache.getInstance().get(metaData.getPath());
if (Objects.isNull(reference) || StringUtils.isEmpty(reference.getInterface())) {
ApplicationConfigCache.getInstance().invalidate(metaData.getPath());
reference = ApplicationConfigCache.getInstance().initRef(metaData);
}
// genericService是一个代理对象,接口信息是从zookeeper中拿到
GenericService genericService = reference.get();
try {
// 核心代码,将请求参数转化为dubbo的泛化参数
Pair<String[], Object[]> pair;
if (ParamCheckUtils.dubboBodyIsEmpty(body)) {
pair = new ImmutablePair<>(new String[]{}, new Object[]{});
} else {
pair = dubboParamResolveService.buildParameter(body, metaData.getParameterTypes());
}
// 真实的泛化调用
return genericService.$invoke(metaData.getMethodName(), pair.getLeft(), pair.getRight());
} catch (GenericException e) {
log.error("dubbo invoker have exception", e);
throw new SoulException(e.getMessage());
}
}
8. 返回结果也是使用map进行存储
9. DubboResponsePlugin插件再对结果做一些包装返回
四、sofa插件流程
- sofa的接入步骤,参考sofa 接入soul网关
- 配置完boostrap的pom文件、启动zk注册中心、web管理端开启插件、启动soul-test-sofa
- postman请求验证
- 在SoulWebHandler插件链上打断点,sofa相关的插件一共有三个
- BodyParamPlugin插件
执行链路中是先执行的,负责处理一些请求类型的参数预处理的。 - SofaPlugin插件
SofaDubbo插件的处理逻辑和dubbo一致:检查元数据 --检查参数类型–泛化调用。
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
// 获取body
String body = exchange.getAttribute(Constants.SOFA_PARAMS);
SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
// 检查metadata
MetaData metaData = exchange.getAttribute(Constants.META_DATA);
if (!checkMetaData(metaData)) {
assert metaData != null;
log.error(" path is :{}, meta data have error.... {}", soulContext.getPath(), metaData.toString());
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);
}
// 检查请求参数类型和body
if (StringUtils.isNoneBlank(metaData.getParameterTypes()) && StringUtils.isBlank(body)) {
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
Object error = SoulResultWrap.error(SoulResultEnum.SOFA_HAVE_BODY_PARAM.getCode(), SoulResultEnum.SOFA_HAVE_BODY_PARAM.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// 泛化调用
final Mono<Object> result = sofaProxyService.genericInvoker(body, metaData, exchange);
return result.then(chain.execute(exchange));
}
public Mono<Object> genericInvoker(final String body, final MetaData metaData, final ServerWebExchange exchange) throws SoulException {
//获取服务信息
ConsumerConfig<GenericService> reference = ApplicationConfigCache.getInstance().get(metaData.getServiceName());
if (Objects.isNull(reference) || StringUtils.isEmpty(reference.getInterfaceId())) {
ApplicationConfigCache.getInstance().invalidate(metaData.getServiceName());
reference = ApplicationConfigCache.getInstance().initRef(metaData);
}
GenericService genericService = reference.refer();
//请求参数转化为泛化调用的参数
Pair<String[], Object[]> pair;
if (null == body || "".equals(body) || "{}".equals(body) || "null".equals(body)) {
pair = new ImmutablePair<>(new String[]{}, new Object[]{});
} else {
pair = sofaParamResolveService.buildParameter(body, metaData.getParameterTypes());
}
//异步返回结果
CompletableFuture<Object> future = new CompletableFuture<>();
//响应回调
RpcInvokeContext.getContext().setResponseCallback(new SofaResponseCallback<Object>() {
@Override
public void onAppResponse(final Object o, final String s, final RequestBase requestBase) {
future.complete(o);
}
@Override
public void onAppException(final Throwable throwable, final String s, final RequestBase requestBase) {
future.completeExceptionally(throwable);
}
@Override
public void onSofaException(final SofaRpcException e, final String s, final RequestBase requestBase) {
future.completeExceptionally(e);
}
});
// 泛化调用
genericService.$invoke(metaData.getMethodName(), pair.getLeft(), pair.getRight());
// 返回结果
return Mono.fromFuture(future.thenApply(ret -> {
if (Objects.isNull(ret)) {
ret = Constants.SOFA_RPC_RESULT_EMPTY;
}
exchange.getAttributes().put(Constants.SOFA_RPC_RESULT, ret);
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
return ret;
})).onErrorMap(SoulException::new);
}
- SofaResponsePlugin插件
对结果再一次包装,处理错误信息和成功的结果信息
//org.dromara.soul.plugin.sofa.response.SofaResponsePlugin#execute
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
return chain.execute(exchange).then(Mono.defer(() -> {
final Object result = exchange.getAttribute(Constants.SOFA_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);
}));
}
五、小结
- springcloud插件
- 获取selector和rule
- 检验
- 负载均衡找出一个待调用方
- 转换url全路径
- 调用
- dubbo和sofa插件
- 获取元数据
- 从zk获取代理对象,获取接口服务等信息
- 泛化入参准备
- 泛化真实调用
- 得到泛化结果
- 统一包装一下返回