十分钟,带你快速掌握 Dubbo 的架构设计

我以前在阅读源码的时候,经常会陷入到实现细节里。看着看着就不知道走到哪里去了,即便是debug,也只是把代码的执行流程看懂了。有时候,即使当时是看懂了,可是用不了多久就忘了。

后来,随着阅读的源码越来越多,我发现,通过拆分框架的模块结构,梳理清楚各个接口(类)之间的关系(继承、实现、依赖等),对我理解源码的实现原理有很大帮助。比如说,在 debug 的时候能猜测出大概的执行逻辑,便于更好的跟踪源码。同时,这种拆分和梳理的方式,也让我对实现原理的记忆时间更长。

所以,本文将从 Dubbo 整体架构的视角出发,带你从全局俯瞰 Dubbo 的架构设计。具体来说,本文将先从 Dubbo 的基本组成模块开始分析,然后不断拆分各个模块,最后带你形成对 Dubbo 由粗到细、由全局到细节的完整认知。

整体架构

我们先从 Dubbo 的基本组成模块开始分析。

image.png 如图所示,Dubbo 由 4 个基本模块组成,分别为 Registry、Consumer、Provider 和 Monitor。

  • Registry:注册中心,用于服务注册与发现。
  • Consumer:服务消费者,远程服务的调用方。
  • Provider:服务提供者,包装了服务实现,并暴露服务。
  • Monitor:服务监控中心,用于统计服务的调用次数和调用时间等信息。

下面,我们逐个拆解并剖析上面的基本模块。

注册中心

首先拆解并剖析注册中心模块。

image.png 如图所示,注册中心,可以分为两个部分,一个是第三方存储介质,它支持存储服务信息和通知服务变更功能,像Zookeeper、Redis 等都可以作为注册中心。另一个是 Dubbo 框架中对注册中心的抽象,Dubbo 将注册中心抽象为以Registry为核心的注册中心组件,将这些组件引入到ConsumerProvider后,就可以执行注册中心的相关操作了,如注册服务、订阅服务变更事件和通知服务变更等。

本文中所拆解的注册中心,指的是 Dubbo 中以Registry为核心的注册中心组件。

由上图可知,Dubbo 中注册中心模块的核心组件包含RegistryRegistryFactoryDirectoryNotifyListener组件。

  • Registry:是对注册中心的抽象,一个Registry就代表一个注册中心。
  • RegistryFactory:注册中心工厂,用于在初始化时创建Registry
  • Directory:服务目录,用于刷新和保存可用于远程调用的Invoker,严格来说,服务目录是一个公用组件,它既可以划分到注册中心,也可以划分到下文中服务容错的模块里,因为这两个功能模块里都用到了Directory(服务目录)。
  • NotifyListener:定义了通知(notify())接口,此接口的实现类用于接收服务变更的通知。 图中的RegistryDirectory同时实现了DirectoryNotifyListener,它既实现了刷新和保存可用于远程调用的Invoker的功能,也实现了接收服务变更通知的功能。

通过以上核心组件,联合实现了注册中心相关的功能,如注册服务、订阅服务变更事件、通知服务变更和拉取服务信息等功能。

服务消费者

下面拆解并剖析服务消费者。

服务消费者中的内容比较多,这里单独拿出来剖析。

image.png 上图中,从两个角度剖析了服务消费者。一个是从组成部分的角度,将服务消费者拆分为 Registry、Proxy、Protocol、Cluster、Invoker 和 Client 六大组成部分。另一个是从执行流程的角度,将其分为初始化、请求和响应三步。

官网中,根据组件的定位将其分成了 10 层,拆分的很细。但是我感觉这里如果分的太细会比较琐碎,不好理解,因此我在这里将其分成了 6 个部分。在理解这样划分的基础上,再往更细粒度的拆分,也会更加简单。

下面先从组成部分的角度分析,图中每个虚线大方块都表示一个组成部分。

  • Registry:即注册中心,前文已经分析过了,通过RegistryFactoryRegistryDirectoryNotifyLisetener联合实现注册中心相关的功能,包括subscriberegisterlookupnotify等。
  • Proxy:即服务代理,用于代理依赖的接口。在使用过程中,通过<dubbo:reference />标签配置完依赖的接口后,就会生成一个代理。当调用接口时,实际上调用的是这个代理类。
  • Protocol:服务协议,它相当于一个中间层,用于封装 RPC 调用。它在初始化时会创建用于远程调用的Invoker,并通过调用Client模块与服务端建立连接。
  • Cluster:服务集群,内部的主要功能是服务容错。内部包括了ClusterClusterInvokerDirectoryRouterLoadBalance等组件。
  • Invoker:服务调用者,其内部通过调用Client模块,完成与服务端的通讯(请求和响应)。
  • Client:客户端模块,我将ExchangerTransporterClientSerialize等组件全部都划到了客户端模块里,因为这些组件共同实现了connect(与服务端建立连接)、request(发送双向通讯请求)、send(发送单向通讯请求)和received(接收响应消息)等功能。

下面再从执行流程的角度分析,图中的红色箭头表示初始化流程,绿色箭头表示发送请求流程,蓝色箭头表示接收响应结果流程。

初始化流程

如上图所示,初始化流程从ReferenceConfig发起,其最终目的是生成Proxy服务代理。过程中,先通过Registry注册中心订阅服务变更事件,并在第一次初始化时主动执行notify通知服务变更,通过DubboProtocol调用Client模块与服务端建立连接,并生成DubboInvoker。同时,生成用于服务容错的ClusterInvoker。其中在Client模块中建立与服务端的连接时,依次通过ExchangerTransporter组件,最终通过Client组件完成与服务端的网络连接。

