Soul网关源码解析(三)代理Dubbo服务

目标

  • 使用 soul 代理 dubbo 服务

  • dubbo 服务如何注册到网关的?

  • dubbo 插件是如何工作的?

  • 理清 http --> 网关–> dubbo provider 整条链路经历了什么。

  • 总结

一、使用 soul 代理 dubbo 服务

1、dubbo 服务接入网关
1.1 springboot 项目接入方式

1.1.1 依赖引入

alibaba dubbo 用户

       <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-client-alibaba-dubbo</artifactId>
            <version>${last.version}</version>
       </dependency>

apache dubbo 用户

       <dependency>
            <groupId>org.dromara</groupId>
            <artifactId>soul-spring-boot-starter-client-apache-dubbo</artifactId>
            <version>${last.version}</version>
       </dependency>

1.1.2 yml 配置

   soul:
     dubbo:
       adminUrl: http://localhost:9095
       contextPath: /dubbo
       appName: dubbo
1.2 spring 项目接入方式

1.2.1 依赖引入

alibaba dubbo 用户

      <dependency>
          <groupId>org.dromara</groupId>
          <artifactId>soul-client-alibaba-dubbo</artifactId>
          <version>${last.version}</version>
       </dependency>

apache dubbo 用户

      <dependency>
          <groupId>org.dromara</groupId>
          <artifactId>soul-client-apache-dubbo</artifactId>
          <version>${last.version}</version>
       </dependency>

1.2.2 xml 配置

alibaba dubbo 用户

        <bean id ="alibabaDubboServiceBeanPostProcessor" class ="org.dromara.soul.client.alibaba.dubbo.AlibabaDubboServiceBeanPostProcessor">
             <constructor-arg  ref="dubboConfig"/>
        </bean>

        <bean id="dubboConfig" class="org.dromara.soul.client.dubbo.common.config.DubboConfig">
             <property name="adminUrl" value="http://localhost:9095"/>
             <property name="contextPath" value="/你的contextPath"/>
             <property name="appName" value="你的名字"/>
        </bean>

apache dubbo 用户

         <bean id ="apacheDubboServiceBeanPostProcessor" class ="org.dromara.soul.client.apache.dubbo.ApacheDubboServiceBeanPostProcessor">
              <constructor-arg  ref="dubboConfig"/>
         </bean>

         <bean id="dubboConfig" class="org.dromara.soul.client.dubbo.common.config.DubboConfig">
              <property name="adminUrl" value="http://localhost:9095"/>
              <property name="contextPath" value="/你的contextPath"/>
              <property name="appName" value="你的名字"/>
         </bean>
2、配置 dubbo 插件

启动 soul-adminsoul-bootstrap,前往后台插件管理页面

启用 dubbo 插件,并配置注册中心 ip + 端口
在这里插入图片描述

3、注册 dubbo 服务到网关

dubbo 服务实现类的方法上加上 @SoulDubboClient 注解

    @Override
    @SoulDubboClient(path = "/findById", desc = "Query by Id")
    public DubboTest findById(final String id) {
        DubboTest dubboTest = new DubboTest();
        dubboTest.setId(id);
        dubboTest.setName("hello world Soul Alibaba Dubbo, findById");
        return dubboTest;
    }

启动 dubbo 提供者项目,日志输出 dubbo client register success 注册成功
在这里插入图片描述

4、http 方式请求 dubbo 服务

通过 http post 方式请求网关,通过 body 传递 json 格式参数(contextPath=/dubbo)
在这里插入图片描述

dubbo 服务访问成功,同时 soul-bootstrap 输出匹配日志

dubbo selector success match , selector name :/dubbo
dubbo selector success match , selector name :/dubbo/findById

二、dubbo 服务如何注册到网关?

alibaba dubbo 与 apache dubbo 差异较小,alibaba dubbo 的注册流程弄明白后,apache dubbo 也自然清楚了。

从控制台日志 dubbo client register success 定位到 RegisterUtils 类,在打印注册日志的地方加上断点
在这里插入图片描述

重启项目,可以看到成功进入断点,在此处调用 OkHttpTools.post 进行服务注册。

注意此处是注册到 soul-admin,再由网关和 soul-admin 的数据同步机制同步到网关内存。

注册的元数据怎么拿到的呢?

从堆栈定位到 doRegister 方法的调用方 AlibabaDubboServiceBeanPostProcessor.handler

    private void handler(final ServiceBean<?> serviceBean) {
        Class<?> clazz = serviceBean.getRef().getClass();
        // 如果是 cglib 代理,则取带有泛型的父类
        if (ClassUtils.isCglibProxyClass(clazz)) {
            String superClassName = clazz.getGenericSuperclass().getTypeName();
            try {
                clazz = Class.forName(superClassName);
            } catch (ClassNotFoundException e) {
                log.error(String.format("class not found: %s", superClassName));
                return;
            }
        }
        final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
        for (Method method : methods) {
            // 如果方法上有 SoulDubboClient 注解,则调用上面的 doRegister 方法注册元数据信息
            SoulDubboClient soulDubboClient = method.getAnnotation(SoulDubboClient.class);
            if (Objects.nonNull(soulDubboClient)) {
                RegisterUtils.doRegister(buildJsonParams(serviceBean, soulDubboClient, method), url, RpcTypeEnum.DUBBO);
            }
        }
    }

