dubbo服务执行过程源码分析

在之前的讲解中,我们说明了springboot和dubbo的整合使用,但对很多同学来说,可能还并不是很清楚dubbo内部的执行流程,如果要弄清楚dubbo的运行流程的话就需要跟踪源码进行分析,这是一个耗时耗力的过程,但如果搞清楚了这其中的原理,则对于我们理解dubbo这个框架有很好的帮助,

首先我们贴上dubbo的官方两张图,

第一张图是dubbo的基本的模块图,也叫逻辑结构抽象图,
在这里插入图片描述

我们解释以下这个架构图:

Consumer服务消费者,Provider服务提供者。
Container服务容器。消费当然是invoke提供者了,invoke这条实线按照图上的说明当然同步的意思了。但是在实际调用过程中,Provider的位置对于Consumer来说是透明的,上一次调用服务的位置(IP地址)和下一次调用服务的位置,是不确定的。这个地方就需要使用注册中心来实现软负载,通常我们使用zookeeper作为注册中心,这也是官方推荐的;

Register
服务提供者先启动start,然后注册register服务。消费订阅subscribe服务,如果没有订阅到自己想获得的服务,它会不断的尝试订阅。新的服务注册到注册中心以后,注册中心会将这些服务通过notify到消费者。

Monitor
这是一个监控,图中虚线表明Consumer 和Provider通过异步的方式发送消息至Monitor,Consumer和Provider会将信息存放在本地磁盘,平均1min会发送一次信息。Monitor在整个架构中是可选的(图中的虚线并不是可选的意思),Monitor功能需要单独配置,不配置或者配置以后,Monitor挂掉并不会影响服务的调用。

第二张图是dubbo运行流程分析图,也是dubbo的调用链路图,
在这里插入图片描述

这个图涉及到的东西太多,初看起来比较费劲,但如果搞清楚了里面的关键模块,基本上就理解了,

Dubbo框架设计一共划分了10个层,最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口, 位于中轴线上的为双方都用到的接口。
下面,结合Dubbo官方文档,我们分别理解一下框架分层架构中,各个层次的设计要点:

  1. 服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。

  2. 配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。

  3. 服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。

  4. 服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。

  5. 集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
    监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。

  6. 远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

  7. 信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。

  8. 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。

  9. 数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。

根据官方提供的,对于上述各层之间关系的描述,如下所示:

  • 在RPC中,Protocol是核心层,也就是只要有Protocol + Invoker + Exporter就可以完成非透明的RPC调用,然后在Invoker的主过程上Filter拦截点。

  • 图中的Consumer和Provider是抽象概念,只是想让看图者更直观的了解哪些类分属于客户端与服务器端,不用Client和Server的原因是Dubbo在很多场景下都使用Provider、Consumer、Registry、Monitor划分逻辑拓普节点,保持统一概念。

  • 而Cluster是外围概念,所以Cluster的目的是将多个Invoker伪装成一个Invoker,这样其它人只要关注Protocol层Invoker即可,加上Cluster或者去掉Cluster对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster的。

  • Proxy层封装了所有接口的透明化代理,而在其它层都以Invoker为中心,只有到了暴露给用户使用时,才用Proxy将Invoker转成接口,或将接口实现转成Invoker,也就是去掉Proxy层RPC是可以Run的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。

  • 而Remoting实现是Dubbo协议的实现,如果你选择RMI协议,整个Remoting都不会用上,Remoting内部再划为Transport传输层和Exchange信息交换层,Transport层只负责单向消息传输,是对Mina、Netty、Grizzly的抽象,它也可以扩展UDP传输,而Exchange层是在传输层之上封装了Request-Response语义。

  • Registry和Monitor实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。

以上为dubbo的一些基本的概念和术语,主要是便于我们对dubbo的结构有一个全局的理解,在解读源码的时候更容易理解,下面,我们从dubbo的服务暴露一端,也就是生产者一方,来说明服务是如何暴露出去的,

