Apache-shenYu源码阅读11-AlibabaDubbo调用分析

582 阅读7分钟

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泛化调用叫为核心的基础知识。