前言
前段时间,从头开始将netty源码了解了个大概,但都是原理上理解。刚好博主对dubbo框架了解过一些,这次就以dubbo框架为例,详细看看dubbo这种出色的开源框架是如何使用netty的,又是如何与框架本身逻辑进行融合的。
本文分成两大部分,一部分是dubbo服务端对netty的封装,一部分是dubbo客户端对netty的封装,而每部分都分netty初始化和调用两个阶段,下面进入正题。
前言
前段时间,从头开始将netty源码了解了个大概,但都是原理上理解。刚好博主对dubbo框架了解过一些,这次就以dubbo框架为例,详细看看dubbo这种出色的开源框架是如何使用netty的,又是如何与框架本身逻辑进行融合的。
本文分成两大部分,一部分是dubbo服务端对netty的封装,一部分是dubbo客户端对netty的封装,而每部分都分netty初始化和调用两个阶段,下面进入正题。
[图片上传失败…(image-eb3834-1614247162101)]
一、Dubbo服务端
Dubbo服务端对netty的调用始于服务导出,在服务导出的最后,会调用DubboProtocol#openServer方法,就是在此方法中完成的netty服务端的初始化(本文均以配置了netty通信为前提),下面就以该处作为起点探寻。
1、服务端初始化
openServer方法源码如下,主体逻辑是先获取了address作为key—ip:port格式的字符串,然后做了一个双重检查,server不存在则调createServer创建一个放入serverMap中。到这里我们可以知道,dubbo服务提供者中一个ip+端口对应一个nettyServer,所有的nettyServer统一放在一个ConcurrentHashMap中维护了起来。但其实通常情况下,一个服务提供者的服务器,只会暴露一个端口给dubbo用,故虽然用Map存起来,但一般只会有一个nettyServer。此处还要注意,dubbo中是暴露一个服务提供者执行一次export方法,即一个服务提供者接口触发一次openServer方法、对应一个nettyServer,下面跟进server的创建过程。
1 private void openServer(URL url) {
2 // find server.
3 String key = url.getAddress();
4 //client can export a service which's only for server to invoke
5 boolean isServer = url.getParameter(IS_SERVER_KEY, true);
6 if (isServer) {
7 ProtocolServer server = serverMap.get(key);
8 if (server == null) {
9 synchronized (this) {
10 server = serverMap.get(key);
11 if (server == null) {
12 serverMap.put(key, createServer(url));
13 }
14 }
15 } else {
16 // server supports reset, use together with override
17 server.reset(url);
18 }
19 }
20 }
openServer调用的方法栈如下所示:
进入NettyTransporter的bind方法,NettyTransporter一共有两个方法-bind和connect,前者初始化服务端时调用,后者初始化客户端时触发,源码如下:
1 public class NettyTransporter implements Transporter {
2 public static final String NAME = "netty";
3 @Override
4 public RemotingServer bind(URL url, ChannelHandler listener) throws RemotingException {
5 return new NettyServer(url, listener);
6 }
7 @Override
8 public Client connect(URL url, ChannelHandler listener) throws RemotingException {
9 return new NettyClient(url, listener);
10 }
11 }
下面看NettyServer如何与netty关联起来的。先看下NettyServer的类图:
有经验的园友看到类图估计就能猜到,此处是源码框架中常用的分层抽象,AbstractServer作为一个模板的抽象,继承它之后可以扩展出其他类型的通信,比如MinaServer、GrizzlyServer。下面回到本文的主角NettyServer,看看其构造器:
1 public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
2 super(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME), ChannelHandlers.wrap(handler, url));
3 }
设置了一下url中的线程名参数,将handler和url进行了封装,然后调用了父类AbstractServer的构造器。
到这里,需要确定好入参的handler类型和传给父类构造器的handler类型。NettyServer构造器入参ChannelHandler是在HeaderExchanger#bind中封装的,方式如下:
1 public ExchangeServer bind(URL url, ExchangeHandler handler) throws RemotingException {
2 return new HeaderExchangeServer(Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
3 }
再进一步,bind方法入参ExchangeHandler的实现类要追溯到DubboProtocol,是其成员变量requestHandler如下:
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
// 省略若干个重写的方法逻辑
}
至此,NettyServer构造器入参ChannelHandler的类型已经确认了,其内部最终实现是DubboProtocol中的ExchangeHandlerAdapter,外部封装了一层HeaderExchangeHandler,又封装了一层DecodeHandler。简图如下:
搞清楚NettyServer构造器入参的ChannelHandler之后,下面跟进ChannelHandlers.wrap方法,最终封装方法如下:
1 protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
2 return new MultiMessageHandler(new HeartbeatHandler(ExtensionLoader.getExtensionLoader(Dispatcher.class)
3 .getAdaptiveExtension().dispatch(handler, url)));
4 }
而Dispatcher默认是AllDispatcher,其dispatch方法如下:
1 public ChannelHandler dispatch(ChannelHandler handler, URL url) {
2 return new AllChannelHandler(handler, url);
3 }
至此,ChannelHandlers.wrap方法执行完后得到的ChannelHandler结构如下,采用的是装饰器模式,层层装饰。
[图片上传失败…(image-8e480e-1614247162100)]
了解清楚了wrap方法,下面回到主线,进入AbstractServer的构造器:
1 public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
2 // 1、调用父类构造器将这两个变量存起来,最终是存在了AbstractPeer中
3 super(url, handler);
4 // 2、设置两个address
5 localAddress = getUrl().toInetSocketAddress();
6 String bindIp = getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost());
7 int bindPort = getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort());
8 if (url.getParameter(ANYHOST_KEY, false) || NetUtils.isInvalidLocalHost(bindIp)) {
9 bindIp = ANYHOST_VALUE;
10 }
11 bindAddress = new InetSocketAddress(bindIp, bindPort);
12 this.accepts = url.getParameter(ACCEPTS_KEY, DEFAULT_ACCEPTS);
13 this.idleTimeout = url.getParameter(IDLE_TIMEOUT_KEY, DEFAULT_IDLE_TIMEOUT);
14 try {
15 // 3、完成netty源码的调用-开启netty服务端
16 doOpen();
17 } catch (Throwable t) {
18 // 省略异常处理
19 }
20 // 4、获取/创建线程池
21 executor = executorRepository.createExecutorIfAbsent(url);
22 }
1/2的逻辑较简单,3和4才是重点,下面进入3处的doOpen方法,doOpen方法在AbstractServer中是抽象方法,所以要到其子类NettyServer中看:
1 protected void doOpen() throws Throwable {
2 // 这里可以看到熟悉的netty代码了
3 bootstrap = new ServerBootstrap();
4 // bossGroup一个线程
5 bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", tru