handler 方法通过识别 serviceBean 上的 SoulDubboClient 来判断需要注册哪些服务的元数据。

注意到这里使用 buildJsonParams 来构造元数据,其内容如下:

    private String buildJsonParams(final ServiceBean<?> serviceBean, final SoulDubboClient soulDubboClient, final Method method) {
        // 从 dubboConfig 取 appName,若未配置则使用 applicationName
        String appName = dubboConfig.getAppName();
        if (StringUtils.isEmpty(appName)) {
            appName = serviceBean.getApplication().getName();
        }
        String path = dubboConfig.getContextPath() + soulDubboClient.path();
        String desc = soulDubboClient.desc();
        String serviceName = serviceBean.getInterface();
        String configRuleName = soulDubboClient.ruleName();
        String ruleName = ("".equals(configRuleName)) ? path : configRuleName;
        String methodName = method.getName();
        Class<?>[] parameterTypesClazz = method.getParameterTypes();
        // 拼装方法参数类型名
        String parameterTypes = Arrays.stream(parameterTypesClazz).map(Class::getName)
                .collect(Collectors.joining(","));
        // 构造元数据并序列化为 json 串
        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);

    }

继续回到上面的handler,通过堆栈找到其调用方 AlibabaDubboServiceBeanPostProcessor.handler

    @Override
    public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
        if (Objects.nonNull(contextRefreshedEvent.getApplicationContext().getParent())) {
            return;
        }
        // Fix bug(https://github.com/dromara/soul/issues/415), upload dubbo metadata on ContextRefreshedEvent
        Map<String, ServiceBean> serviceBean = contextRefreshedEvent.getApplicationContext().getBeansOfType(ServiceBean.class);
        for (Map.Entry<String, ServiceBean> entry : serviceBean.entrySet()) {
            executorService.execute(() -> handler(entry.getValue()));
        }
    }

通过实现 ApplicationListener<ContextRefreshedEvent> 接口监听容器刷新事件,容器刷新后取到 ServiceBean 调用 handler 进行处理。

至此,alibaba dubbo 服务注册到网关的流程梳理清楚:

1)AlibabaDubboServiceBeanPostProcessor 监听到容器刷新后,从 spring 上下文取出 ServiceBean 调用 handler 进行处理

2)handler 拿到 ServiceBean 的接口或带泛型父类带 @SoulDubboClient 的方法,调用 RegisterUtils.doRegister 进行注册

3)RegisterUtils.doRegister 使用 OkHttpTools 将元数据 post 到 soul-admin 落库

4)soul-admin 再将元数据同步到网关内存

三、dubbo 插件如何工作的?

在此就不展开插件的统一工作流程了,只聚焦于 dubbo 插件本身的工作内容。

打开 soul-plugin-alibaba-dubbo 插件工程,定位到 AlibabaDubboPlugin.doExecute

    @Override
    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);
        }
        // 请求体缺失处理
        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);
    }

可以看到,进入 dubbo 插件处理以后,最核心的是从 exchange 拿到元数据和请求体后进行 dubbo 泛化调用。

进入 AlibabaDubboProxyService.genericInvoker

    public Object genericInvoker(final String body, final MetaData metaData) throws SoulException {
        // 拿到 ReferenceConfig
        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 进行泛化调用
        GenericService genericService = reference.get();
        try {
            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.getExceptionMessage());
        }
    }

可以看到,AlibabaDubboProxyService 只是封装了 alibaba 的 GenericService,先组织方法名、参数类型和参数,再交由 genericService 进行泛化调用。

四、总结

本篇从使用侧先梳理 dubbo 服务接入流程,再从 dubbo 服务注册到网关以及 dubbo 插件的工作内容进行分析,最终将 soul 代理 dubbo 服务的整个脉络串起来。

1)客户端发布 dubbo 服务,同时注册 dubbo 服务元数据到 soul-admin

2) soul-admin 同步元数据到 网关 jvm 内存

3)客户向网关发起 http 请求,访问目标为后端 dubbo 服务

4)网关拿到 requestBody 和内存中的元数据,解析成方法名、参数类型和参数值后发起 dubbo 泛化调用

5)客户端响应 dubbo 泛化调用并回吐响应报文

下一篇,将分析 soul 如何代理 sofa-rpc 服务。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值