本系列参考官网文档、芋道源码的源码解读和《深入理解Apache Dubbo与实战》一书。Dubbo版本为2.6.1
这篇文章是为了理清Dubbo
里的线程模型、Handler
机制,顺便解释在服务暴露时候最后遗留下来的问题,即在最后的DubboProtocol#createServer(url)
方法里调用了Exchangers.bind(url, requestHandler)
做了什么。
我们将讨论当收到请求的时候是怎么从Netty转到Dubbo的逻辑来的,再介绍Handler的调用链路,并且分析如何将解码这一IO操作Dubbo的业务线程池做的
文章内容顺序:
1. Dubbo的线程模型
1.1 Dubbo的线程模型调用流程
1.2 Netty线程模型
1.3 Dubbo中的线程池
1.4 那么什么业务会由Dubbo自己的线程池来实现呢?
2. Handler的包装流程
2.1 Handler包装图解和描述
2.2 创建一个requestHandler
2.3 Exchange层做两次包装new DecodeHandler(new HeaderExchangeHandler(requestHandler))
2.4 HeaderExchangeHandler#received
2.5 DecodeHandler#received
2.6 Transporters#bind
2.6.1 ChannelHandlerDispatcher#received
2.7 NettyTransporter(netty3)
2.8 NettyServer构造方法
2.9 ChannelHandlers#wrap 又进行三层包装
2.10 AllChannelHandler 与Dubbo线程池耦合
2.10.1 ChannelEventRunnable 线程的执行逻辑
2.11 HeartbeatHandler 处理心跳请求
2.12 MultiMessageHandler 处理批量请求
2.13 NettyServer
2.14 NettyServer#doOpen创建Netty的执行链,包装到NettyHandler里
2.15 NettyHandler做了什么,为什么要封装?
3. Handler的调用顺序
4. 需要注意的几个类
5. Dubbo是怎么用到Netty的
1. Dubbo的线程模型模型
首先来讲讲Dubbo的线程模型,可以看下这篇博客的介绍,下面的图也是来此博客。
dubbo线程模型
1.1Dubbo的线程调用流程
image.png
客户端的主线程发出一个请求后获得future
,在执行get
时进行阻塞等待;
服务端使用worker线程
(netty通信模型)接收到请求后,将请求提交到server线程池
(Dubbo线程池)中进行处理
server
线程处理完成之后,将相应结果返回给客户端的worker
线程池(netty通信模型),最后,worker线程
将响应结果提交到client线程池
进行处理
client线程将
响应结果填充到future
中,然后唤醒等待的主线程,主线程获取结果,返回给客户端
这边再简单概括下博客的内容:
1.2Netty线程模型
Dubbo使用netty作为网络传输框架,所以我们先来简单了解下Netty,下图为Netty的线程模型。
image.png
Netty
中存在两种线程:boss线程
和worker线程
。
boss线程
的作用:
accept客户端的连接;将接收到的连接注册到一个worker线程上
个数:通常情况下,服务端每绑定一个端口,开启一个boss线程
worker线程
的作用:
处理注册在其身上的连接connection上的各种io事件
个数:默认是核数+1
注意:
一个worker线程可以注册多个connection
一个connection只能注册在一个worker线程上
1.3Dubbo中的线程池
为了配合worker线程工作,在Dubbo中还实现了自己的线程池来执行各种业务。
Dubbo扩展接口 ThreadPool 的SPI实现有如下几种:
- fixed:固定大小线程池,启动时建立线程,不关闭,一直持有(默认实现)。
coresize:200
maxsize:200
队列:SynchronousQueue
回绝策略:AbortPolicyWithReport - 打印线程信息jstack,之后抛出异常- cached:缓存线程池,空闲一分钟自动删除,需要时重建。
- limited:可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然带来大流量引起性能问题。
1.4那么什么业务会由Dubbo自己的线程池来实现呢?
有5种派发策略:
image.png
- 默认是all:所有消息都派发到Dubbo线程池,包括请求,响应,连接事件,断开事件,心跳等。 即worker线程接收到事件后,将该事件提交到业务线程池中,自己再去处理其他事。
- direct:worker线程接收到事件后,由worker执行到底。
- message:只有请求响应消息派发到Dubbo线程池,其它连接断开事件,心跳等消息,直接在 IO线程上执行
- execution:只请求消息派发到Dubbo线程池,不含响应(客户端线程池),响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行
- connection:在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到Dubbo线程池。
如图所示,各个策略的实现也很简单,直接返回了一个对应名称的XXXChannelHandler
,说明具体代码逻辑在这个Handler
里面实现。
以上就是基本的Dubbo线程模型了。
2. Handler的包装流程
接下来来讲讲Dubbo里的Handler,会一步步来分析他是怎么包装起来的。
参考如下:dubbo的handler机制
2.1 Handler包装图解和描述
这是使用
image.pngDubbo Protocol
并且使用Netty
作为服务器的情况下Handler
的整个包装过程
上图是来自参考链接的图的一部分,他的
image.pngTransport
层部分画错了,我就自己画了一个,如下图,比较简陋,下图就是完整的handler
包装。(我也知道你们喜欢彩色的图啊,可惜我懒)
1.在
DubboProtocol
中构建ExchangeHandler
命名requestHandler
2.在
Exchange
层做两次包装new DecodeHandler(new HeaderExchangeHandler(requestHandler))
,具体参考类:HeaderExchanger
① 使用HeaderExchangeHandler
做一次包装,HeaderExchangeHandler
的作用是实现了Reques
t和Response
的概念,当接到received
请求后,将请求转为reply
。请参考类HeaderExchangeHandler
,
② 使用DecodeHandler
做一次包装,DecodeHandler
的作用是用来对Request Message
和Response Message
做解码操作,解码完成后才能给HeaderExchangeHandler
使用。3.在
Exchange
层包装后的Handler
会被传递到Transporter
层(NettyTransporter
)并且把类型转换成ChannelHandler
,因为ChannelHandler
更为抽象。4.
Handler
在Transporter
层流转,会被传递到NettyServer
中5.在
NettyServer
中被AllChannelHandler
包装,其作用是把NettyServer
接收到的请求转移给Transporter
层的线程池来处理。同步转异步。6.接着就先被
HeartbeatHandler
包装用以处理心跳请求,接着被MultiMessageHandler
包装用以处理批量请求7.这个
MultiMessageHandler
会在NettyServer
中以构造函数的方式注入进来8.
NettyServer
被再次NettyHandler
包装,NettyHandler
的父类是SimpleChannelHandler
。它属于Netty
的Handler
。由Netty
来管理和调用其中的回调方法。Netty
在接受到channelActive
,channelRead
等方法后,会把请求转移给Dubbo
的Handler
,这样每当请求过来,Netty
的Handler
接到请求就立马把数据和相关信息转交给Dubbo
的Handler
,由Dubbo
的Handler
来管理了。
上面是简单的概括,我们来一个个上代码
2.2创建一个requestHandler
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
@Override
public Object reply(ExchangeChannel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
Invocation inv = (Invocation) message;
// 获得请求对应的 Invoker 对象
Invoker<?> invoker = getInvoker(channel, inv);
// 如果是callback 需要处理高版本调用低版本的问题
if (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
String methodsStr = invoker.getUrl().getParameters().get("methods");
boolean hasMethod = false;
if (methodsStr == null || !methodsStr.contains(",")) {
hasMethod = inv.getMethodName().equals(methodsStr);
} else {
String[] methods = methodsStr.split(",");
for (String method : methods) {
if (inv.getMethodName().equals(method)) {
hasMethod = true;
break;
}
}
}
if (!hasMethod) {
logger.warn(new IllegalStateException("The methodName " + inv.getMethodName() + " not found in callback service interface ,invoke will be ignored. please update the api interface. url is:" + invoker.getUrl()) + " ,invocation is :" + inv);
return null;
}
}
// 设置调用方的地址
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
// 执行调用
return invoker.invoke(inv);
}
throw new RemotingException(channel, message.getClass().getName() + ": " + message
+ ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
}
@Override
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Invocation) {
this.reply((ExchangeChannel) channel, message);
} else {
super.received(channel, message);
}
}
@Override
public void connected(Channel channel) {
this.invoke(channel, Constants.ON_CONNECT_KEY);
}
@Override
public void disconnected(Channel channel) throws RemotingException {
if (logger.isInfoEnabled()) {
logger.info("disconected from " + channel.getRemoteAddress() + ",url:" + channel.getUrl());
}
this.invoke(channel, Constants.ON_DISCONNECT_KEY);
}
/**
* 调用方法
*
* @param channel 通道
* @param methodKey 方法名
*/
private void invoke(Channel channel, String methodKey) {
// 创建 Invocation 对象
Invocation invocation = createInvocation(channel, channel.getUrl(), methodKey);
// 调用 received 方法,执行对应的方法
if (invocation != null) {
try {
this.received(channel, invocation);
} catch (Throwable t) {
logger.warn("Failed to invoke event method " + invocation.getMethodName() + "(), cause: " + t.getMessage(), t);
}
}
}
private Invocation createInvocation(Channel channel, URL url, String methodKey) {
String method = url.getParameter(methodKey);
if (method == null || method.length() == 0) {
return null;
}
RpcInvocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
invocation.setAttachment(Constants.PATH_KEY, url.getPath());
invocation.setAttachment(Constants.GROUP_KEY, url.getParameter(Constants.GROUP_KEY));
invocation.setAttachment(Constants.INTERFACE_KEY, url.getParameter(Constants.INTERFACE_KEY));
invocation.setAttachment(Constants.VERSION_KEY, url.getParameter(Constants.VERSION_KEY));
if (url.getParameter(Constants.STUB_EVENT_KEY, false)) {
invocation.setAttachment(Constants.STUB_EVENT_KEY, Boolean.TRUE.toString());
}
return invocation;
}
};
image.png
他实现了ChannelHandler
接口5个关键方法,连接,断开连接,发送消息,接受消息和异常处理方法。也是rpc调用的常用处理方法。 同时也是线程派发处理关注的方法。
而requestHandler
第一次用到,也就是在服务暴露那篇最后提的DubboProtocol#Exchangers.bind(url, requestHandler)
方法中
2.3 Exchange层做两次包装new DecodeHandler(new HeaderExchangeHandler(requestHandler))
进去后发现
image.pngExchangers
是个门面类,调用的是HeaderExchanger#bind
方法,事实上也只有HeaderExchanger
这一个实现。如下图
image.png
接着来看HeaderExchanger#bind
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))));
}
}
注意,这里我们的requestHandler
直接被包装了两层,对应的是最上面图里的Exchange层的包装,从里到外看看这两个包装的Handler
做了什么(主要看他的received
方法)
2.4 HeaderExchangeHandler#received
/**
* ExchangeReceiver
*
* 基于消息头部( Header )的信息交换处理器实现类
*/
public class HeaderExchangeHandler implements ChannelHandlerDelegate {
public void received(Channel channel, Object message) throws RemotingException {
// 设置最后的读时间
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
// 创建 ExchangeChannel 对象
ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
// 处理请求( Request )
if (message instanceof Request) {
// handle request.
Request request = (Request) message;
// 处理事件请求
if (request.isEvent()) {
handlerEvent(channel, request);
} else {
// 处理普通请求,判断是否要响应(即双向通信)
if (request.isTwoWay()) {
Response response = handleRequest(exchangeChannel, request);
// 将调用结果返回给服务消费端
channel.send(response);
// 如果是单向通信,仅向后调用指定服务即可,无需返回调用结果
} else {
handler.received(exchangeChannel, request.getData());
}
}
// 处理响应( Response )
} else if (message instanceof Response) {
handleResponse(channel, (Response) message);
// 处理 String
} else if (message instanceof String) {
// 客户端侧,不支持 String
if (isClientSide(channel)) {
Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
logger.error(e.getMessage(), e);
// 服务端侧,目前是 telnet 命令
} else {
String echo = handler.telnet(channel, (String) message);
if (echo != null && echo.length() > 0) {
channel.send(echo);
}
}
// 提交给装饰的 `handler`,继续处理
} else {
handler.received(exchangeChannel, message);
}
} finally {
// 移除 ExchangeChannel 对象,若已断开
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}
//省略其他代码
}
代码注释已经比较清晰,简单来说就是会辨别收到的message
是服务端收到的Request还是消费端发起请求后得到的Reponse
进行一系列业务判断操作(比如对于不同的请求,有事件请求、需要响应的和不需要响应的,都有不同的执行逻辑)
如果需要响应还会传到更里面一层也就是我们的requestHandler
执行received
方法
再来看看在更外面一层的DecodeHandler
2.5DecodeHandler#received
public class DecodeHandler extends AbstractChannelHandlerDelegate {
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof Decodeable) {
// 对 Decodeable 接口实现类对象进行解码
decode(message);
}
if (message instanceof Request) {
// 对 Request 的 data 字段进行解码
decode(((Request) message).getData());
}
if (message instanceof Response) {
// 对 Request 的 result 字段进行解码
decode(((Response) message).getResult());
}
// 执行后续逻辑
handler.received(channel, message);
}
private void decode(Object message) {
if (message != null && message instanceof Decodeable) {
try {
((Decodeable) message).decode(); // 解析消息
if (log.isDebugEnabled()) {
log.debug(new StringBuilder(32).append("Decode decodeable message ").append(message.getClass().getName()).toString());
}
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.warn(new StringBuilder(32).append("Call Decodeable.decode failed: ").append(e.getMessage()).toString(), e);
}
} // ~ end of catch
} // ~ end of if
} // ~ end of method decode
//省略其他代码
}
注意看这个
received
中的方法,很明显这个DecodeHandler
就是用来解码的,根据不同的message类型,执行不同的逻辑,,这里的if (message instanceof Decodeable)
判断下的代码就是我们解码篇提到的交由业务线程池来执行解码操作。
每个Handler都会执行到这一步,如果被解码过自然不用再解码了(在DecodeableRpcInvocation
或者DecodeableRpcResult
中会有是否解码已完成的标志位),如果还未解码,当前线程就会进行解码操作
接下来进入到Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))
中的Transporters#bind
方法
2.6 Transporters#bind
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
// 创建 handler
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
// 创建 Server 对象
return getTransporter().bind(url, handler);
}
同样是门面类,会调用真正的Transporter
来执行bind方法,
这里我们只传了一个ChannelHandler
对象,所以直接到了rerun
,如果ChannelHandler
有多个的情况下,说明这些handler是同级的,new ChannelHandlerDispatcher(handlers)
每个实现的方法,都会循环调用 channelHandlers
的方法
2.6.1ChannelHandlerDispatcher#received
如下面的代码例子所示:
public class ChannelHandlerDispatcher implements ChannelHandler {
public void received(Channel channel, Object message) {
for (ChannelHandler listener : channelHandlers) {
try {
listener.received(channel, message);
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
}
}
//省略其他代码
}
那我们便直接来到真正的
image.pngTransporter#bind
方法中一探究竟。
这个Transporter有四种实现,其中又有
netty3
和netty4
,默认实现为netty3
,我们就来看看netty3的实现吧
netty3的NettyTransporter 实现如下
2.7 NettyTransporter(netty3)
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);
}
}
接着来看NettyServer
的实现
2.8 NettyServer构造方法
public class NettyServer extends AbstractServer implements Server {
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
}
又是一层对handler
的包装,调用了ChannelHandlers#wrap
方法,跟进去看看
这里注意一下!!!我们先关注
ChannelHandlers#warp
的调用,这里的super
方法我们最后会分析。
2.9 ChannelHandlers#wrap 又进行三层包装
public class ChannelHandlers {
/**
* 单例
*/
private static ChannelHandlers INSTANCE = new ChannelHandlers();
public static ChannelHandler wrap(ChannelHandler handler, URL url) {
return ChannelHandlers.getInstance().wrapInternal(handler, url);
}
protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(
new HeartbeatHandler(
ExtensionLoader.getExtensionLoader(Dispatcher.class)
.getAdaptiveExtension().dispatch(handler, url)
)
);
}
//省略其他方法
}
wrap
方法直接调用了他自己实现的wrapInternal
方法,套了一层又一层。
注意ExtensionLoader.getExtensionLoader(Dispatcher.class) .getAdaptiveExtension().dispatch(handler, url)
这一段代码,dispatch(handler, url)
前面很好理解,就是拿到Dispatcher
这个接口的扩展类,我们的默认扩展类是AllDispatcher
,在前面已经介绍过了,我们就以默认的类来继续讲,后面直接调用了这个AllDispatcher#dispatch
方法,这个我们前面也同样介绍过,再来简单复习下下面的图,就是直接把我们的handler又又又封装了一层,封装到了AllChannelHandler
中。那么就来看看这个AllChannelHandler
又做了什么吧
2.10 AllChannelHandler 与Dubbo线程池耦合
/**
* `all` 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
*/
public class AllChannelHandler extends WrappedChannelHandler {
public AllChannelHandler(ChannelHandler handler, URL url) {
super(handler, url);
}
public void received(Channel channel, Object message) throws RemotingException {
ExecutorService cexecutor = getExecutorService();
try {
cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
} catch (Throwable t) {
//TODO A temporary solution to the problem that the exception information can not be sent to the opposite end after the thread pool is full. Need a refactoring
//用来解决线程池已满后无法将异常信息发送到另一端的问题的临时解决方案,仍然需要重构
if(message instanceof Request && t instanceof RejectedExecutionException){
Request request = (Request)message;
if(request.isTwoWay()){
String msg = "Server side(" + url.getIp() + "," + url.getPort() + ") threadpool is exhausted ,detail msg:" + t.getMessage();
Response response = new Response(request.getId(), request.getVersion());
response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR);
response.setErrorMessage(msg);
channel.send(response);
return;
}
}
throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);
}
}
}
这个类还调用了父类的有参构造,父类的构造方法里面就是通过SPI机制拿到url后创建对应的线程池(还记得我们Dubbo中的三种线程池不,默认是fixed
),这里就不贴代码了。
还是来看看这个AllChannelHandler#received
方法,直接调用了线程池来执行方法。
2.10.1 ChannelEventRunnable 线程的执行逻辑
熟悉线程池的小伙伴肯定猜出来了,这个执行的ChannelEventRunnable
肯定是实现了Runnable接口的类了。再来进去看看他的执行逻辑吧
public class ChannelEventRunnable implements Runnable {
public void run() {
switch (state) {
case CONNECTED:
try {
handler.connected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case DISCONNECTED:
try {
handler.disconnected(channel);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel, e);
}
break;
case SENT:
try {
handler.sent(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is " + message, e);
}
break;
case RECEIVED:
try {
handler.received(channel, message);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is " + message, e);
}
break;
case CAUGHT:
try {
handler.caught(channel, exception);
} catch (Exception e) {
logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel
+ ", message is: " + message + ", exception is " + exception, e);
}
break;
default:
logger.warn("unknown state: " + state + ", message is " + message);
}
}
//省略其他代码
}
这里的线程池很明显,用了case
语句来判断到底执行的我们传入的包装类DecodeHandler
里的什么方法。(AllChannelHandler里面的一层是DecodeHandler)
到了这里,我们别忘了在ChannelHandlers#wrapInternal
还没分析完,外面还有一层HeartbeatHandler
和一层MultiMessageHandler
。先来看HeartbeatHandler
2.11 HeartbeatHandler 处理心跳请求
public class HeartbeatHandler extends AbstractChannelHandlerDelegate {
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);
if (logger.isInfoEnabled()) {
int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);
if (logger.isDebugEnabled()) {
logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()
+ ", cause: The channel has no data-transmission exceeds a heartbeat period"
+ (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));
}
}
}
return;
}
// 如果是心跳事件响应,返回
if (isHeartbeatResponse(message)) {
if (logger.isDebugEnabled()) {
logger.debug(new StringBuilder(32).append("Receive heartbeat response in thread ").append(Thread.currentThread().getName()).toString());
}
return;
}
// 提交给装饰的 `handler`,继续处理
handler.received(channel, message);
}
//省略其他代码
}
可以看到HeartbeatHandler
对received
方法进行了处理,所以心跳的消息的接受和发送是不会派发到Dubbo线程池
的。
接着是外面的那层MultiMessageHandler啦
2.12 MultiMessageHandler 处理批量请求
public class MultiMessageHandler extends AbstractChannelHandlerDelegate {
public MultiMessageHandler(ChannelHandler handler) {
super(handler);
}
@SuppressWarnings("unchecked")
@Override
public void received(Channel channel, Object message) throws RemotingException {
if (message instanceof MultiMessage) { // 多消息
MultiMessage list = (MultiMessage) message;
for (Object obj : list) {
handler.received(channel, obj);
}
} else {
handler.received(channel, message);
}
}
}
从他的名字就可以看出来主要是处理多消息的,主要完成多消息类型的循环解析接收。
这里来做下收尾:还记得我当时三个!!!的NettyServer
构造方法调用的父类构造方法嘛,一起来看看。
2.13 NettyServer
public abstract class AbstractServer extends AbstractEndpoint implements Server {
public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, handler);
// 服务地址
localAddress = getUrl().toInetSocketAddress();
// 绑定地址
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;
}
bindAddress = new InetSocketAddress(bindIp, bindPort);
// 服务器最大可接受连接数
this.accepts = url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS);
// 空闲超时时间
this.idleTimeout = url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT);
// 开启服务器
try {
doOpen();
if (logger.isInfoEnabled()) {
logger.info("Start " + getClass().getSimpleName() + " bind " + getBindAddress() + ", export " + getLocalAddress());
}
} catch (Throwable t) {
throw new RemotingException(url.toInetSocketAddress(), null, "Failed to bind " + getClass().getSimpleName()
+ " on " + getLocalAddress() + ", cause: " + t.getMessage(), t);
}
// 获得线程池
//fixme replace this with better method
DataStore dataStore = ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension();
executor = (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort()));
}
//省略其他代码
}
这个AbstractServer
就是NettyServer
的父类,可以看到这个构造方法中,开启服务器时调用了doOpen()
方法,这是交由子类自己的实现的方法。(注意,我们这里只关注handler
的传递过程,其他代码的介绍就略过啦)
2.14 NettyServer#doOpen创建Netty的执行链,包装到NettyHandler里
public class NettyServer extends AbstractServer implements Server {
public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}
@Override
protected void doOpen() {
// 设置日志工厂
NettyHelper.setNettyLoggerFactory();
// 创建线程池
ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));
ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));
// 创建 ChannelFactory 对象
ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));
// 实例化 ServerBootstrap
bootstrap = new ServerBootstrap(channelFactory);
// 创建 NettyHandler 对象,着重注意!!!
final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
// 设置 `channels` 属性
channels = nettyHandler.getChannels();
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() {
// 创建 NettyCodecAdapter 对象
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;
}
});
// 服务器绑定端口监听
// bind
channel = bootstrap.bind(getBindAddress());
}
//省略其他代码
}
这个就是
image.pngNettyServer#doOpen()
的代码啦,注意代码打上着重注意!!!的两行,
一行是final NettyHandler nettyHandler = new NettyHandler(getUrl(), this);
将这个NettyServer
又包装进了NettyHandler
里,
还有一行是pipeline.addLast("handler", nettyHandler);
,这是Netty的机制,
可以参考详细讲解Netty中Pipeline责任链做一个简单的了解,这里解释了为什么收到消息后能调用到我们的Handler。
顺带一提,NettyServer
其实也实现了ChannelHandler
,他也算是个"handler",看下图。
闲话至此,我们来看下NettyHandler做了什么,为什么要封装?
2.15 NettyHandler做了什么,为什么要封装?
@Sharable
public class NettyHandler extends SimpleChannelHandler {
public NettyHandler(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 void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);
try {
handler.received(channel, e.getMessage());
} finally {
NettyChannel.removeChannelIfDisconnected(ctx.getChannel());
}
}
//省略其他代码
}
这个
image.pngSimpleChannelHandler
是Netty
的接口,说明这个NettyHandler
是Netty handler服务
与Dubbo handler职责
交接的地方,Netty
首先接收到信息后在内部进行一连串调用,然后调用到Dubbo
自己实现的Netty接口的方法,从此开始在Dubbo
中进行方法的调用。
再顺带一提,当我们debug
的时到NettyHandler#messageReceived
这行的时候,
这里的handler
一个个分解下去,就是包装的顺序啦,看下图。
这里再再顺便提一嘴,我们介绍的都是以Netty3
,如果换成Netty4
实现的话,最外面的handler类名
不是现在的NettyHandle
r而是NettyServerHandler
。(一开始我迷糊了后来才知道所以在这一提,可能会有跟我一样迷糊的人呢)
3. Handler的调用顺序
至此,我们的Handler终于分析完毕了!,再来捋一遍调用的顺序:
- netty处理接收请求
- ->NioServerSocketPipelineSink$Boss
- ->注册NioWorker线程用于处理长连接和IO操作
- ->DefaultChannelPipeline(处理请求的反序列化和分发handler,初始化过程见NettyServer,会往pipeline加入decoder、encoder和NettyHandler)
- ->decoder做反序列化
- ->NettyHandler处理请求
- ->NettyServer处理请求(NettyServer初始化封装了MultiMessageHandler、HeartbeatHandler以及根据dispatcher得到的channelHandler(里面还有封装,就不往下说了),默认是AllDispatcher)
- ->MultiMessageHandler for each处理批量请求
- ->HeartbeatHandler处理心跳请求
- ->AllDispatcher得到的AllChannelHandler处理将通道所有状态变更用新线程处理(包括建立连接、断开连接、接收请求、异常)
- ->从指定的线程池拿到线程执行rpc请求
- ->DecodeHandler进行对message的解码,此操作可能会被放在work线程进行,如果work线程已经解码过了,得到的message内部会有标识,解码的方法会直接返回进行下一步操作
- ->HeaderExchangeHandler判断双向和单向处理请求,双向则将结果使用当前channel回写
- ->到DubboProtocol$ExchangeHandlerAdapter处理请求调用
- ->到此服务端网络传输层结束
4. 需要注意的几个类
1.DubboProtocol类,Dubbo Handler初始化创建的地方
2.HeaderExchangeHandler类,Request和Response概念重点提现的地方
3.DecodeHandler类,Dubbo线程池帮忙解码的地方
3.NettyHandler类,Netty Handler服务与Dubbo Handler职责交接的地方
5. Dubbo是怎么用到Netty的?
可以看到,
dubbo
使用Netty
还是挺简单的,消费者使用NettyClient
,提供者使用NettyServer
,Provider
启动的时候,会开启端口监听,使用我们平时启动Netty
一样的方式。而Client
在Spring getBean
的时候,会创建Client
,当调用远程方法的时候,将数据通过dubbo
协议编码发送到NettyServer
,然后NettServer
收到数据后解码,并调用本地方法,并返回数据,完成一次完美的 RPC 调用。
网友评论