发送请求流程

当调用<dubbo:reference />标签配置的依赖的接口时,实际上是调用的Proxy域中的服务代理。

在调用过程中,Proxy会通过Cluster服务容错模块调用DubboInvokerDubboInvoker最终通过Client组件向服务提供者发送请求。其中服务容错的功能由DirectoryRouteLoadBalance等组件共同组合完成;DubboInvoker在发送请求后会通过AsyncToSyncInvoker阻塞等待结果;Client向服务提供者发送消息之前,要通过Serialize组件对请求消息进行编码。

接收响应结果流程

服务提供者处理完请求后,会向服务提供者发送响应结果。服务消费者接收到响应数据后,先通过Serialize组件对响应消息解码,然后通过ChannelHandler组件将响应结果分发到服务消费者的线程池里,最终唤醒上一步阻塞等待结果的AsyncToSyncInvoker。最后,将响应结果返回给接口调用方。

服务提供者

下面拆解并剖析服务提供者。 image.png 和剖析服务消费者一样,这里也是从两个角度剖析了服务提供者。一个是从组成部分的角度,将服务提供者拆分为 Registry、Proxy、Protocol、Invoker 和 Server 五大组成部分。另一个是从执行流程的角度,将其分为初始化、接收并处理请求和发送响应结果三步。

服务提供者的组成部分在剖析服务消费者的时候大部分都解释过了,这里只说下不同之处。

  1. 通讯模块,在服务消费者中叫 Client 模块,而在服务提供者中叫 Server 模块,里面的组件基本相同。
  2. 服务消费者中有 Cluster 集群容错模块,而服务提供者中没有。
  3. 在 Invoker 模块中,服务消费者中的InvokerDubboInvoker,相对来说内容较多,封装了远程调用;服务提供者中的InvokerAbstractProxyInvoker,内容较简单,用于调用本地生成的Proxy
  4. Proxy 模块中,服务消费者的配置类是ReferenceConfig,服务提供者的配置类是ServiceConfig
  5. Proxy 模块中,服务消费者的代理类用于封装调用远程方法的细节,服务提供者的代理类用于调用本地实现类。

下面再从执行流程的角度分析,图中的红色箭头表示初始化流程,绿色箭头表示接收并处理请求流程,蓝色箭头表示发送响应结果流程。

初始化流程

服务提供者的初始化流程,是从 Proxy 模块中的ServiceConfig发起的。它首先通过 Protocol 模块调用Server 模块,启动服务;然后生成用于调用本地实现类的 Proxy,并创建用于调用 Proxy 的AbstractProxyInvoker;最后通过 Registry 模块,将服务信息注册到注册中心。

接收并处理请求流程

服务提供者的 Server 接收到请求后,首先通过Seralize组件解码,然后交给ChannelHandler处理,在ChannelHandler的处理过程中,分发到ThreadPool中进行业务处理。

HeaderExchangeHandler进行最终的业务处理调度,通过ExchangeHandlerAbstractProxyInvoker调用本地代理和本地实现,并得到最终的业务处理结果。

发送响应结果流程

HeaderExchangeHandler得到业务结果后,通过Channel向Server的对端(服务消费者)发送响应结果。在将响应结果发送到对端(服务消费者)之前,会通过Seralize组件对响应结果编码。

发送完成之后,就走到了前文中服务消费者接收响应结果流程。

总结

根据以上的分析结果,Dubbo 整体架构如下图所示。

image.png

从组成模块层面分析,Dubbo 包括注册中心、服务消费者、服务提供者和监控中心四大模块。注册中心包括两部分,一个是用于存储和通知服务信息的第三方存储媒介,另一个是 Dubbo 框架中抽象出的 Registry 模块。服务消费者由 Proxy、Protocol、Cluster、Invoker、Registry 和 Client 六大模块组成。服务提供者由 Proxy、Protocol、Invoker、Registry 和 Server 五大模块组成。

从整体的执行流程上分析。

  • 服务提供者初始化,启动服务,生成本地服务代理,并将服务注册到注册中心里。
  • 服务消费者初始化,连接服务提供者,订阅服务注册中心中的变更事件,接收服务变更通知,生成远程调用的服务代理。
  • 服务消费者发送请求,经过服务容错、远程调用、消息编码等过程,将请求消息发送到服务提供者。
  • 服务提供者接收到请求数据,并经过消息解码、分发处理、调用本地实现等过程,得到业务处理结果。
  • 服务提供者得到业务处理结果后,将响应消息编码后,通过Channel将响应结果发送到服务消费者。
  • 服务消费者接收响应结果,解码响应结果,唤醒远程调用的阻塞等待。
  • 业务调用方得到最终的远程调用结果。
  • 此外,在监控中心模块中,会定时将调度统计信息上送到监控中心。

至此,Dubbo 的整体架构就介绍完了。在后面分析 Dubbo 的实现原理时,我们也会继续按照本文的思路,分别从执行流程和核心组件两个角度进行详细分析。

温馨提示

本文是从全局的角度对 Dubbo 架构的整体分析,是对 Dubbo 框架的高度概括说明,不包含实现细节,所以内容比较密集,如果你没有之前了解过 Dubbo,可能看起来会比较吃力。

如果你了解过 Dubbo,但还是对本文有些困惑,这也非常正常。我建议你看完后面的文章后再回来看这一节,就会很轻松了。到那时,你肯定也会有更多不一样的感受。

想系统深入理解Dubbo架构设计和实现原理,请阅读:《深入剖析 Dubbo 架构设计和实现原理》

版权声明:本文于2022年8月17日发布在掘金平台,谢绝转载!

猜你喜欢

转载自juejin.im/post/7132680001245151269