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

2.4 基于基础架构组成剖析代码结构

正如任何一个框架都存在一个核心流程一样,任何一个框架也都有一个充当内核的基础架构。以Dubbo框架和MyBatis框架为例,可以认为前者的基础架构包含网络通信、序列化、传输协议等组件,而后者则包含数据库连接管理、SQL执行等组件。而这些组件的设计方法和实现原理在很长一段时间内都没有什么变化,Dubbo和MyBatis等框架只不过是在不同层次上对这些组件进行了封装。我们只要能够对这些基础架构中的组件有深刻的理解,那么面对同类型的新框架,就可以掌握对应的设计思路和方法。本节将从“基于基础架构组成剖析代码结构”这一主题出发展开讨论。

2.4.1 如何从基础架构扩展到具体实现框架

身处在互联网行业,笔者在做一些培训和分享时,周围的同事、朋友和学员经常会抛出以下几个问题。

□ 技术发展太快了,旧的工具框架还没掌握,新的不断出现,怎么办?

□ 随着年纪越来越大,自己的学习能力和热情逐渐下降,如何进一步提升自己的技术水平?

诚然,我们处在一个行业快速变化、“35岁程序员”成为一个社会性话题的时代中,上述问题的出现有一定的必然性。但是,在这里笔者想分享的一个观点是:就算新的工具框架不断出现,但这些工具框架背后的很多基础架构实际上已经存在了十几年甚至几十年了。我们只要掌握了这些基础架构,不管面对新框架还是老框架,就能够快速掌握其基础的代码结构,从而在学习上达到事半功倍的效果。围绕剖析框架代码的系统方法,今天我们要讨论的一个问题是:如何从基础架构扩展到具体实现框架?

这里所谓的基础架构有很多,比方说接下来要讨论的RPC架构已经足足存在和发展了40余年,其原型可追溯至1974年发布的RFC 674草案——过程调用协议文档第2版。RPC架构也是构建一切分布式系统的基础性架构,Dubbo也只不过是RPC架构的一款典型实现框架而已。在阅读Dubbo框架源码时,我们同样可以基于RPC架构来梳理代码结构。在此之前,我们先来看一下RPC基础架构的组成。

2.4.2 RPC基础架构

本节讨论RPC基础架构,我们将给出RPC基础架构图,然后抽象出该架构中的3大核心组件。

1.RPC基础架构图

基础的RPC架构由左右对称的两大部分构成,分别代表了远程过程调用所涉及的客户端和服务器端组件,如图2-28所示。

图2-28 RPC基础架构图

从图2-28中,可以看到客户端组件及其职责包括:

□ RpcClient,负责导入由RpcProxy提供的远程接口;

□ RpcProxy,远程接口的代理实现,提供远程服务本地化访问的入口;

□ RpcInvoker,负责发送调用请求到服务器端,并等待响应结果;

□ RpcProtocol,负责网络传输协议的编码和解码;

□ RpcConnector,负责维护客户端和服务器端的连接通道,并发送数据到服务器端;

□ RpcChannel,提供网络数据传输通道。

而服务器端组件及其职责则包括:

□ RpcServer,负责导出远程接口;

□ RpcInvoker,负责调用服务器端接口的具体实现并返回结果;

□ RpcProtocol,负责网络传输协议的编码和解码;

□ RpcAcceptor,负责接受客户端请求,并返回响应结果;

□ RpcProcessor,负责控制调用过程,包括管理调用线程池、超时时间等;

□ RpcChannel,网络数据传输通道。

RPC基础架构的特点在于概念和语义清晰明确,过程调用简洁,并且提供了通用的通信机制和可扩展的序列化方式。

2.RPC基础架构实现的基本策略和示例

接下来我们通过一个简单的示例来展示图2-28中的各个客户端和服务器端组件的实现方法。通过该示例,我们可以从零开始构建一个最基本的RPC基础架构。

首先我们定义一个简单的服务MathService,用于对输入的两个数字进行求和,如代码清单2-35所示。

代码清单2-35 MathService接口定义代码

MathService接口的实现类也非常简单,如代码清单2-36所示。

代码清单2-36 MathService接口实现类代码

对RPC架构而言,定义了服务之后,我们就需要分别构建一个服务器端组件RpcServer和一个客户端组件RpcClient。但在此之前,我们先来定义一种消息格式,该消息格式用来在客户端和服务器端之间传输数据,将实现该消息格式的类命名为TransportMessage,如代码清单2-37所示。

代码清单2-37 TransportMessage类代码示例

可以看到在TransportMessage中定义了一次服务请求所需要的接口名、方法名以及方法调用所需要的参数。注意到该类同时实现了Serializable接口,这是Java中的序列化接口,实现该接口的类就能够通过网络进行远程传输。在图2-28所示的RPC基础架构中,TransportMessage类相当于由RpcProtocol组件传递的请求数据。

