soul网关系列(十五):协议类插件(springcloud、dubbo、sofa)源码解读

一、插件简介

  • 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插件流程

  1. dubbo的接入步骤,参考dubbo 接入soul网关
  2. 配置完boostrap的pom文件、启动注册中心、web管理端开启插件、启动soul-test-alibaba-dubbo
  3. postman验证一下
    在这里插入图片描述
  4. 直接到核心处理流程
 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);
    }
  1. 整体处理流程就是检查元数据–检查参数类型–泛化调用
  2. 详细看一下泛化调用alibabaDubboProxyService.genericInvoker(body, metaData),入参包括body和metaData

dubbo提供了泛化调用,也就是在consumer只知道一个接口全限定名以及入参和返参的情况下,就可以调用provider端的调用,而不需要传统的接口定义这些繁杂的结构.

泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。

  1. 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插件流程

  1. sofa的接入步骤,参考sofa 接入soul网关
  2. 配置完boostrap的pom文件、启动zk注册中心、web管理端开启插件、启动soul-test-sofa
  3. postman请求验证
    在这里插入图片描述
  4. 在SoulWebHandler插件链上打断点,sofa相关的插件一共有三个
    在这里插入图片描述
  5. BodyParamPlugin插件
    执行链路中是先执行的,负责处理一些请求类型的参数预处理的。
  6. 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);
    }
  1. 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获取代理对象,获取接口服务等信息
    • 泛化入参准备
    • 泛化真实调用
    • 得到泛化结果
    • 统一包装一下返回
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值