文章目录

  • 目标
  • 一、使用 soul 代理 dubbo 服务
    • 1、dubbo 服务接入网关
      • 1.1 springboot 项目接入方式
      • 1.2 spring 项目接入方式
    • 2、配置 dubbo 插件
    • 3、注册 dubbo 服务到网关
    • 4、http 方式请求 dubbo 服务
  • 二、dubbo 服务如何注册到网关?
  • 三、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:9095contextPath: /dubboappName: 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,若未配置则使用 applicationNameString 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

    @Overridepublic 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 ContextRefreshedEventMap<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

    @Overrideprotected 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 {// 拿到 ReferenceConfigReferenceConfig<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 服务。

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

  1. Soul网关源码解析目录

    Soul网关源码解析目录 Soul网关源码解析文章列表     对用Java写的高性能网关:Soul,进行一波学习和研究,下面是相关的文章记录 掘金 了解与初步运行 Soul网关源码解析(一) 概览 ...

  2. Soul网关源码解析(二)代理Http请求

    如何读开源项目:对着文档跑demo,对着demo看代码,懂一点就开始试,有问题了问社区. 文章目录 今日目标: 一.从官方文档开始 1.接入说明: 2.网关需要引入代理插件 3.Http 服务接入网关 ...

  3. soul 网关源码解析

    一.soul网关引入的依赖分析 从上图可以看到我红线划分五个依赖区域 1.soul-common包:这里不是很重要,我们大概看一下他的作用就好了 从上图中可以看出,这个包里主要定义了一个常量,枚举类, ...

  4. soul网关源码解析-环境搭建

    项目功能简介 支持各种语言(http协议),支持 dubbo,springcloud协议. 插件化设计思想,插件热插拔,易扩展. 灵活的流量筛选,能满足各种流量控制. 内置丰富的插件支持,鉴权,限流, ...

  5. Soul 网关源码阅读(四)Dubbo请求概览

    Soul 网关源码阅读(四)Dubbo请求概览 简介     本次启动一个dubbo服务示例,初步探索Soul网关源码的Dubbo请求处理流程 示例运行 环境配置     在Soul源码clone下来 ...

  6. Soul网关源码阅读(八)路由匹配初探

    Soul网关源码阅读(八)路由匹配初探 简介      今日看看路由的匹配相关代码,查看HTTP的DividePlugin匹配 示例运行      使用HTTP的示例,运行Soul-Admin,Sou ...

  7. Soul网关源码阅读(七)限流插件初探

    Soul网关源码阅读(七)限流插件初探 简介     前面的文章中对处理流程探索的差不多了,今天来探索下限流插件:resilience4j 示例运行 环境配置     启动下MySQL和redis d ...

  8. Soul 网关源码阅读(六)Sofa请求处理概览

    Soul 网关源码阅读(六)Sofa请求处理概览 简介     今天来探索一下Sofa请求处理流程,看看和前面的HTTP.Dubbo有什么异同 Sofa示例运行 PS:如果请求加上参数运行不成功,请更 ...

  9. Soul网关源码阅读(十)自定义简单插件编写

    Soul网关源码阅读(十)自定义简单插件编写 简介     综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长 编写准备     首先我们先探究一下,一个P ...

最新文章

  1. pykafka连接重要使用pykafka,kafka-python的api开发kafka生产者和消费者
  2. heartbeat v2版CRM的高可用web集群的实现
  3. slam中特征点归一化原因以及方法
  4. django修改服务器名称,django部署和服务器配置教程
  5. php程序员笔试题库,2017年初级PHP程序员笔试题
  6. DSP 28335中GPIO配置
  7. 轩辕炼妖录java_一个Java对象的回忆录:那些被锁住的日子
  8. 静态代码块 构造代码块 构造方法的执行顺序
  9. 写给嵌入式方向的某些同学 - 基于WINCE系统的程序开发[不完整版]
  10. PHP的类中的常量,静态变量的问题。
  11. 高通:蓝牙5.0将可同时连接两个设备
  12. 3使用技巧_盆栽金钱树,平时使用“3个”技巧,叶子稠密、基部冒新芽
  13. Go语言Revel框架 环境搭建
  14. 分享线下活动丨物联网走进UIC
  15. 基因编辑最新研究进展(2022年3月)
  16. 证监会叫停VR等行业跨界定增,福兮祸兮?
  17. 使用cordova将Ext JS 6.2的Modern应用程序打包为安卓APP
  18. suse11 安装 apache 记录
  19. Mysql的快照读和当前读
  20. 《乘风破浪》三年,靠王心凌救市?

热门文章

  1. 区间dp,绝对值不等式
  2. 职场中年危机,可能只是你放水太多又不接受现实而已
  3. BUG的定义、分类、要素、生命周期
  4. mySQL下载后的初次使用
  5. Java中随机数的产生
  6. ElasticSearch之——Java操作ES实例(基于ES-2.3.0)
  7. 高中计算机应用基础试题及答案,春学期职业高中计算机应用基础试卷
  8. CSS3盒子模型-盒子模型的布局
  9. 计算机类SCI杂志排名
  10. 首届“十大最具价值”AR/VR创业项目遴选榜单丨Xtecher权威发布