接下来我们考虑构建RpcServer类,该类需要实现RPC基础架构图中的各个服务器端组件。RpcServer类的完整代码如代码清单2-38所示。

代码清单2-38 RpcServer类代码示例

RpcServer类代码比较长,我们结合RPC基本架构对其进行分段解析。

□ service方法

service方法接收请求并启动Socket进行端口监听,该方法使用线程池为进入的每个请求启动一个线程。就RPC基础架构而言,service方法相当于扮演了RpcAcceptor的角色。

□ process方法

service方法启动了线程,而每个线程会调用process方法。这里的process方法从Socket中获取输入流,然后把输入流中的数据转化为TransportMessage,从而获取远程方法调用的各项元数据。就RPC基础架构而言,该process方法充当了RpcProcessor的角色。

□ call方法

一旦获取了TransportMessage,process方法就会调用call方法来执行真正的方法调用。call方法通过反射机制获取位于服务器端的方法并执行。显然,这个call方法对应RpcInvoker的角色。

我们再来看一下RpcClient的代码,如代码清单2-39所示。

代码清单2-39 RpcClient类代码示例

客户端代码比较简单,主要就是通过Socket对远程服务地址发起请求,传入一个TransportMessage并返回远程调用的结果。

完成了RpcServer和RpcClient类之后,我们可以编写一些测试用例来对其进行验证。验证方法就是启动RpcServer,然后通过RpcClient发起远程调用。这里我们编写一个ServerTest测试类来启动RpcServer,如代码清单2-40所示。

代码清单2-40 ServerTest测试类代码示例

然后我们再编写一个ClientTest测试类对远程服务发起请求,整个过程如代码清单2-41所示。

代码清单2-41 ClientTest测试类代码示例

3.RPC基础架构的3大组件

RPC的基础架构已经存在很多年了,我们对其进行进一步抽象,可以看到构成RPC基本架构的组件有3个,分别是网络通信、传输协议和服务调用。

□ 网络通信

网络通信涉及面很广,RPC架构中的网络通信关注网络连接、I/O模型和可靠性设计,以及基于序列化和反序列化对数据进行传输和转换的过程。

□ 传输协议

RPC架构的设计和实现通常会涉及传输层及以上各层的相关协议(如TCP和HTTP等),当然我们也可以根据需要在通用的传输协议中添加定制化内容。

□ 服务调用

在RPC架构中,服务调用存在两种基本模式,即单向模式和请求应答模式。同时,服务调用也包括并行调用和泛化调用等多种调用方法。

2.4.3 从RPC基础架构扩展到Dubbo框架

在本节中,我们将结合Dubbo框架探讨RPC组件在该框架中的设计和实现方法。

1.Dubbo中的网络通信

任何分布式系统都需要解决网络通信问题,Dubbo作为一款具有代表性的RPC框架,网络通信是其基础组件。而网络通信是一个可复用性很高但又相对独立的技术体系,业界也存在一批优秀的框架专门对网络底层的通信过程进行了封装,例如Netty和Mina。Dubbo充分利用了这些框架并将它们整合到整个远程方法调用过程中。

在Dubbo中,真正执行网络通信的组件同时涉及服务的提供者和消费者,本节将分别从服务提供者和消费者的角度出发梳理Dubbo的网络通信过程。在此之前,让我们回顾一下在2.2节中介绍的Remoting模块。

我们在介绍Dubbo整体架构的演进过程中提到了Remoting模块,该模块包含的组件如图2-29所示。

图2-29 Dubbo Remoting模块中的组件

从功能职责上讲,Exchange层主要用于Dubbo对自身通信过程的抽象和封装,跟网络通信关系不大,具体网络传输工作都是由Transport层负责完成的。因此,在解决网络通信问题时,我们需要更多关注Transport层。事实上,Dubbo网络通信的底层也是基于Netty、Mina等框架进行的封装和扩展。关于Dubbo中网络通信的实现原理我们在第3章中会进行详细的讨论。

所谓序列化就是将对象转化为字节数组,用于网络传输、数据持久化或其他用途,而反序列化则是把从网络、磁盘等渠道中读取的字节数组还原成原始对象,以便后续进行业务逻辑操作。

在Dubbo框架中,Serialization是序列化器的接口,其定义如代码清单2-42所示,其中核心的serialize和deserialize方法分别用于执行具体的序列化和反序列化操作。

代码清单2-42 Serialization接口定义代码