在spring或springboot和dubbo整合的时候,我们一启动服务,spring容器加载的时候就会去查找相应的bean,如果用spring 的xml形式整合dubbo,则xml的配置文件就会被加载解析,

在这里插入图片描述

即如上图中的以dubbo开头的标签将作为一个个bean被spring的标签解析器解析,所以,暴露服务的第一步就是容器启动的时候,创建和dubbo相对应的标签解析器,我们知道spring中有一个很重要的标签解析器,叫BeanDefinitionParser,在其实现类中,具体下去对应着解析各类模块的解析器,其中我们看到有一个叫做DubboBeanDefinitionParser的解析器,这个就是用来解析dubbo的配置文件中的标签的,即以dubbo开头的那些标签,

在这里插入图片描述

在这个标签解析器中,通过断点首先进入parse这个方法,即解析xml标签的方法,
在这里插入图片描述

这个方法的作用是干什么的呢?通过断点我们进入该方法,并执行一遍流程,总结来说,这个方法的目的就是,解析xml文件中以dubbo开头的那些bean,解析出来后,分别存放到对应的实体类,也就是dubbo提前预置的实体类中,

在这里插入图片描述

这个初始化的方法会在解析器构造函数执行的时候执行,解析xml文件中的各个标签,然后将各个bean里面的属性和对应的值放到相应的config实体中以供后面使用,

在这里插入图片描述

那么,这个parse方法中后面要做的工作其实就是一个个去解析各个标签对应的bean然后取出属性值,分类放到各自对应的实体类中,解析完毕后,返回beanDefinition;

在上述解析各个标签的bean中,有一个很重要的bean,就是serviceBean,这个bean是我们服务调用的时候的bean,所以分析的时候需要从这个bean着手,

我们进入serviceBean看一下,
在这里插入图片描述

这个serviceBean实现了InitializingBean中的afterPropertiesSet方法,也就是当spring容器加载完毕后会执行这个方法,我们来看看afterPropertiesSet这个方法做了什么,

在这里插入图片描述

通过断点去看,其实可以看到,这个地方主要是为了解析关于provider的配置标签bean,然后将解析出来的属性值放到预置的实体类中供后面使用,保存的实体是serviceBean,解析并保存完标签的属性值以后,断点进入onApplicationEvent 这个方法,

这个也很好理解,因为serviceBean还实现了ApplicationContextAware 这个方法,则在spring容器启动完毕就会触发这个回调的方法,
在这里插入图片描述

我们进入onApplicationEvent这个方法看看,
在这里插入图片描述

这个方法的用意很明显,就是当生产者的bean标签解析完毕之后,容器刷新并回调这个方法,将标签中对应的生产者提供的接口暴露出去,怎么暴露呢?我们直接进入这个export方法看看,

这个方法的实现在ServiceConfig中,跟着断点走,我们定位到doExport这个方法,也就是具体实现服务暴露的方法,怎么暴露的呢?我们继续看,
在这里插入图片描述

点进去,我们最终可以定位到上述方法的这个位置,这个方法的其他内容大致要做的事情就是各种检查判断,有兴趣的童鞋可以点进去瞧瞧,跟着断点我们进入doExportUrls这个方法里面去,
在这里插入图片描述

进入doExportUrls方法,断点可以看到,这个方法先去加载注册中心的信息,注册中心的信息从哪里来,也是我们第一步标签解析出来的时候的属性啊,被放到RegistryConfig这个实体类了,获取到注册中心的信息后,就开始执行服务接口的暴露了,
在这里插入图片描述

这里有个问题需要注意一下,如果我们配置了多个注册中心,比如还有20883,20884之类的,在上述的方法里面将会通过for循环暴露出去,也就是多次执行这个方法啊,
doExportUrlsFor1Protocol(protocolConfig, registryURLs)

在这里插入图片描述

