10分钟带你彻底搞懂Dubbo中的网络通信实现原理

你好,我是萧亦然。
对于 Dubbo 这样的 RPC 框架,网络通信是基础技术能力之一。而 Dubbo 对于网络通信的实现过程也做了高度抽象,专门提供了一个 Remoting 模块,这一模块包含的组件如下图所示:
图1  Dubbo Remoting 模块组件图
从功能定位上讲,Remoting 模块主要包含三个组件,即:
Exchange 组件:信息交换层,用来封装请求——响应模式
Transport 组件:网络传输层,封装 Netty 等框架并提供统一的接口
Serialize 组件:序列化层,主要完成数据的序列化和反序列化过程
从技术分层上讲,Remoting 模块处于整个 Dubbo 框架的底层,是实现远程服务发布和消费的基础。而且 Remoting 模块的组件呈现的是一种对称结构,即 Dubbo 的生产者和消费者都依赖于底层的网络通信。而在 Dubbo 中,真正实现网络通信的过程也是委托给了第三方组件。
Dubbo 通过 SPI 的方式提供了 Netty、Mina 等多种通信框架的集成方式,这部分内容相当于是对 Remoting 模块的补充。
图2 Dubbo框架中网络通信的分层结构
在今天的内容中,我们也将分别从服务器端和客户端这两个角度出发分析 Dubbo 中具体的网络通信过程。在讨论的思路上,我们试图先从框架的底层实现过程出发,然后再推导出上层的设计。

Dubbo 服务器端通信原理

我们先来介绍 Dubbo 服务器端的网络通信过程。首先请记住,Dubbo 中服务器端通信的目的就是集成并绑定 Netty 服务从而启动服务监听。我们关注 Exchange 信息交互层和 Transport 网络传输层这两个核心层之间的交互和协作过程,协作过程如下图所示:
图3  Dubbo服务器端通信核心交互流程图
上图中涉及了 ExchangeServer、HeaderExchange、NettyTransport 和 NettyServer 等核心类。我们从这些类的命名上就可以把它们很明确地划分到 Exchange 和 Transport 这两个层。
我们知道 Dubbo 中存在 Protocol 接口,这个接口是 Dubbo 中最基本的 RPC 组件,具有服务的发布和引用功能。而在 Protocol 接口中存在 export 和 refer 这两个核心方法,其中前者用于对外暴露服务,后者则用来对远程服务进行引用。对应的代码如下:
public interface Protocol {
//服务暴露
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
//服务引用
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}
对于服务器端而言,我们应该关注 export 方法的实现过程,我们同样知道在 Protocol 的实现类中,最重要的就是 DubboProtocol。DubboProtocol 的 export 方法如下方代码所示,这里我们省略了服务发布相关内容,只关注网络通信。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
// 创建Exchange服务器
openServer(url);
return exporter;
}
可以看到,在上面代码中我们通过获取 URL 对象并将它传入到 OpenServer 方法中来创建 Exchange 服务器。OpenServer 方法如下所示:
private void openServer(URL url) {
String key = url.getAddress();
boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
if (isServer) {
ExchangeServer server = serverMap.get(key);
if (server == null) {
serverMap.put(key, createServer(url));
} else {
server.reset(url);
}
}
}
这里出现了 ExchangeServer 接口,要想弄明白这个接口,我们首先得理解 Dubbo 中关于端点(Endpoint)和服务器(Server)的功能抽象。在 Dubbo 中,Endpoint 为网络端点的抽象接口,定义了获取网络端点地址、连接以及最原始的发送消息的方法,而 Server 为网络服务端的抽象接口,继承了 EndPoint 的功能,并扩展了与服务端建立通信通道(Channel)的方法。这些接口都位于网络通信层。
图4 Endpoint和Server的功能抽象
在实现过程中,Dubbo 基于网络通信层的这些接口进行扩展,在信息交换层提供了 ExchangeServer 和 ExchangeChannel 接口,分别继承自 Server 接口和 Channel 接口。掌握这些接口是理解 Dubbo 服务器端运行机制的基础,它们之间的继承关系以及所处的层次如图所示:
图5 Dubbo网络通信层核心接口类图
正如上图所展示的,在 Dubbo 中,Endpoint、Server 和 Endpoint 接口属于网络通信层,而 ExchangeServer 接口属于信息交换层。
有了对这些基础接口的了解,我们接下来就来看 Exchange 服务器的创建过程。createServer 方法如下所示:
private ExchangeServer createServer(URL url) {
// 省略其他代码
ExchangeServer server;
try {
server = Exchangers.bind(url, requestHandler);
}
return server;
}
注意到这里出现了一个新的接口,即 Exchanger。在 Dubbo 中,Exchanger 的实现类只有一个,就是 HeaderExchanger,它的 bind 方法如下所示:
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
我们在 HeaderExchangeServer 中终于看到了 Transport 组件。站在服务器的角度来讲,网络通信的目的就是装配服务并启动监听,从而可以接收来自服务消费者的访问。因此,在 Dubbo 中存在一个 Transporter 接口,这个接口提供了 bind 方法分别用于封装网络通信操作。
与 HeaderExchanger 不同,Dubbo 中针对 Transporter 接口提供了一批实现类,包括 GrizzlyTransporter、MinaTransporter 以及两个 NettyTransporter。系统默认会加载 org.apache.dubbo.remoting.transport.netty 包下的 NettyTransporter,它的 bind 方法如下所示:
public class NettyTransporter implements Transporter {
public static final String NAME = "netty4";
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
}
这里看到了真正实现网络通信的 NettyServer 类,从命名上,我们也不难明白这个类最终通过 Netty 框架实现了底层的网络通信,这里以打开网络连接的 doOpen 方法为例来讲解这一实现过程:
protected void doOpen() throws Throwable {
...
bootstrap = new ServerBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
channels = nettyHandler.getChannels();
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
channel = bootstrap.bind(getBindAddress());
}
熟悉 Netty 的开发人员对上面代码一定不会陌生,这里使用 Netty 的 ServerBootstrap 完成服务启动监听,同时构建了 NettyHandler 作为其网络事件的处理器。最终,通过调用 ServerBootstrap 的 bind 方法来对目标地址进行监听。
NettyServer 的其他方法的实现过程也都是对 Netty 框架基础能力的封装,例如 doClose 方法调用 Netty 的 boostrap 和 channel 对象完成了网络资源释放。
至此,我们明确了 Dubbo 中服务器端实现网络通信的底层技术体系,这些底层技术体系是 Remoting 模块的一部分。作为总结,Dubbo 服务器端通信的整体过程可以用下图所示的时序图来进行概括。
图6 Dubbo服务器端通信的完整时序图

