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


简介

    本次启动一个dubbo服务示例,初步探索Soul网关源码的Dubbo请求处理流程

示例运行

环境配置

    在Soul源码clone下来以后,有一个 soul-example 目录,这个就是示例工程,里面有很多的示例可以运行

    可能初始文件夹不被IDEA识别为Java工程,我们需要点击改工程目录下的 pom.xml 文件,然后在右键,在菜单中选择:add as maven project

    我们选择运行:soul-examples --> soul-examples-apache-dubbo-service

    此次示例需要mysql和zookeeper,我们使用docker启动一下,记得修改 soul-admin 的数据库配置,其他的都不需要懂,猜测zk有默认配置

docker run -dit --name zk -p 2181:2181 zookeepe
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latest

测试运行

    然后运行 Soul-admin、Soul-bootstrap,可以参考:Soul 源码阅读(二)代码初步运行

    运行soul-examples-apache-dubbo-service

    登录 soul-admin 管理界面:http://localhost:9095/ ,账号和密码:admin 123456

    进入界面:系统管理 --> 插件管理

    在插件:dubbo,点击编辑,将状态修改为开启(是个开关图标)

    开启成功后,可以在Soul-Bootstrap中看到相关的日志,大致如下:

o.d.s.p.a.d.c.ApplicationConfigCache     : init aliaba dubbo reference success there meteData is :MetaData
o.d.s.p.a.d.c.ApplicationConfigCache     : init aliaba dubbo reference success there meteData is :MetaData
o.d.s.p.a.d.c.ApplicationConfigCache     : init aliaba dubbo reference success there meteData is :MetaData

    进入Soul-admin管理界面:插件管理 --> dubbo ,我们可以看到很多的配置,这些都是soul-examples-apache-dubbo-service的配置

    随便找个查询的接口:http://localhost:9195/dubbo/findAll

    使用浏览器访问得到的结果如下:

{"code": 200,"message": "Access to success!","data": {"name": "hello world Soul Apache, findAll","id": "-1206394682"}
}

    很好,已经成功跑通示例,接下来,进入源码进行debug

源码分析

    基于上篇,我们已经知道了一个HTTP请求的初步处理流程:soul源码阅读3-请求处理概览.md

    基于上篇的熟悉度,我们就开始看看Dubbo的处理流程和Http的有什么区别,顺带看一看各个Plugin都干了啥

    Plugins的链式处理核心类是:SoulWebHandler,我们就从这里开始打上断点,逐步进入每个Plugin进行查看

        public Mono<Void> execute(final ServerWebExchange exchange) {return Mono.defer(() -> {if (this.index < plugins.size()) {SoulPlugin plugin = plugins.get(this.index++);Boolean skip = plugin.skip(exchange);if (skip) {return this.execute(exchange);}return plugin.execute(exchange, this);}return Mono.empty();});}

GlobalPlugin

    代码看的不是很懂,猜测有下面的功能:

  • 获取请求头的 upgrade,此时为null,进入逻辑
  • 另外的分支的MultiValueMap有点像文件上传之类需要的,后面有时间验证一下
  • 功能是修改了SoulContext

    这个不是此次分析的目标,到这就跳过

SignPlugin

    这个应该是一个认证相关的插件,但在获取插件信息,没有开启,不进入逻辑,直接跳过,没有进入具体执行

    这里发现有的能需要进入函数:execute,有的直接进入:doExecute。稍微有点好奇,就看了下原因,大致是直接继承SoulPlugin就直接进入doExecute,而 AbstractSoulPlugin需要进入execute,这里不是重点,就不继续研究这个了,有个大概了解即可

WafPlugin、RateLimiterPlugin、HystrixPlugin、Resilience4JPlugin

    上面几个都是获取插件信息,没有开启,不进入逻辑,直接跳过

DividePlugin

    这里是true,有点和自己想的不一样,查看管理界面看到divide是开启的,稍微挖一下这个

    定位到关键函数:Boolean skip = plugin.skip(exchange),判断是否可以跳过,我们看下具体的代码:

    public Boolean skip(final ServerWebExchange exchange) {final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);return !Objects.equals(Objects.requireNonNull(soulContext).getRpcType(), RpcTypeEnum.HTTP.getName());}

    好像就是判断类型来返回是否跳过,其他的plugins的判断逻辑,至于这些都是都是怎么来的,今天就不分析了,保留体力,留待下次进行分析

