购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 基于架构演进过程剖析代码结构

在上一节中我们从组件设计原则出发,讨论了“为什么这个框架的代码结构要这么设计”这一问题。这一问题的提出背景是很多开发人员在学习开源框架的源码时通常都会陷入代码的细节而无法把握代码的整体结构。在本节中,我们同样基于这一背景,从另一个角度出发讨论如何把握框架整体结构,即基于架构演进过程剖析代码结构。

2.2.1 如何从易到难对框架进行逐步拆解

我们再来回顾一下Dubbo框架。Dubbo源代码包含common、remoting、rpc、cluster、registry、monitor、config和container 8大核心包结构,这些包基于稳定抽象、稳定依赖等组件设计原则进行设计。从组件设计上讲,我们看到的是一个成熟、稳定且符合设计原则的Dubbo框架,但我相信Dubbo的开发人员也不是一开始就把Dubbo设计成现在这个代码结构的。如果现在让我们自己来设计这样一个框架,我们通常会采用一定的策略,从简单到复杂、从核心功能到辅助机制,逐步实现和完善,这也是软件开发的一条基本规律。从这个角度看,当我们想要解读Dubbo这样的框架源码而又觉得无从下手时,可以考虑一个核心问题:如何从易到难对框架进行逐步拆解?

Dubbo是一个典型的RPC框架,关于Dubbo中的RPC的实现方式,我们后续会有详细介绍,这里不展开讨论。在此之前,我们可以思考一下设计和实现一个RPC过程的基本思路和步骤,如图2-18所示。

图2-18 基础RPC实现的基本步骤

在图2-18中,我们看到首先需要实现一个最基础的RPC组件,用来完成服务提供者和消费者之间一对一的远程访问。

有了RPC基础组件之后,我们就能够实现远程通信了。对此,有些开发人员很可能采用在代码中嵌入远程服务信息的网络通信手段。这种做法比较直接,但对一个服务框架而言显然是不利于其系统开发和集成的,这时候我们就希望通过一定的手段来实现远程调用本地化,即实现透明化远程调用。目前实现这一目标的主流做法就是使用代理。

现在服务访问已经非常简单了,但还只是一对一的模式,需要对其添加集群访问模式。集群相关组件能够为我们提供负载均衡、集群容错等机制。

最后,如果RPC中的服务提供者和消费者的数量逐渐增多,那么就需要添加服务治理机制,实现服务的自动发现和注册。同时,为了监控系统中服务的状态,一般还需要采用一定的服务监控措施。

2.2.2 Dubbo的架构演进过程

Dubbo作为一款优秀的RPC框架,在整个代码结构的设计过程中也基本采用了上述思路。同时,Dubbo也充分体现了功能演进思想,图2-19展示的就是Dubbo代码结构的逐步演进过程。

图2-19 Dubbo框架代码结构演进图

显然,以上代码结构体现了从简单到复杂、从核心功能到辅助机制逐步实现和完善的设计过程。

□ 首先Protocol是核心层,提供RPC基础组件,如果不需要达到透明化调用的效果,使用Protocol层就可以实现低层次的远程方法调用。

□ 在Protocol基础之上,添加Proxy封装透明化动态代理,调用远程方法和调用本地方法一样。

□ 透明化调用实现之后,就需要考虑负载均衡和集群容错机制,Cluster层承载了这方面的功能。

□ 在现有各种序列化工具的基础上,为了提升网络传输性能和扩展功能,Remoting层实现了自定义Dubbo协议并将其作为整个框架的一大扩展点。

□ Registry和Monitor分别提供服务治理和监控相关的辅助功能。

在本节后续内容中,我们将围绕图2-19中的5个阶段来对Dubbo的代码结构进行梳理,帮读者从宏观上把握Dubbo。请注意,我们现在讨论的还是代码整体结构,不会也不应该对每个组件的内部结构和实现方法做过多讲解,后文会结合Dubbo框架详细阐述这些组件的实现方法和细节。

1.Protocol:RPC基础组件

Protocol作为Dubbo中最基本的RPC组件,完成了服务的发布和调用功能。Protocol接口定义如代码清单2-13所示。

代码清单2-13 Protocol接口定义代码

