我们首先介绍Dubbo服务器端的网络通信过程。Dubbo服务器端通信的目的就是集成并绑定Netty服务从而启动服务监听。我们关注Exchange和Transport这两个核心层之间的交互和协作过程,如图3-2所示。
图3-2 Dubbo服务器端通信的核心交互流程图
图3-2涉及了HeaderExchangeServer、HeaderExchanger、NettyTransporter和NettyServer等核心对象。显然,这些对象从命名上就可以很明确地被划分到Exchange和Transport这两个层。
我们已经在第2章中介绍,Dubbo中存在Protocol接口,该接口是Dubbo中最基本的RPC组件,用于完成服务的发布和调用功能。而在Protocol接口中存在export和refer这两个核心方法,其中前者对外暴露服务,后者对远程服务进行引用。对于服务器端而言,显然我们应该关注前者。
在Protocol的实现类中,最重要的就是DubboProtocol,关于这个类,我们会在第4章中“服务发布”这个话题下详细介绍,今天先来看它的export方法。这是Dubbo中服务器端组件中的一个重要入口。
1.服务器端基础接口
DubboProtocol的export方法如代码清单3-1所示,这里我们省略了服务发布相关内容,而是关注网络通信。
代码清单3-1 DubboProtocol中的export方法代码
可以看到,在上述代码中我们通过获取URL对象并将其传入openServer方法中以创建Exchange服务器。openServer方法如代码清单3-2所示。
代码清单3-2 openServer方法代码
代码中出现了ExchangeServer接口,要想理解这个接口,首先得理解Dubbo中关于端点和服务器的抽象,分别定义Endpoint和Server接口,如代码清单3-3所示,其中Server接口继承了Endpoint接口。
代码清单3-3 Endpoint和Server接口定义代码
可以看到Endpoint为网络端点的抽象接口,定义了获取网络端点地址、连接以及最原始的发送消息的方法。而Server为网络服务器端的抽象接口,继承了EndPoint的功能,并扩展了获取与服务器端建连的Channel的方法。Channel即网络通道的抽象接口,是网络通信的另一个重要概念。
同时,ExchangeServer接口又继承了Server接口,如代码清单3-4所示。
代码清单3-4 ExchangeServer接口定义代码
ExchangeServer在Server接口的基础上,将获取Channel的方法扩展为获取Exchange-Channel的方法。Channel接口同样继承了Endpoint接口,如代码清单3-5所示。
代码清单3-5 Channel接口定义代码
Channel还扩展了绑定获取属性和获取通道对端地址的方法。
在类层关系上,ExchangeChannel接口又继承了Channel接口,在Channel接口的基础上扩展了请求响应模式的功能,并能获取绑定在通道上的网络事件监听器。Exchange-Channel接口定义如代码清单3-6所示。
代码清单3-6 ExchangeChannel接口定义代码
从代码中,我们看到了ResponseFuture这种基于Future机制的响应对象,以及Exchange-Handler这种基于事件处理机制的Handler对象。
上述这组接口是理解Dubbo服务器端运行机制的基础,其中有些属于网络通信层,例如Endpoint和Server接口,而有些又属于信息交换层,例如ExchangeServer接口。它们之间的继承关系以及所处的层次如图3-3所示(这些接口都位于dubbo.remoting.api包中)。
图3-3 Dubbo网络通信层核心接口类图
2.创建Exchange服务器
了解这些基础接口之后,来看ExchangeServer的创建过程。使用createServer方法,如代码清单3-7所示(对代码做了简化处理,只保留最核心语句)。
代码清单3-7 createServer方法代码
其中,关键代码就是通过Exchangers的bind方法创建ExchangeServer,bind方法如代码清单3-8所示(同样对代码做了简化处理)。
代码清单3-8 Exchangers的bind方法代码
而这里的getExchanger静态方法的作用就是获取一个Exchanger对象。Exchanger接口定义如代码清单3-9所示。
代码清单3-9 Exchanger接口定义代码
Exchanger接口只有两个方法,一个是面向服务器端的bind方法,另一个是面向客户端的connect方法。在Dubbo中,Exchanger的实现类只有一个,即HeaderExchanger,如代码清单3-10所示。
代码清单3-10 HeaderExchanger类代码
另外,在HeaderExchanger中创建的HeaderExchangeServer作为信息交换层服务器,将网络层的Channel扩展为交换层的ExchangeChannel,并加入心跳检测功能,如代码清单3-11所示。
代码清单3-11 HeaderExchangeServer中实现心跳检测功能的代码
在HeartBeatTask的run方法中,我们根据所配置的hearbeat、heartbeatTimeout属性,执行channel.reconnect、channel.close等方法。
然后,我们在HeaderExchangeServer中终于看到了Transport的相关对象Transporters,接下来看服务器端Transport相关的组件。
站在服务器的角度,网络通信的目的就只有一个,即装配服务并启动监听,从而接收来自服务消费者的访问。而对于服务消费者而言,网络通信的目的无非是对远程服务进行连接、发送请求并获取响应。因此,在Dubbo的com.alibaba.dubbo.remoting包中存在一个Transporter接口,如代码清单3-12所示。
代码清单3-12 Transporter接口定义代码
我们发现Transporter接口的定义与Exchanger接口的定义非常类似,两者都提供了bind和connect这两个方法。区别在于Exchanger用到的是ExchangeHandler,而Transporter用到的是ChannelHandler,显然ChannelHandler面向通道,提供了比ExchangeHandler更底层的操作语义。
与HeaderExchanger不同,Dubbo针对Transporter接口提供了一批实现类,包括GrizzlyTransporter、MinaTransporter以及两个NettyTransporter。系统默认加载com.alibaba.dubbo.remoting.transport.netty包下的NettyTransporter,该类如代码清单3-13所示。
代码清单3-13 NettyTransporter类代码
我们可以看到Transporter作为网络传输层的抽象接口,其核心作用就是提供了创建Server和Client两个核心接口实现类的方法。
其中,真正实现网络通信的是NettyServer类,NettyServer实现了Server接口并扩展了AbstractServer类。在AbstractServer中,Dubbo提供了对网络服务端的通用抽象,即抽象出open、close、send等一组面向网络通信的通用流程。例如,AbstractServer的send方法如代码清单3-14所示,通过getChannels方法获取通道,然后遍历这些通道并调用Channel.send方法进行发送。
代码清单3-14 AbstractServer的send方法代码
AbstractServer同时提供了doOpen和doClose这两个模板方法供子类进行实现。而NettyServer作为AbstractServer的子类,其doOpen方法如代码清单3-15所示。
代码清单3-15 NettyServer中的doOpen方法代码
熟悉Netty的开发人员对上述代码一定不会陌生,这里使用Netty的ServerBootstrap完成服务启动监听,同时构建了NettyHandler作为其网络事件的处理器。然后NettyServer的doClose方法调用Netty的bootstrap和channel对象完成了网络资源释放。这样我们对Dubbo网络通信中服务监听启动和关闭以及发送消息的过程就有了整体的了解。
至此,我们明确了在Dubbo中进行网络通信的底层技术,这些底层技术是Remoting模块的一部分。注意,在Remoting模块中还有Serialize组件,该组件为一个公共组件,负责数据的序列化和反序列化。这就是3.2.3节要介绍的内容。
对于Dubbo中的Remoting模块,还有一个关于序列化和反序列化的Serialize组件没有介绍。通过第2章,我们知道Dubbo提供了Serialization接口(位于dubbo-common代码工程中)作为对序列化的抽象。而该接口序列化和反序列化操作的返回值分别是ObjectOutput和ObjectInput。其中ObjectInput扩展自DataInput,用于读取对象;ObjectOutput扩展自DataOutput,用于写入对象。这两个接口的定义如代码清单3-16所示。
代码清单3-16 ObjectInput和ObjectOutput接口定义代码
在Serialization接口的定义代码中,可以看到Dubbo默认使用的序列化实现方案基于Hessian 2。Hessian是一款优秀的序列化工具。在功能上,它支持二级制的数据表示形式,从而能够提供跨语言支持;在性能上,无论时间复杂度还是空间复杂度都比Java序列化高效很多。
在Dubbo中,Hessian2Serialization实现了Serialization接口,我们就以Hessian2Seria-lization为例介绍具体的在Dubbo中实现序列化/反序列化的方法,Hessian2Serialization类定义如代码清单3-17所示。
代码清单3-17 Hessian2Serialization类代码
Hessian2Serialization中的serialize和deserialize方法分别创建了Hessian2ObjectOutput和Hessian2ObjectInput类。以Hessian2ObjectInput为例,该类使用Hessian2Input完成具体的反序列化操作,如代码清单3-18所示。
代码清单3-18 Hessian2ObjectInput类代码
Hessian2Input是Hessian2的实现库com.caucho.hessian中的工具类,初始化时需要设置一个SerializerFactory,所以上述代码中存在一个Hessian2SerializerFactory专门用于设置SerializerFactory。而在Hessian2ObjectInput中各种以read为前缀的方法实际上都是对Hessian2Input相应方法的封装。用于执行反序列化的Hessian2ObjectOutput与Hessian2ObjectInput类也比较简单,这里不再展开讲解。
关于Dubbo序列化另外一个重点关注点是Codec2接口,该接口位于dubbo-remoting-api代码工程中,提供了对网络编解码的抽象,而编解码显然需要依赖Serialization接口作为其数据序列化的手段。我们可以通过代码清单3-19所示的代码片段来了解DubboCodec中decodeBody方法。
代码清单3-19 DubboCodec中decodeBody方法代码
那么,Codec和Serialization如何与前面介绍的Exchange和Transport结合起来构成一个完整的链路呢?可以明确的是,序列化和编解码过程在网络传输层和信息交换层中都应该存在。在dubbo-remoting-api代码工程的META-INF/dubbo/internal文件夹中,存在一个com.alibaba.dubbo.remoting.Codec2配置文件,内容如代码清单3-20所示。
代码清单3-20 com.alibaba.dubbo.remoting.Codec2配置文件
com.alibaba.dubbo.remoting.Codec2配置文件用来执行SPI机制,我们会在本书第16章中对此进行专项介绍,这里只需要明白Dubbo采用这种配置方式来动态加载运行时的类对象。在代码中,Dubbo对exchange和transport都提供了Codec支持。
总结一下,Dubbo服务器端通信的整体过程可以用如图3-4所示的时序图来进行概括。
图3-4 Dubbo服务器端通信的完整时序图