WebClientPulugin、WebsocketPlugin

    skip 为 true,直接跳过,跳过的判断的和DividePlugin基本相同,通过类型判断

BodyParamPlugin

    对soulContext进行了操作,和GlobalPlugin有一些联动,还看到了MediaType等关键字,感觉很像文件上传之类的,但细节不太清楚,这个也不是本次分析的目的,不要陷入细节,这次放过它,下次有时间再仔细研究

AlibabaDubblePlugin

    这个插件是本次的核心,大致干了下面三个事情

  • 进入了并且匹配上了规则,发现这个plugin是dubbo总插件,同样能处理Apache dubbo,也就是他们是相同的或者复用的
  • 进入到doExecute函数:获取body,soulContext,查看soulContext发现已经有方法、路径等信息(猜测是前面加的),获得关键的metaData
  • 获取请求的结果,并且放到exchange中:Object result = 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;// dubbo 请求的相关数据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);}// 获取请求结果Object result = alibabaDubboProxyService.genericInvoker(body, metaData);if (Objects.nonNull(result)) {// 将结果放到exchange中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);}

    继续跟踪查看获取result的那个函数,通过下面的代码可以明显看出确实是rpc调用,并得到结果

    public Object genericInvoker(final String body, final MetaData metaData) throws SoulException {// 通过rpc和dubbo的相关知识,这里的reference相当于consumer,metaData.getPath()==/dubbo/findAll// ApplicationConfigCache.getInstance().get 的逻辑感觉比较复杂,先不看了,大体感觉是初始化的时候使用 path 作为了 keyReferenceConfig<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);}// 这段很像RPC Demo中的获取字节码生成的对象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());}// 传入方法名、参数名、参数?Dubbo还没用的熟练,暂时这样猜一猜,不影响此次分析的大局return genericService.$invoke(metaData.getMethodName(), pair.getLeft(), pair.getRight());} catch (GenericException e) {log.error("dubbo invoker have exception", e);throw new SoulException(e.getExceptionMessage());}}

    对reference.get(),有点好奇,稍微看下 ReferenceConfig

    public synchronized T get() {// 判断是否能用if (this.destroyed) {throw new IllegalStateException("Already destroyed!");} else {// 有延迟加载的作用if (this.ref == null) {this.init();}return this.ref;}}

    看到它这用法还是挺巧妙的,有一个延迟加载的效果,好像可以按照这个思路去改一改自己的RPC Demo的客户端代理生成,哈哈

MonitorPlugin

    获取插件信息,没有开启,不进入逻辑,直接跳过

WebClientResponsePlugin

    skip = true,直接跳过

DubboResponsePlugin

    看名字就知道是个核心类,我们在then后执行的代码段打个断点(这种需要单独打个端口,后面才能跳进去)

    public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {return chain.execute(exchange).then(Mono.defer(() -> {// 从exchange中拿到结果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));// 进入后使用之前熟悉的:exchange.getResponse().writeWith,返回响应给客户端return WebFluxResultUtils.result(exchange, success);} catch (SoulException e) {return Mono.empty();}}));}

    执行完plugins链后进入到这个断点:大致是获取结果result,加工得到success

    然后WebFluxResultUtils.result(exchange, success),进入后是:exchange.getResponse().writeWith,返回响应给客户端

    到这里,一个Dubbo请求的处理流程大致展示在我们面前了,有了前面几篇的分析基础,还是挺流畅的

总结

    此次分析验证了在上篇中的一些猜想,也得到了一些新的东西,请求处理的大致流程图如下:

  • HttpServerOperations : 明显的netty的请求接收的地方,请求入口
  • ReactorHttpHandlerAdapter :生成response和request
  • HttpWebHandlerAdapter :exchange 的生成
  • FilteringWebHandler : filter 操作
  • SoulWebHandler :plugins调用

    可以参考下上篇分析: Soul 源码阅读(三)HTTP请求处理概览

    这篇中我们详细分析各个Dubbo 经过的 Plugin 的相关处理,有些复杂的就没有看了,但也得到我们此次分析想要的结果,脑海中对一个RPC请求如果进行处理,设计那些组件有了一定的了解,基于这些了解也能进行更近一步的分析

    我们收获和HTTP请求调用、RPC请求调用、Websocket请求调用是互斥的,通过判断请求的类型来进行选择其中一个,而HTTP相应、RPC响应都会转为HTTP响应,然后返回给客户端,exchange里面估计是绑定了一个socket,然后直接调用即可(Netty网关Demo又可以进行借鉴了)

    此次分析有了下面新的疑问:

  • Websocket的响应的返回时怎样的?是长连接吗?因为没有看到Websocket响应相关的处理类
  • exchange中的请求类型是怎么绑定,如何生成的?
  • 限流等插件也是需要进行匹配吗?具体执行逻辑是怎样的?

    上面这些问题就留待以后分析了,慢慢来才能可持续发展

Soul网关源码分析文章列表

  • Soul 源码阅读(一) 概览
  • Soul 源码阅读(二)代码初步运行
  • Soul 源码阅读(三)HTTP请求处理概览

Soul 网关源码阅读(四)Dubbo请求概览相关推荐

  1. Soul 网关源码阅读(一) 概览

    Soul 源码阅读(一) 概览 简介     阅读soul的官方文档,大致了解soul的功能和相关概念 心得     需要对网关的功能有个大致的了解,把soul官方文档读两遍(第一遍通读,能看懂多少是 ...

  2. Soul网关源码阅读番外篇(一) HTTP参数请求错误

    Soul网关源码阅读番外篇(一) HTTP参数请求错误 共同作者:石立 萧 * 简介     在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索 ...

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

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

  4. Soul网关源码阅读(九)插件配置加载初探

    Soul网关源码阅读(九)插件配置加载初探 简介     今日来探索一下插件的初始化,及相关的配置的加载 源码Debug 插件初始化     首先来到我们非常熟悉的插件链调用的类: SoulWebHa ...

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

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

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

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

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

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

  8. Soul网关源码阅读(六)请求类型探索

    Soul网关源码阅读(六)请求类型探索 简介     在上几篇文章中分析了请求的处理流程,HTTP和RPC请求处理是互斥的,通过请求类型来判断,这篇文章来探索下请求类型的前世今生 源码分析     通 ...

  9. Soul 网关源码阅读(二)代码初步运行

    Soul 源码阅读(二)代码初步运行 简介     基于上篇:Soul 源码阅读(一) 概览,这部分跑一下Soul网关的示例 过程记录     现在我们可以根据地图,稍微探索一下周边,摸一摸      ...

最新文章

  1. VS Code 安装插件、自定义模板、自定义配置参数、自定义主题、配置参数说明、常用的扩展插件
  2. 外贸网站制作 网页的宽度多少为合适
  3. Openstack组件部署 — Keystone Install Create service entity and API endpoints
  4. hdu 4614 线段树
  5. jQuery遍历(1)
  6. 编写高性能的C#代码(三)使用SPAN
  7. ios UIScrollView 基础属性
  8. 中国电信:张志勇辞任公司执行副总裁
  9. java程序员目标_Java程序员的目标,你都达到了多少条?
  10. linux下dhcp配置(二)
  11. 有效的数独 python_LeetCode 36. 有效的数独 | Python
  12. 屏幕控制实现消息发送以及轰炸
  13. html5在线聊天插件,纯js网页在线聊天对话插件(原创)
  14. h5直播|微直播weLiveShow|视频h5|video直播
  15. 完美C Perfect C 丰胸胶囊
  16. 次坐标从0开始_定位基础-坐标变换
  17. 解决 Ubuntu 安装显卡驱动后,屏幕变黄的原因
  18. 基于SDR的智能反射面波束成形设计
  19. 无传感器永磁同步电机电机自适应自抗扰ADRC控制策略
  20. 非计算机专业人员的程序之路

热门文章

  1. AngularJs -- 模 块
  2. java抛出异常thorw和throws的用法
  3. 解决OpenCV JavaCameraView相机preview方向问题
  4. laravel5集成支付宝alipay扫码支付流程(Laravel 支付解决方案)
  5. 解决页面上JS文件加载过慢问题
  6. System.UriFormatException: Invalid URI 解决方法
  7. NeurIPS 2020 | AI编程:如何从复制粘贴走向推理合成(文末附论文及代码)
  8. 【白皮书分享】2020中国美颜消费趋势白皮书.pdf(附下载链接)
  9. 【白皮书分享】中国新能源汽车供应链白皮书2020.pdf(附下载链接)
  10. 比特币区块链如何运作?