Dubbo 客户端端通信原理

有了对前面服务器端各个技术组件的介绍,理解 Dubbo 客户端通信原理就会容易很多。我们在介绍服务器端时所引入的 Exchanger 和 Transporter 接口都同时包含了对客户端方法的定义,而 Transporter 的实现类 NettyTransporter 也同时会创建一个 NettyClient 类。
public class NettyTransporter implements Transporter {
public static final String NAME = "netty";
public Server bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}
}
与 NettyTransporter 相关的这些核心类之间的关系如下图所示:
图7 NettyTransporter相关核心类结构图
上图中的很多类我们都已经明确了,因此在介绍 Dubbo 的客户端通信原理时,不会像服务端那样做全面展开,而是更多关注于客户端本身的特性,所以我们的思路先从底层的 NettyClient 类进行切入。
NettyClient 中的 doOpen 方法如下所示,创建 ClientBootstrap 并完成初始化参数设置:
@Override
protected void doOpen() throws Throwable {
...
bootstrap = new ClientBootstrap(channelFactory);
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", adapter.getDecoder());
pipeline.addLast("encoder", adapter.getEncoder());
pipeline.addLast("handler", nettyHandler);
return pipeline;
}
});
}
和 NettyServer 一样,NettyClient 同样完成了对 Netty 框架的封装。
然后,我们来到 HeaderExchangeClient 类。与 HeaderExchangeServer 类类似,HeaderExchangeClient 中也添加了定时心跳收发及心跳超时监测机制。
我们继续从底层往上走,一直追溯到 DubboProtocol 的 getClients 方法,这个方法完成 ExchangeClient 的创建。
private ExchangeClient[] getClients(URL url) {
...
ExchangeClient[] clients = new ExchangeClient[connections];
for (int i = 0; i < clients.length; i++) {
if (service_share_connect) {
//获取共享客户端
clients[i] = getSharedClient(url);
} else {
//创建新客户端
clients[i] = initClient(url);
}
}
return clients;
}
这里值得注意和学习的一点在于,Dubbo 在客户端资源的管理上引入了共享客户端的概念,通过计数引用(Reference Count)方式来决定是否复用已创建的 ExchangeClient。

总结

好了,今天的课程就到这里了,和我一起进行一下总结吧!
今天的核心内容是对 Dubbo 中网络通信具体实现过程的深入分析,并明确了 Dubbo 框架在实现网络通信模块时所采用的分层架构。我们在源码级别讨论了信息交换层和网络传输层这两层中各个核心类的实现过程。Dubbo 针对网络通信组件的分层架构为我们如何合理规划底层模块的职责和边界提供了很好的参考价值。
最后,我想给你留一道思考题:你能简要描述 Dubbo 服务器端是如何实现网络通信的吗?你可以在评论区留下你的答案,和我讨论,也欢迎你把这节课分享给需要的朋友。

精选留言

由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。