进入doExportUrlsFor1Protocol这个方法,我们找到方法中关键的位置,
在这里插入图片描述

通过断点定位到这个位置,这段代码的意思就很明显了,通过代理工厂将我们要暴露的服务接口,接口实现类,以及注册的地址包装起来成为一个新的对象然后丢出去,丢给这个方法去执行,protocol.export(wrapperInvoker),我们进入protocol去看看,发现这个接口里面的实现类很多,这里我们使用的是dubbo的protocol,和RegistryProtocol,

在这里插入图片描述

断点之后,首先会进入registry的protocol的export这个方法里面,这个很好理解,暴露的时候先被注册中心捕获到,
在这里插入图片描述

在这个方法里面有两处值得注意,一个是doLocalExport()这个方法,即执行本地的暴露,另外一个是
ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
这个方法的作用就是将服务提供者的信息,如要暴露的服务列表信息保存到一个地址列表中,以供后面消费者从服务地址列表中获取,

先看doLocalExport()这个方法,断点进入,本地暴露方法最终要进入dubbo的protocol的暴露方法中去,
在这里插入图片描述

这个地方可能大家有一个疑问,为什么不直接是让dubbo去执行暴露的方法呢?不难理解,我们的服务首先是要挂到注册中心上去的,如果不挂到注册中心而直接暴露出去,这就等于是直连了啊,而且注册中心可以做集群,然后做统一的服务管理、服务协调和高可用了,也就是先注册到注册中心,然后dubbo的暴露方法被调用,dubbo的export()做了什么呢?

在这里插入图片描述

和前面的export类似,这里也是先将前面包装在invoker中的服务接口等信息取出来,然后交给新构建的暴露器,其实最主要的还是最后那句代码,openServer(url),很好理解,你总要将信息通过某种渠道发布出去吧,发布到哪里呢?自然是某个服务器连接的通道啊,

进入openServer这个方法看看,
在这里插入图片描述

这里将执行创建一个服务器的逻辑,其实我们看依赖的jar包也可以看到,dubbo的底层最终调用的是netty服务,也就是使用netty做为通信的服务器,继续进入createServer这个方法,

在这里插入图片描述

这个方法里面有一段关键的代码,就是通过交换器 Exchangers.bind(url, requestHandler)
来绑定url对应的服务器,一路点击进去,我们可以看到,
在这里插入图片描述

最后就是调用了netty底层的那一套方法,关于netty,这里不做过多说明,大家有兴趣可以找一些资料去学习,很多做通讯类的中间件底层都有netty的影子,netty在做消息的传输方面性能很好,

回到最初的RegistryProtocol 的export 方法里面,上面执行完了doLocalExport这个方法后,dubbo开启了一个netty服务器的连接通道,将服务地址挂上去了,
在这里插入图片描述

接下来会将服务信息通过registerProvider这个方法注册到一个注册列表中,以供消费者查找,怎么注册的呢?我们断点进入这个方法看看,
在这里插入图片描述

在这个方法里,最终要做的就是将包装在invoker里面的服务信息,registryUrl注册中心的地址,providerUrl 服务提供者的地址一起存放到一个用set包装的invoker里面,后面消费者端使用的时候,只需要从这个invoker里面获取即可拿到全部的信息;

至此,以上我们大致走了一遍dubbo的服务暴露流程,虽然有点儿复杂,但主要就是那么几步,搞清楚关键的几个步骤和原理,基本上就ok了,下面用一张时序图来简单总结诶一下上面的服务暴露过程,

在这里插入图片描述

还剩下的有关消费者调用的执行流程,大家可以使用相同的方式通过断点去调试一下,亲炙感受一下调用的流程,方法类似,这里就不再赘述了,希望对各位童鞋理解dubbo有所帮助,不足之处,敬请谅解,最后,感谢浏览!

发布了193 篇原创文章 · 获赞 113 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/zhangcongyi420/article/details/85470082