为了先从结构上对Protocol有基本了解和把握,这里去掉了Protocol接口上原有的@SPI("dubbo")和@Adaptive等核心注解。本节介绍其他接口或类时也会先忽略这些注解以降低理解难度,而在第3章中会具体讲解这些注解。

从方法命名上不难看出,Protocol接口中的核心方法就是export()和refer(),前者用于对外暴露服务,而后者则用于对远程服务进行引用。

对Protocol接口而言,在Dubbo中存在一大批实现类。Protocol接口的类层结构如图2-20所示。

图2-20 Protocol接口的类层结构

从命名上不难看出,RedisProtocol和MemcachedProtocol会提供把对Redis和Memcached的客户端调用访问封装成接口调用格式的协议;InjvmProtocol实现进程内的接口访问及提供服务;ThriftProtocol支持Thrift协议格式的接口转换,而DubboProtocol支持Dubbo协议格式的接口转换。在Protocol的实现类中,最重要的就是DubboProtocol,我们会在第4章对其进行详细的讲解。

2.Proxy:透明化远程调用

当使用Dubbo运行远程服务调用时,我们所做的事情就是在配置文件或代码中添加对某个服务的引用,整个过程让人感觉不到任何与远程方法调用相关的网络连接、数据传输、序列化等操作,就像在调用一个普通的本地方法一样。远程调用本地化的背后就是动态代理机制。所谓动态代理,可以理解为在系统运行期间为目标类生成二进制的class文件,并运行该class文件。而当系统运行完之后,这个class文件也会消失。在RPC架构中,这些class文件能帮我们自动实现诸如网络连接、数据传输、序列化等具体操作。

在上一节的Protocol接口中,我们看到export方法会传入一个Invoker。什么是Invoker?在Dubbo中,Invoker代表一个可执行体,是一个核心模型,其他模型都向它靠拢或转换成它。Invoker可能是一个本地的实现,可能是一个远程的实现,也可能是一个集群实现。如果这里的Invoker是一个远程实现,就需要用到本节介绍的Proxy机制。

在远程调用过程中,当创建Invoker完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,就可以通过代理对象进行远程调用。代理对象通过ProxyFactory接口的getProxy方法生成。ProxyFactory接口定义如代码清单2-14所示。

代码清单2-14 ProxyFactory接口定义代码

在Dubbo中,ProxyFactory接口的实现类比较明确,即JavassistProxyFactory和Jdk-ProxyFactory。其中JavassistProxyFactory基于动态字节码实现动态代理,而JdkProxyFactory则使用JDK中自带的Proxy.newProxyInstance()静态方法生产代理类。

动态代理是一个值得专门讨论的话题,第12章会对其进行详细讲解。

3.Cluster:负载均衡和集群容错机制

Dubbo中的Cluster主要完成了两部分工作,一个是负载均衡,另一个是集群容错。

在前文中提到,在Dubbo中Invoker是一个核心模型,可能是一个远程的实现,也可能是一个集群实现。当Invoker代表集群实现时,获取具体某一个调用结果的过程就涉及负载均衡。在Dubbo中,负载均衡功能由LoadBalance接口进行定义,如代码清单2-15所示。

代码清单2-15 LoadBalance接口定义代码

可以看到LoadBalance接口只有一个方法,完成了在Invoker列表中选择其中一个Invoker进行返回。显然,不同的负载均衡策略会导致不同的选择结果。Dubbo中LoadBalance的实现主要有4种策略,即随机、轮询、最少活跃调用数和一致性Hash。其中,随机和轮询属于负载均衡的静态策略,实现比较简单;而最少活跃调用数和一致性Hash算法属于动态策略,实现相对比较复杂。

我们再来看集群容错。Dubbo中的Cluster接口定义如代码清单2-16所示。

代码清单2-16 Cluster接口定义代码

我们可以把Cluster看作工厂类,将目录Directory下的多个Invoker合并成一个统一的Invoker,根据不同集群策略的Cluster创建不同的Invoker。对于这里的Directory我们暂时不展开讲解,目前只需要知道它代表多个Invoker,可以把它看成List<Invoker>。Cluster对上层透明,包含集群的容错机制。Dubbo提供了多种Cluster实现方案,默认使用FailoverCluster类,这里以该方案为例展开讨论。FailoverCluster的字面含义即失败转移集群,当请求处理过程失败时将重试其他服务器,其代码如代码清单2-17所示。