可以看到serialize()和deserialize()这两个方法对应的返回值分别是ObjectOutput和ObjectInput,其中ObjectInput扩展自DataInput,用于读取对象;而ObjectOutput扩展自DataOutput,用于写入对象。

Dubbo还提供了对Java二进制、JSON、SOAP等多种序列化协议的支持,其具体实现方法与对应的序列化框架有关,但整体实现过程都是类似的,我们在第3章中还会对Dubbo的序列化实现方式做进一步分析。

2.Dubbo中的传输协议

按照ISO/OSI网络模型,传输协议可以分成7个层次,自上而下分别是应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。其中传输层实现端到端连接、会话层实现互连主机通信、表示层用于数据表示、应用层则直接面向应用程序,如图2-30所示(其中,AH、PH、SH、TH、NH、DH均为消息头简称,DT为消息尾简称)。

图2-30 传输协议的层次

RPC架构的设计和实现通常会涉及传输层及以上各层的相关协议,常见的TCP就属于传输层,而HTTP则位于应用层。TCP提供基于连接的、可靠的字节流服务,可以支持长连接和短连接。而HTTP是一个无状态的面向连接的协议,基于TCP的客户端/服务器端请求和应答标准,同样支持长连接和短连接,但HTTP的长连接和短连接本质上采用的还是TCP的连接模式。

在构建RPC架构时,我们可以使用TCP和HTTP等公共协议,也可以基于HTTP的Web Service和RESTful风格实现更加强大和友好的数据传输方式。但大部分RPC框架往往在内部使用私有协议进行通信,这样做的主要目的在于提升性能,因为公共协议出于通用性的考虑添加了很多辅助性功能,这些辅助性功能会消耗通信资源从而降低性能,设计私有协议可以确保协议尽量精简。另外,具备高度定制化能力的私有协议也比公共协议更加容易实现扩展。

传输协议的消息包括消息头和消息体两部分,消息体表示需要传输的业务数据,而消息头用于控制传输过程。在如图2-30所示的ISO/OSI网络7层模型中,我们可以看到每一层都会从上层获取数据,加上消息头信息形成新的数据单元,并将新的数据单元传递给下一层。我们可以在消息头中添加各种定制化信息形成自定义协议。图2-31是Dubbo采用的私有协议的定义方式。

图2-31 Dubbo协议(来自dubbo.apache.org)

我们可以看到Dubbo私有协议在会话层中添加了自定义消息头,该消息头包括用于支持多协议的Magic Code属性、支持同步转异步并扩展消息头的Id属性等。Dubbo私有协议的设计者认为远程服务调用的时间消耗主要取决于传输数据包的大小,所以Dubbo序列化的主要优化目标在于减小数据包大小,从而提高序列化/反序列化性能。

3.Dubbo中的远程调用

服务调用存在两种基本模式,即单向模式和请求应答模式,其中前者体现为异步操作,而后者一般为同步操作。

同步调用可能会造成业务线程阻塞,但其开发和管理相对简单。同步调用时序图参考图2-32,我们可以看到服务线程发送请求到I/O线程之后就一直处于等待阶段,直到I/O线程完成与网络的读写操作之后被主动唤醒。

在异步操作中,网络I/O不需要等待。因此,使用异步调用的目的在于获取高性能,队列和事件驱动架构都是实现异步调用的常见策略,但都依赖基础中间件平台。

图2-32 同步调用时序图

在Dubbo中,远程调用存在3种调用方式,即单向调用、异步调用以及同步调用。

让我们抛开方法调用链路的中间环节直接切入DubboInvoker类的代码,其核心方法是doInvoke(),如代码清单2-43所示。

代码清单2-43 DubboInvoker类中的doInvoke代码

在上述方法中,我们可以清晰看到代码执行的三条路径。

□ 单向调用

不需要返回值,不管同步还是异步,请求直接发出,不会创建Future对象,直接返回RpcResult空对象。

□ 异步调用

先创建ResponseFuture对象,并使用FutureAdapter包装该ResponseFuture对象。然后该FutureAdapter对象会被嵌入当前线程的上下文RpcContext中,最后返回空的RpcResult。这样,后续调用方能够在合适的时候从RpcContext中获取Future对象,并通过Future对象的get方法获取响应结果。

□ 同步调用

先创建ResponseFuture对象,之后直接调用Future的get方法获取响应结果。

这里比较复杂的是异步调用场景,Dubbo基于Netty的非阻塞特性发送异步请求,但是这种异步请求最终会转化为同步处理过程并获取响应结果,这个过程就是所谓的异步转同步。这是一个复杂的实现过程,我们将在第4章中做进一步讨论。 rEWqm1Yx7hk/juqbrG/ecf64YEZUrJY4xlndyD+GYkzaNy5DXy9q7QX1OM0vIBoN

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