文章目录
一、前言
本系列为个人Dubbo学习笔记,内容基于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章,仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。
系列文章地址:Dubbo源码分析:全集整理
在 Dubbo笔记⑤ : 服务发布流程 - Protocol#export 中,我们对 RegistryProtocol#export 、 InjvmProtocol#export 、DubboProtocol#export 服务暴露过程进行了分析,结果如下:
- RegistryProtocol#export :完成服务暴露时注册中心相关的处理。
- InjvmProtocol#export :本地服务暴露的处理。
- DubboProtocol#export :启动 了Netty 服务 让提供者获得网络通信的能力。
而本文我们就来看一下 DubboProtocol#export 开启 Netty 服务的过程。
二、分层架构
1. 概述
在 DubboProtocol#createServer 中 通过如下代码开启了服务端口,至此Dubbo结构进入了Exchange 层。
server = Exchangers.bind(url, requestHandler);
Exchangers#bind 实现如下:
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// getExchanger(url) 根据 SPI 机制获取的实现类是 HeaderExchanger
return getExchanger(url).bind(url, handler);
}
这里涉及到的 Dubbo 分层,如下图,关于 Dubbo 架构更多内容详参官方文档 : 框架设计。
本文主要涉及下面三个层级:
- Exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer。
- Transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec。
- Serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool。
结合上面的架构图,我们来简述 Exchange -> Transport -> Serialize 层的过程,以 Netty 服务为例
消费者
-
服务启动时,消费者通过 Exchangers#connect 绑定或连接服务,并获取到 ExchangeClient,之后的服务调用通过 ExchangeClient 来发起。
-
Protocol -> Exchange : 消费者发起调用后,在 DubboInvoker#doInvoke 会通过 ExchangeClient#request 发起请求,此时从 Protocol 层进入到 Exchange 层。具体调用链如下:
DubboInvoker#doInvoke -> ReferenceCountExchangeClient#request -> HeaderExchangeClient#request -> HeaderExchangeChannel#request -> NettyClient#send
-
Exchange -> Transport : 根据上面的调用链,HeaderExchangeChannel#request 会调用 NettyClient#send 发送消息,此时从 Exchange 层进入到 Transport 层。随后会通过 NettyClient#send -> NettyChannel#send 发送消息,而NettyClient 在初始化时在 Netty 中添加了编解码器。当消息发送后会调用编解码器,这里发送消息会调用编码器来序列化信息,调用链如下:
DubboCountCodec#encode -> DubboCodec#encode -> Serialization#serialize
-
Transport -> Serialize : 在 DubboCodec#encode 中会调用 Serialization#serialize 获取到 ObjectOutput 对象,并通过 ObjectOutput#writer 来获取序列化后的内容。
-
最后将序列化后的请求内容发送给提供者。
提供者
-
服务启动时。提供者通过 Exchangers#bind, 绑定端口服务,并获取到 ExchangeServer。
-
当消费者发送请求后,提供者的 Netty 信道接收到消息后,首先通过调用 Codec#decode 来反序列化,调用链如下:
DubboCountCodec#decode -> DubboCodec#decode -> Serialization#deserialize
之后会根据 Serialization#deserialize 来获取 ObjectInput对象,通过 ObjectInput#read 获取反序列化的结果。
-
Serialize -> Transport :反序列化的请求内容会按照下面的调用链交由ChannelHandler#received 来处理,此时已经从 Serialize 层进入到 Transport 层:
NettyServerHandler#channelRead -> NettyServer#received -> MultiMessageHandler#received -> HeartbeatHandler#received -> AllChannelHandler#received (根据Dubbo 线程模型的不同会有不同的实现) -> DecodeHandler#received -> HeaderExchangeHandler#received -> 请求需要返回值 : ExchangeHandler#reply (DubboProtocol 内部的 requestHandler 属性) 请求不需返回值 : ExchangeHandler#received (DubboProtocol 内部的 requestHandler 属性) -> ExchangeHandler#received
这需要注意 :
-
在 NettyServer 中 ChannelHandler 在初始化时被Dubbo线程模型进行了包装,如下
public NettyServer(URL url, ChannelHandler handler) throws RemotingException { super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))); }
因此 上图 中的 AllChannelHandler#received 可能会随着线程模型的不同选择不同的实现类
-
HeaderExchangeHandler#received 请求后,如果请求需要返回值则调用 ExchangeHandler#reply ,如果不需要返回值则调用 ExchangeHandler#received。这里的ExchangeHandler都是 DubboProtocol 内部的 requestHandler 属性。
-
-
Transport -> Exchange : 根据上面的调用链和描述,如果请求不需要返回值才会调用 ExchangeHandler#received 进入 Exchange 层,否则则直接调用 ExchangeHandler#reply 进入 Protocol。
-
Exchange -> Protocol :根据上面调用链,请求无论是有无返回值最终都会调用 ExchangeHandler#reply (DubboProtocol 内部的 requestHandler 属性) 来处理请求。DubboProtocol#requestHandler 会获取 DubboExporter 并调用 DubboExporter#invoke 来完成调用。DubboExporter 中保存了Dubbo服务实例对象,因此可以完成这次调用。
2. Exchange
根据官方的描述, Exchange 层的两个作用:
- 封装请求响应模式 : 请求数据封装成 Request 类型,响应数据封装成 Response 类型。
- 同步转异步 : 通过 DefaultFuture 类来完成的异步调用。
下面我们看一下 Exchange 层是如何实现 封装请求响应模式、同步转异步。
Exchanger是此层的核心接口类,我们这里来看一下 Exchanger 的定义,如下:
@SPI(HeaderExchanger.NAME)
public interface Exchanger {
// 提供者启动后绑定机器端口
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException;
// 消费者消费前连接提供者的机器
@Adaptive({Constants.EXCHANGER_KEY})
ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException;
}
从 Exchanger 的方法定义可以看到 :
- Exchanger 方法定义的返回类型为 ExchangeServer 和 ExchangeClient 。ExchangeServer 和 ExchangeClient 实际上就是 Server 和 Client 的包装类。
- Exchanger 方法定义的入参 Handler 类型限制为 ExchangeHandler。ExchangeHandler 继承了 ChannelHandler 和 TelnetHandler。提供了提供了连接建立,连接端口,发送请求,接受请求等功能。
Exchanger 默认的 SPI 实现 HeaderExchanger 如下,可以看到这里返回的是 HeaderExchangeClient 和 HeaderExchangeServer 类型 ,也即是说,默认情况下 ExchangeServer 和 ExchangeClient 的实现类是 HeaderExchangeServer 和 HeaderExchangeClient :
public class HeaderExchanger implements Exchanger {
public static final String NAME = "header";
@Override
public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
@Override
public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
}
}
下面我们来看一下ExchangeClient 和 ExchangeServer 具体内容:
2.1 ExchangeClient
ExchangeClient 定义如下,是 Client 和 ExchangeChannel 的子接口,ExchangeChannel负责将上层的 data 包装成 Request,然后发送给Transport层。
这里我们只看ExchangeChannel 的定义。
public interface ExchangeClient extends Client, ExchangeChannel {
}
public interface ExchangeChannel extends Channel {
// 发送请求,消费者通过该方法向提供者发送请求
ResponseFuture request(Object request) throws RemotingException;
// 发送请求,消费者通过该方法向提供者发送请求
ResponseFuture request(Object request, int timeout) throws RemotingException;
ExchangeHandler getExchangeHandler();
@Override
void close(int timeout);
}
根据上面提到的流程消费者发起请求后会执行 ExchangeClient#request 方法,我们上面提到 ExchangeClient 的默认实现是 HeaderExchangeClient,所以调用链如下:
HeaderExchangeClient#request -> HeaderExchangeChannel#request
HeaderExchangeChannel#request 方法实现如下,在该方法中完成了请求模式封装和同步转异步的操作 :
@Override
public ResponseFuture request(Object request) throws RemotingException {
return request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT));
}
@Override
public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
// 1. 创建 Request 对象
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
// 设置需要返回值
req.setTwoWay(true);
// 设置请求数据
req.setData(request);
// 2. 创建 DefaultFuture 对象
DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout);
try {
// 3. 请求发送到远端
channel.send(req);
} catch (RemotingException e) {
// 出现异常,取消调用。
future.cancel();
throw e;
}
// 返回 future。为 DefaultFuture
return future;
}
这里可以看到两个操作:
-
封装请求模式 : 请求数据封装成 Request 类型。
-
同步转异步 : 通过 DefaultFuture 类来完成的同步转异步的调用。关于异步调用的内容,这里简单说明一下:DefaultFuture中会以 requestId 作为本次请求的唯一id,将channel保存到 DefaultFuture中。
channel.send(req);
执行后并不会等待结果,而是直接将 DefaultFuture 返回给上层。当真正的调用结果返回后会触发 DefaultFuture 的回调,将结果保存。当上层通过 DefaultFuture 获取结果时,如果调用已经结束则将结果返回,否则阻塞获取,直至提供者将结果返回。如有需要详参: Dubbo笔记 ⑳ :消费者的异步调用 和 Dubbo笔记 ㉑ :提供者的异步执行
2.2 ExchangeServer
ExchangeServer 作为提供者端的作用是接受请求、处理请求、返回结果。
public interface ExchangeServer extends Server {
Collection<ExchangeChannel> getExchangeChannels();
ExchangeChannel getExchangeChannel(InetSocketAddress remoteAddress);
}
HeaderExchangeServer 的相关实现如下,可以看到 HeaderExchangeServer 的这里是将 Channel 封装成 HeaderExchangeChannel 类型返回。:
/******* Server 接口的方法 ******/
@Override
public Channel getChannel(InetSocketAddress remoteAddress) {
return getExchangeChannel(remoteAddress);
}
/******* ExchangeServer 接口的方法 ******/
@Override
public Collection<ExchangeChannel> getExchangeChannels() {
Collection<ExchangeChannel> exchangeChannels = new ArrayList<ExchangeChannel>();
Collection<Channel> channels = server.getChannels();
if (channels != null && !channels.isEmpty()) {
for (Channel channel : channels) {
exchangeChannels.add(HeaderExchangeChannel.getOrAddChannel(channel));
}
}
return exchangeChannels;
}
@Override
public ExchangeChannel getExchangeChannel(InetSocketAddress remoteAddress) {
Channel channel = server.getChannel(remoteAddress);
return HeaderExchangeChannel.getOrAddChannel(channel);
}
当消费者发起请求后,提供者会通过如下流程来提供服务
NettyServerHandler#channelRead -> NettyServer#received -> HeaderExchangeHandler#received -> (如果是需要返回值的请求) HeaderExchangeHandler#handleRequest
HeaderExchangeHandler#handleRequest 实现如下 ,在该方法中会将请求返回结果封装为 Response。完成响应请求封装。
// 处理请求消息
void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
// 封装为 Response
Response res = new Response(req.getId(), req.getVersion());
// 检测请求是否合法,不合法则返回状态码为 BAD_REQUEST 的响应
if (req.isBroken()) {
Object data = req.getData();
String msg;
if (data == null) {
msg = null;
} else if (data instanceof Throwable) {
msg = StringUtils.toString((Throwable) data);
} else {
msg = data.toString();
}
res.setErrorMessage("Fail to decode request due to: " + msg);
// 设置 BAD_REQUEST 状态
res.setStatus(Response.BAD_REQUEST);
channel.send(res);
return;
}
// find handler by message class.
// 获取 data 字段值,也就是 RpcInvocation 对象
Object msg = req.getData();
try {
/*************************** 处理请求 ***************************/
// handle data.
// 1. 继续向下调用, 这里调用的是 replay 方法,这里的handler 为 DubboProtocol#requestHandler
CompletableFuture<Object> future = handler.reply(channel, msg);
// 2. 如果请求已经完成则写回结果
if (future.isDone()) {
res.setStatus(Response.OK);
res.setResult(future.get());
channel.send(res);
return;
}
// 3. 如果还没完成,则等待结束后写回结果
future.whenComplete((result, t) -> {
try {
if (t == null) {
res.setStatus(Response.OK);
res.setResult(result);
} else {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
channel.send(res);
} catch (RemotingException e) {
logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
} finally {
// HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
});
/******************************************************/
} catch (Throwable e) {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(e));
channel.send(res);
}
}
2.3 总结
- 封装请求响应模式 :消费者在发起请求时会 HeaderExchangeChannel#request在中将请求内容封装成 Request。提供者在将结果返回时会在 HeaderExchangeHandler#handleRequest 中将结果封装成 Response。
- 同步转异步 :消费者在发起请求时,在 HeaderExchangeChannel#request 中完成了同步转异步。
三、NettyServer
上面介绍了 Dubbo 的分层流转,下面我们来看看提供者在启动时是如何实现的。
1. 概述
Dubbo通过在 DubboProtocol#createServer 中 通过如下代码开启了服务端口,这里需要注意一下 方法入参中的 requestHandler 是 DubboProtocol 中的匿名 ExchangeHandlerAdapter 实现类,调用消息最终会交由该类来处理。:
server = Exchangers.bind(url, requestHandler);
关于DubboProtocol#createServer 的详细介绍,如有需要详参Dubbo笔记⑤ : 服务发布流程 - Protocol#export
Exchangers.bind(url, requestHandler)
代码调用因为涉及到了Dubbo多个层级,所以显得较为复杂,具体如下:
public static ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
...
// 如果 codec 没有默认值,则添加 exchange。但是在基础参数设置中我们已经指定了编码器为 dubbo
url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
// 1. getExchanger(url) : 获取url 中的 exchanger 属性来获取到 Exchanger,默认是Header
// 2. bind(url, handler):第一步中默认是HeaderExchanger,所以这里实际上是 HeaderExchanger#bind(url, handler)
return getExchanger(url).bind(url, handler);
}
-
Exchangers.bind(url, requestHandler) 实现如下:getExchanger(url) : 通过SPI 获取 Exchanger,默认实现是 HeaderExchanger。所以这里bind(url, handler) 调用的是
HeaderExchanger#bind
// Exchangers#getExchanger 实现如下: public static Exchanger getExchanger(URL url) { String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER); return getExchanger(type); } // 通过SPI 获取Exchange public static Exchanger getExchanger(String type) { return ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type); }
-
HeaderExchanger#bind 实现如下:
// HeaderExchanger#bind @Override public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException { return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))); }
其中
Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
实现如下:public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException { ... ChannelHandler handler; // 如果有多个 Handlers ,则使用 ChannelHandlerDispatcher 来分发处理。 if (handlers.length == 1) { handler = handlers[0]; } else { handler = new ChannelHandlerDispatcher(handlers); } // 1. getTransporter() 通过SPI 获取到了默认的 Transporter实现 NettyTransporter。 // 2. .bind(url, handler) 的实现是 NettyTransporter.bind(url, handler),完成了绑定了当前服务.所以此处调用的是NettyTransporter#bind return getTransporter().bind(url, handler); }
我们这里假设传输类型是 Netty,所以这里 getTransporter() 通过SPI 获取到了默认的 Transporter实现 NettyTransporter,所以此处调用的是NettyTransporter#bind,其详细实现如下:
@Override public Server bind(URL url, ChannelHandler listener) throws RemotingException { return new NettyServer(url, listener); }
综上:上面一大堆逻辑就是 Exchange、Transport、Serialize 层级的嵌套调用。
这里我们可以看到 NettyServer 是在 NettyTransporter#bind 中创建。实际上 在 NettyTransporter 中不仅创建了提供者的 NettyServer,还创建了消费者的 NettyClient,不过本文重点在于 NettyServer 对于 NettyClient 暂时不做介绍。
Dubbo 中提供了 Netty 3.x 版本的实现和 Netty4.x 版本的实现,实现大致相同,我们这里看的是Netty4版本, 即 org.apache.dubbo.remoting.transport.netty4.NettyServer
NettyServer 的构造函数如下:
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
// 1. super 调用 NettyServer 父类 AbstractServer#AbstractServer 的构造函数
// 2. ChannelHandlers.wrap 完成了Dubbo线程策略的应用
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
需要注意,此时入参中的ChannelHandler 实际类型为 DecodeHandler, 结构如下图
消息的处理会按照如下流程处理。
DecodeHandler#received -> HeaderExchangeHandler#received -> DubboProtocol.requestHandler#received
NettyServer 的构造函数虽然只有一句,但是其中工作还是挺多的,如下:
- 线程模型的应用 : 在构造函数中通过 ChannelHandlers.wrap 包装 Handler,而ChannelHandlers#wrap 中应用了 Dubbo 线程策略。
- Netty 服务开启 :NettyServer 构造函数中使用 super 调用了 父类构造函数AbstractServer#AbstractServer,而 AbstractServer#AbstractServer 中开启了 Netty 服务。
下面我们来详细看看这两点的实现:
2. 线程模型的应用
ChannelHandlers#wrap 中会对 当前 Handler 按照指定的线程模型进行包装。
2.1 Dubbo 线程模型
Dubbo 默认的底层网络通信使用的是Netty,服务提供方NettyServer 使用两级线程池,其中boss线程池主要用来接受客户端的链接请求,并把完成Tcp三次握手的连接分发给 worker 来处理,我们把 boss 和worker线程组称为 I/O 线程。
在实际调用时,如果服务提供方的逻辑处理能够迅速完成,并且不会发起新的IO 请求,那么直接在 IO 线程上处理会更快,因为这样减少了线程池调度与上下文切换的开销。
但是如果处理逻辑较慢,或者需要发起新的IO请求(比如需要查询数据库),则IO线程必须派发请求到新的线程池进行处理,否则 IO 线程被阻塞,导致不能接收其他请求。
在Dubbo 中,线程模型的扩展接口为 org.apache.dubbo.remoting.Dispatcher
,all为默认的线程模型,即由AllChannelHandler 来处理Netty 事件。
根据请求的消息是否被 IO线程处理还是被业务线程处理,Dubbo提供了下面几种线程模型:
all=org.apache.dubbo.remoting.transport.dispatcher.all.AllDispatcher
direct=org.apache.dubbo.remoting.transport.dispatcher.direct.DirectDispatcher
message=org.apache.dubbo.remoting.transport.dispatcher.message.MessageOnlyDispatcher
execution=org.apache.dubbo.remoting.transport.dispatcher.execution.ExecutionDispatcher
connection=org.apache.dubbo.remoting.transport.dispatcher.connection.ConnectionOrderedDispatcher
-
all (AllDispatcher) : 所有的消息都派发到业务线程池,这些消息包括请求、响应、连接事件、断开事件、心跳事件等(话虽如此,但是在看AllChannelHandler 实现时发现sent 方法仍是由I/O线程处理,暂时不明),如下图,其中NettyServerBoss 、NettyServerWorker 为I/O 线程。
-
direct : 所有的消息都不派发到业务线程池,全都在 IO 线程上直接执行,如下
-
message :只有请求响应消息派发到业务线程池,其他的消息如连接消息、断开连接、心跳事件等直接在 IO 线程上执行
-
execution :只把请求类消息派发到业务线程池,但是响应、连接、断开、心跳等消息直接在 IO上执行
-
connection :在 IO 线程上将连接、断开消息放入队列,有序逐个执行,其他消息派发到业务线程池处理。
我们这里举个例子来帮助理解:
我们这里把 Boss 线程池理解为医院挂号护士, Workder线程池理解为医院问诊医生。传统的 Netty 就是靠这两个线程池完成工作,由 护士分发患者找不同的医生问诊 (boss 线程池分发新连接给 worker 线程池处理)。而 Dubbo 的线程模型相当于给 问诊医生 添加了很多助理医生(业务线程池)。这时候问诊医生在处理一些耗时操作时可以交由助理医生来帮助完成,问诊医生可以继续接诊下一个病人,防止后面的病人等不及(worker 线程池接收到请求后可以交由 业务线程池处理,自身便可继续接受下一个请求,防止请求堆积阻塞)。而我们通过 Dubbo 线程模型 来规定助理医生可以帮助处理哪些事情。如 : AllDispatcher 则表示所有的事件都会交由助理医生 (业务线程池)处理;DirectDispatcher 表示所有的事情都不交给助理医生(业务线程池)处理 等。
2.2 ChannelHandlers#wrap
上面介绍完了Dubbo线程模型基础概念,下面我们来看一下ChannelHandlers#wrap 的具体实现,如下:
public class ChannelHandlers {
// 保存 ChannelHandlers 实例
private static ChannelHandlers INSTANCE = new ChannelHandlers();
protected ChannelHandlers() {
}
// 进行包装
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
// 获取自身的实例,INSTANCE 是 单例
protected static ChannelHandlers getInstance() {
return INSTANCE;
}
static void setTestingChannelHandlers(ChannelHandlers instance) {
INSTANCE = instance;
}
// 进行线程模型的包装
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
// 我们按照如下方式拆分一下
// 1. MultiMessageHandler
// 2. HeartbeatHandler
// 3. ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url))
return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)));
}
}
很显然,这里的关键逻辑在 所以这里的关键方法在于 ChannelHandlers#wrapInternal 中。ChannelHandlers#wrapInternal 方法使用了装饰器模式 对 Handler进行了装饰。我们这里拆解一下ChannelHandlers#wrapInternal 方法的实现 :
-
MultiMessageHandler
:使用 MultiMessageHandler 装饰 HeartbeatHandler ,其目的是使其可以处理多个请求事件。如下:public class MultiMessageHandler extends AbstractChannelHandlerDelegate { public MultiMessageHandler(ChannelHandler handler) { super(handler); } @SuppressWarnings("unchecked") @Override public void received(Channel channel, Object message) throws RemotingException { // 如果 message 类型是 MultiMessage,则Message由多个消息构成,遍历消息分别接收 if (message instanceof MultiMessage) { MultiMessage list = (MultiMessage) message; for (Object obj : list) { handler.received(channel, obj); } } else { handler.received(channel, message); } } }
-
HeartbeatHandler
:心跳Handler,其主要功能是处理心跳返回与心跳请求,直接在IO线程中执行,每次收到信息,更新通道的读事件戳,每次发送数据时,记录通道的写事件戳。public class HeartbeatHandler extends AbstractChannelHandlerDelegate { ... @Override public void connected(Channel channel) throws RemotingException { // 记录读写时间戳 setReadTimestamp(channel); setWriteTimestamp(channel); // 消息透传给下一层 Handler handler.connected(channel); } @Override public void disconnected(Channel channel) throws RemotingException { // 记录读写时间戳 clearReadTimestamp(channel); clearWriteTimestamp(channel); // 消息透传给下一层 Handler handler.disconnected(channel); } @Override public void sent(Channel channel, Object message) throws RemotingException { // 记录写时间戳 setWriteTimestamp(channel); // 消息透传给下一层 Handler handler.sent(channel, message); } @Override public void received(Channel channel, Object message) throws RemotingException { // 记录读时间戳 setReadTimestamp(channel); // 如果是心跳请求 if (isHeartbeatRequest(message)) { Request req = (Request) message; // 心跳请求需要回复 if (req.isTwoWay()) { // 回复心跳消息 Response res = new Response(req.getId(), req.getVersion()); res.setEvent(Response.HEARTBEAT_EVENT); channel.send(res); ... 打印日志 } return; } // 如果是心跳响应 if (isHeartbeatResponse(message)) { ... 打印日志 return; } // 不是心跳消息则透传给下一层 handler handler.received(channel, message); } .... }
-
ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension().dispatch(handler, url)
: 这里通过 SPI 机制,返回合适的Dispatcher 实现类,完成基于Dubbo线程模型的事件派发。ExtensionLoader.getExtensionLoader(Dispatcher.class).getAdaptiveExtension()
获取一个 Dispatcher 适配器,随后调用 Dispatcher#dispatch 返回一个线程模型的 ChannelHandler@SPI(AllDispatcher.NAME) public interface Dispatcher { @Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"}) // The last two parameters are reserved for compatibility with the old configuration ChannelHandler dispatch(ChannelHandler handler, URL url); }
这里可以看到,整个 Dubbo线程模型的引用就是一个大型的包装过程, 当 原始 的 Handler (即图中的 DecodeHandler ) 经过 ChannelHandlers#wrap 包装后返回的 Handler 结构如下:
MultiMessageHandler#received -> HeartbeatHandler#received -> Dubbo线程模型指定的 Handler#received -> DecodeHandler#received -> HeaderExchangeHandler#received -> DubboProtocol.requestHandler#received
综上当有消息到达时,流转如下:
- MultiMessageHandler#received 判断消息类型后透传给 HeartbeatHandler#received
- HeartbeatHandler#received 处理心跳消息,如果不是心跳消息则透传给 线程模型指定的 Handler,我们这里假设是默认的 AllChannelHandler 。
- AllChannelHandler#received 中会获取业务线程池中线程,在线程中执行 DecodeHandler#received 方法。
3. Netty 服务开启
NettyServer 的父类 AbstractServer 在构造函数中开启了Netty 服务。其实现如下:(需要注意,此时构造函数中的 handler 是 经历过Dubbo线程模型包装的 MultiMessageHandler。 )
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
/************** 1. 准备工作 ***********/
// 获取部分属性和编解码器
super(url, handler);
// 获取指定的到本地地址(ip:port) 如: /192.168.131.10:20880
localAddress = getUrl().toInetSocketAddress();
// 获取绑定的ip和端口, Netty 服务启动后会绑定 bind.ip 的 bind.port端口,监听消息。因为可能存在多网卡,此时则需要指定暴露哪个ip。
// 如果配置了< dubbo:parameter key = “bind.ip” value = “”/> 与 < dubbo:parameter key = “bind.port” />,则用该IP与端口创建bindAddress,通常用于多网卡,如果未配置,bindAddress与 localAddress绑定的IP与端口一样
String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
if (url.getParameter(Constants.ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
bindIp = NetUtils.ANYHOST;
}
// Netty 将要绑定的地址
bindAddress = new InetSocketAddress(bindIp, bindPort);
// 获取最大可接受连接数,默认为 0,不限制
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
// 获取空闲时间, 默认 600s
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
/************** 2. 开启服务 ***********/
try {
// 正式在相应端口建立网络监听
doOpen();
//... 日志打印
} catch (Throwable t) {
//... 日志打印
}
/************** 3. 获取业务线程池 ***********/
//fixme replace this with better method
// 从数据存储类 dataStore 中获取执行任务的线程池。
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}
我们按照注释顺序进行解析
3.1 准备工作
准备工作主要是 暴露 Ip、Port 、最大连接数的确认等操作。 该部分的代码注释比较清楚,这里不再赘述。
3.2 服务开启
开启服务是通过 doOpen 方法,而 doOpen 方法在 AbstractServer 并没有实现,而是交由子类实现。所以该方法实现在 org.apache.dubbo.remoting.transport.netty4.NettyServer
中。在 org.apache.dubbo.remoting.transport.netty4.NettyServer#doOpen
中, Dubbo完成了服务的建立,实现过程是常规的Netty流程,其实现如下:
@Override
protected void doOpen() throws Throwable {
bootstrap = new ServerBootstrap();
// 创建 boss 和 worker 线程池
bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
new DefaultThreadFactory("NettyServerWorker", true));
// 业务 handler,这里将Netty 的各种请求分发给NettyServer 的方法。
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
channels = nettyServerHandler.getChannels();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
// 添加解码器 handler
.addLast("decoder", adapter.getDecoder())
// 添加编码器 handler
.addLast("encoder", adapter.getEncoder())
// 添加业务handler
.addLast("handler", nettyServerHandler);
}
});
// bind
// 绑定ip端口,启用netty服务
ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
channelFuture.syncUninterruptibly();
channel = channelFuture.channel();
}
服务的建立是常规的 Netty 流程。我们这里主要关注添加到管道的三个处理器
- decoder : adapter.getDecoder() 是解码器,当服务接收到消息后使用该Handler对数据进行解码。
- encoder :adapter.getEncoder() 是编码器,当服务发送消息时使用该Handler对数据进行编码
- handler :nettyServerHandler 是业务处理器,是真正处理业务的地方。解码器解码后的数据会交由 nettyServerHandler 处理, nettyServerHandler 写回通道的数据会被编码器编码后写入信道。
关于编解码的内容,并非本文内容,如有需要,详参: Dubbo笔记 ㉓ : Dubbo协议与网络传输。
我们这里主要看一下业务处理器 NettyServerHandler 处理业务的过程。
3.2.1 NettyServerHandler
NettyServerHandler 的结构如下:
可以看到 NettyServerHandler 继承了 ChannelDuplexHandler 类,这就代表这 NettyServerHandler 具备处理读写消息的能力。而NettyServerHandler 的具体实现如下 :
/*
* ChannelInboundHandlerAdapter 中的一些方法和作用
* ` channelActive() ` > 在到服务器的连接已经建立之后将被调用(成为活跃状态)
* ` channelRead()` > 当从服务器接受到一条消息时被调用
* ` exceptionCaught()` > 在处理过程中引发异常时调用
* ` channelReigster() ` > 注册到EventLoop上
* ` handlerAdd() ` > Channel被添加方法
* ` handlerRemoved()` > Channel被删除方法
* ` channelInActive() ` > Channel离开活跃状态,不再连接到某一远端时被调用
* ` channelUnRegistered()` > Channel从EventLoop上解除注册
* ` channelReadComplete()` > 当Channel上的某个读操作完成时被调用
*/
// @Sharable 注解 :指示可以将相同的带注释的ChannelHandler实例添加到一个或多个ChannelPipeline ,而无需竞争条件。如果未指定此注释,则每次将其添加到管道时都必须创建一个新的处理程序实例,因为它具有未共享的状态,例如成员变量
@io.netty.channel.ChannelHandler.Sharable
public class NettyServerHandler extends ChannelDuplexHandler {
// 保存远程调用者的ip端口,对应的通道
private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>(); // <ip:port, channel>
private final URL url;
// 传入的ChannelHandler ,这里实际是 NettyServer
private final ChannelHandler handler;
public NettyServerHandler(URL url, ChannelHandler handler) {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
this.url = url;
this.handler = handler;
}
public Map<String, Channel> getChannels() {
return channels;
}
// 将 channelActive 交由 ChannelHandler#connected 来处理
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
if (channel != null) {
channels.put(NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel);
}
handler.connected(channel);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
// 将 channelInactive交由 ChannelHandler#disconnected来处理
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
channels.remove(NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()));
handler.disconnected(channel);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
// 将 channelRead交由 ChannelHandler#received来处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
handler.received(channel, msg);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
// 将 write 交由 ChannelHandler#sent来处理
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
super.write(ctx, msg, promise);
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
handler.sent(channel, msg);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
// 将 exceptionCaught交由 ChannelHandler#caught来处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
try {
handler.caught(channel, cause);
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.channel());
}
}
}
这里我们发现 NettyServerHandler 将 Netty 事件交由 NettyServerHandler#handler 来处理。NettyServerHandler#handler 是 NettyServerHandler 构造函数传入的。如下,此时的 NettyServerHandler#handler 为 NettyServer。
NettyServerHandler 将 Netty 的事件交由 NettyServer 来处理,具体为:
channelActive =》 ChannelHandler#connected : 客户端连接事件
channelInactive =》 ChannelHandler#disconnected : 客户端断连事件
channelRead =》 ChannelHandler#received : 服务端收到客户端消息(从管道中读取)
write =》 ChannelHandler#sent : 服务端向客户端发送消息(写入管道)
exceptionCaught =》 ChannelHandler#caught : 异常消息处理
令人惊(抓)喜(狂)的是,NettyServer 也会将 事件交由 NettyServer#handler 来处理。而
NettyServer#handler 是 Dubbo线程模型包装后的 Handler。如下:
而 NettyServer的内部handler 结构如下:
那么我们到这里可以知道整个消息的传递过程如下:
// NettyServerHandler 接收到netty 消息后透传给 内部 NettyServer处理,而 NettyServer 又交由 内部的 handler 处理。
Netty消息 => NettyServerHandler -> NettyServer -> NettyServer#handler
到这里,我们可以知道一下,Dubbo 中 对 Netty 消息的处理就是一个套娃套娃再套娃的过程。
-
关于 NettyServer#handler 中套娃的 Handler 的分析在之前的文章中或多或少有所提及。这里整理了一下内容,如有需要详参:Dubbo笔记衍生篇④:ChannelHandler
-
DubboProtocol#requestHandler 是真正处理消息的 Handler,整个消息的传递过程如下时序图( 如有需要详参: Dubbo笔记 ⑦ : 服务请求处理流程):
3.3 业务线程池获取
AbstractServer 在构造函数中会获取当前服务的业务线程池。 获取业务线程池的目的是在关闭或重置服务时会对业务线程池做对应的操作,如当当前服务关闭时会调用 AbstractServer#close, 在该方法中会关闭该服务的业务线程池。
DataStore 是 Dubbo SPI 接口,用于数据缓存,默认实现是 SimpleDataStore,使用Map 结构缓存数据。
在上面我们介绍过。在 Dubbo线程模型的 ChannelHandler 创建时会调用父类 WrappedChannelHandler 的构造函数。
如下:
public class AllChannelHandler extends WrappedChannelHandler {
public AllChannelHandler(ChannelHandler handler, URL url) {
// 调用 WrappedChannelHandler 的构造函数
super(handler, url);
}
...
}
而 WrappedChannelHandler 构造函数中会将当前 ChannelHandler 使用的业务线程池保存到 DataStore 中,也即是说 AbstractServer#executor 保存了当前线程模型所使用的业务线程池。
public WrappedChannelHandler(ChannelHandler handler, URL url) {
this.handler = handler;
this.url = url;
// 业务线程池
executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);
...
// 保存到 dataStore中
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
dataStore.put(componentKey, Integer.toString(url.getPort()), executor);
}
如当服务关闭时会调用 NettyServer#close,此时在 AbstractServer#close() 中会停止业务线程池
@Override
public void close() {
...
// 停止业务线程池。
ExecutorUtil.shutdownNow(executor, 100);
...
}
4. 总结
NettyServer 中完成了两件事:
- 启动了 Netty 服务。NettyServer 在 初始化时会调用 NettyServer#doOpen ,而 NettyServer#doOpen 中会启动 Netty 服务,并且添加了业务 Handler。
- 同样是在 NettyServer 构造时,上层传入的 Handler 会被 ChannelHandlers.wrap 包装,实现Dubbo线程策略的应用。
当服务端接收到 Netty 消息时,消息会经过如下几个 处理器(还有一些委托 handler 这里省略了)。
1. MultiMessageHandler :ChannelHandlers.wrap 中包装,用于多消息处理
2. HeartbeatHandler :ChannelHandlers.wrap 中包装,用于心跳消息处理
3. 线程策略 Handler : ChannelHandlers.wrap 中包装。应用 Dubbo线程策略
4. NettyServer :业务处理器 NettyServerHandler 内部的 Handler。本身作为 Handler没有做什么事情,仅仅透传消息请求到下层
5. DecodeHandler :上层传给 NettyServer 的 handler,用于解码消息内容
6. HeaderExchangeHandler :对消息做了一些简单的处理
7. DubboProtocol#requestHandler :真正处理消息的 Handler
以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://dubbo.apache.org/zh/docs/v2.7/dev/source/
https://dubbo.apache.org/zh/docs/v2.7/dev/design/
https://blog.csdn.net/weixin_34223655/article/details/88715901
https://www.jianshu.com/p/39c9fc1b9eff
https://blog.csdn.net/prestigeding/article/details/81079009
https://blog.csdn.net/prestigeding/article/details/81013642
https://blog.csdn.net/prestigeding/article/details/81165691
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正