代码清单2-17 FailoverCluster类代码

FailoverCluster的join方法返回的是FailoverClusterInvoker,而其他Cluster也会返回对应的ClusterInvoker。Dubbo包含了一组ClusterInvoker,分别与各个Cluster类一一对应。

负载均衡和集群容错都是Cluster的核心功能,我们会在第5、6章中对这些实现机制做源码分析。

4.Remoting:自定义网络通信协议

Dubbo的Remoting模块是远程通信模块,相当于Dubbo自定义协议的实现。从架构演进角度来看,这是一个具有较高扩展性的组件。

对于自定义协议而言,Dubbo要做的事情就是对现有的TCP协议进行封装,并实现对自定义消息头和消息体的正确解析。为此,Dubbo提供了一个Codec接口来实现这一目标。这里列出了新版的Codec2接口的代码定义,如代码清单2-18所示。

代码清单2-18 Codec2接口定义代码

Codec2接口的实现类的层次结构如图2-21所示,这里的每一个实现类都负责在不同层次上处理消息的编解码。例如,TransportCodec负责网络传输,而ExchangeCodec则处理具体的消息交换过程。

图2-21 Codec2接口类层结构

5.Registry和Monitor:服务治理和监控辅助功能

对于一个基本的RPC框架而言,前面的各个步骤已经足够支撑Dubbo的基本功能。但在Dubbo架构的演进过程中,最后一个步骤还为它添加了服务治理和服务监控功能。

Registry即注册中心,它的引入为Dubbo添加了服务注册和服务发现机制,从而使得Dubbo从一个PRC框架上升到一个分布式服务框架(在一定程度上也可以称为微服务框架)。在Dubbo中,RegistryService接口是对注册中心服务的抽象,该接口定义如代码清单2-19所示。

代码清单2-19 RegistryService接口定义代码

从接口定义中可以看到其所提供的方法可以实现注册和取消注册、订阅和取消订阅,以及查找服务。而Registry接口继承了RegistryService接口并提供了如图2-22所示的实现类,可以看到Dubbo分别基于Multicast、Nacos和ZooKeeper等提供了多种注册中心实现方式。

图2-22 RegistryService接口类层结构

注册中心是一个复杂和重要的主题,在目前的分布式服务架构以及微服务架构中扮演着重要角色,我们将在第8章中对其进行详细讲解。

此外,Dubbo还提供了服务监控机制,并通过MonitorService接口实现了服务运行时的数据采集。Dubbo服务监控不是本书的重点,不做具体讨论。

6.整合:架构演进的结果

根据图2-19所示的5个步骤,我们从架构演进这一特定角度剖析了Dubbo框架的代码结构。我们对整个过程进行整合,见图2-23。

图2-23 Dubbo组件分层整合图

通过图2-23,我们把Dubbo中的核心组件分成3大模块。最底部的Remoting模块主要完成网络通信,包含Exchange、Transport和Serialize组件,其中Exchange和Transport组件的功能在前面已经有所提及,而Serialize组件则主要完成数据的序列化和反序列化过程。图2-23的中间部分被称为Core模块,包含Protocol、Proxy、Cluster、Registry、Monitor等组件,这些组件也都在介绍Dubbo框架演进的过程中做了介绍。而图2-23最上面的Business模块代表的是业务服务。

在本节最后,我们结合2.2节中的内容对Dubbo中包结构与代码层次之间的关系做简单梳理。我们在包结构中分别添加了对应的分层组件,如图2-24所示。

图2-24 包含核心组件的Dubbo包结构

从图2-24中我们不难看出以下结构关系。

□ Protocol层和Proxy层放在rpc包中,构成RPC架构的核心。如果不考虑集群环境,可以只使用这两层完成RPC调用。

□ remoting包实现Dubbo协议,Transport层和Exchange层都放在remoting包中。如果不使用自定义Dubbo协议,那么该层组件在运行时不会被使用。

□ Serialize层放在common包中以便更大程度得到复用。

□ Registry、Monitor、Config层则分别对应registry、monitor和config这三个包结构。 wn1Q8TFos4ZbVhP0z+MLw3B5M69Atquh1/HSkn8/7PsYhrBvRvBa0/6d7PJSS9Ce

点击中间区域
呼出菜单
上一章
目录
下一章
×