学习Dubbo-带你通过源码看看dubbo对netty的使用(2),阿里、字节跳动、京东、腾讯、小米等名企高频面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

学习Dubbo-带你通过源码看看dubbo对netty的使用

搞清楚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”, true));
6 // workerGroup线程数取的CPU核数+1与32的小值
7 workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
8 new DefaultThreadFactory(“NettyServerWorker”, true));
9 // ***1、此处将NettyServer封装进NettyServerHandler中,实现了netty和dubbo的连接
10 final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
11 channels = nettyServerHandler.getChannels();
12 // netty封装
13 bootstrap.group(bossGroup, workerGroup)
14 .channel(NioServerSocketChannel.class)
15 .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
16 .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
17 .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
18 .childHandler(new ChannelInitializer() {
19 @Override
20 protected void initChannel(NioSocketChannel ch) throws Exception {
21 int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
22 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
23 if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
24 ch.pipeline().addLast(“negotiation”,
25 SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
26 }
27 ch.pipeline()
28 .addLast(“decoder”, adapter.getDecoder())
29 .addLast(“encoder”, adapter.getEncoder())
30 .addLast(“server-idle-handler”, new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
31 .addLast(“handler”, nettyServerHandler);
32 }
33 });
34 // 绑定IP和端口,此处用到的就是AbstractServer中的bindAddress变量
35 ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
36 channelFuture.syncUninterruptibly();
37 channel = channelFuture.channel();
38 }

标星号***的1处就是关键点,看下面的pipeline.addLast可知,存放着dubbo逻辑的NettyServer被封装进了NettyServerHandler中,进而放入了pipeline里面,当有客户端连接的时候就会触发这个nettyServerHandler中的对应方法,进入dubbo的接口调用逻辑。从dubbo功能到netty框架之间的连接者就是这个NettyServerHandler类。NettyServer中封装了一个线程池,即一个客户端连接过来之后,服务端用一个线程池来接收处理这个客户端的一系列请求,即在netty原有线程模型基础上又加了一层线程池。

4中的executorRepository.createExecutorIfAbsent(url)用于生成线程池,此处为服务端,点进去源码可以发现在dubbo的服务端,一个port端口对应一个线程池,而且此处未设置特殊的参数,故走ThreadPool的默认类型fixed,即FixedThreadPool的getExecutor方法:

1 public Executor getExecutor(URL url) {
2 String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
3 int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS);
4 int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
5 return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
6 queues == 0 ? new SynchronousQueue() :
7 (queues < 0 ? new LinkedBlockingQueue()
8 : new LinkedBlockingQueue(queues)),
9 new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
10 }

该方法可关注两点:线程数默认为200个,阻塞队列由于queues==0采用的是SynchronousQueue。这个线程池初始化之后干啥用?搜遍了调用关系,发现只有在NettyServer进行重置或者关闭时才会操作这个线程池。但理论上讲不通啊,总不能创建了一个线程池之后只是为了关闭它。且往下看。

2、服务端调用

其实服务端的线程池这里给博主看源码一个启发,注意,此处是去仓库获取一个线程池的引用(即executorRepository.createExecutorIfAbsent(url)),而仓库创建了线程池是将其缓存了起来,而缓存之后的线程池引用还可以暴露给其他地方,在其他地方执行线程池的execute方法。具体在这里,最终是在AllChannelHandler中调用的线程池,比如connected方法,如下所示,getExecutorService方法就是去仓库中获取了服务端的这个线程池,封装出一个ChannelEventRunnable丢给线程池执行。而服务端接收到请求时的received方法也是同样的处理流程。

1 public void connected(Channel channel) throws RemotingException {
2 ExecutorService executor = getExecutorService();
3 try {
4 executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.CONNECTED));
5 } catch (Throwable t) {
6 throw new ExecutionException(“connect event”, channel, getClass() + " error when process connected event .", t);
7 }
8 }

下面结合netty的调用流程对服务调用时的处理流程做一个梳理:netty的ServerBootstrap启动后,会开启bossGroup中的那个线程(即Reactor线程),一直执行run方法。而当有客户端要连接时,select方法会从操作系统获取到一个连接事件,Reactor线程会为该连接方创建一个NioSocketChannel,并从workerGroup中挑选一个线程,运行run方法,该线程用于处理服务端与这个客户端的后续通讯。而此处添加进pipeline中的nettyServerHandler会在客户端传来读写请求时触发对应的方法。最终调用到上述AllChannelHandler中的对应方法,用线程池执行后续业务逻辑。

二、Dubbo的客户端

1、客户端初始化

dubbo客户端初始化时会调用RegistryProtocol的refer方法,几经周折,最后到了DubboProtocol的protocolBindingRefer方法,如下,其中第5行调用的getClients方法是与netty整合的重点,即生成连接服务端的客户端。注意此处是在客户端中每一个引入的服务接口对应一个DubboInvoker。

1 public Invoker protocolBindingRefer(Class serviceType, URL url) throws RpcException {
2 optimizeSerialization(url);
3
4 // create rpc invoker.
5 DubboInvoker invoker = new DubboInvoker(serviceType, url, getClients(url), invokers); // 为每个invoker生成对应的nettyClient
6 invokers.add(invoker);
7
8 return invoker;
9 }

继续跟进,会进入NettyTransporter的connect方法,到这里应该会很熟悉,因为服务端初始化时调用的是该类下面的bind方法。bind方法初始化的是NettyServer对象,而connect初始化的是NettyClient对象。

public class NettyTransporter implements Transporter {

public static final String NAME = “netty”;

@Override
public RemotingServer bind(URL url, ChannelHandler listener) throws RemotingException {
return new NettyServer(url, listener);
}

@Override
public Client connect(URL url, ChannelHandler listener) throws RemotingException {
return new NettyClient(url, listener);
}

}

NettyClient的类图结构与NettyServer类似:

[图片上传失败…(image-881bf2-1614247162099)]

下面看NettyClient的构造器:

1 public NettyClient(final URL url, final ChannelHandler handler) throws RemotingException {
2 // you can customize name and type of client thread pool by THREAD_NAME_KEY and THREADPOOL_KEY in CommonConstants.
3 // the handler will be warped: MultiMessageHandler->HeartbeatHandler->handler
4 super(url, wrapChannelHandler(url, handler));
5 }

直接调用了父类构造器,其中wrapChannelHandler方法与NettyServer中的一样,不再赘述。下面看父类构造器:

1 public AbstractClient(URL url, ChannelHandler handler) throws RemotingException {
2 super(url, handler);
3
4 needReconnect = url.getParameter(Constants.SEND_RECONNECT_KEY, false);
5 // 1、初始化客户端线程池
6 initExecutor(url);
7
8 try {
9 doOpen(); // 2、创建客户端的Bootstrap
10 } catch (Throwable t) {
11 // 省略异常处理
12 }
13 try {
14 // 3、连接Netty服务端
15 connect();
16 } catch (RemotingException t) {
17 // 省略异常处理
18 } catch (Throwable t) {
19 close();
20 // 抛异常
21 }
22 }

主要有三步,已经在上面标出,下面分别跟进这三个方法。

1)、initExecutor方法直接先将线程池类型添加进url中,客户端默认是Cached类型,所以在调用executorRepository.createExecutorIfAbsent(url)时会进入CachedThreadPool中。

1 private void initExecutor(URL url) {
2 url = ExecutorUtil.setThreadName(url, CLIENT_THREAD_POOL_NAME);
3 url = url.addParameterIfAbsent(THREADPOOL_KEY, DEFAULT_CLIENT_THREADPOOL);
4 executor = executorRepository.createExecutorIfAbsent(url);
5 }

CachedThreadPool代码如下,可见是创建的核心线程数为0最大线程数无上限的线程池,阻塞队列默认SynchronousQueue。

1 public class CachedThreadPool implements ThreadPool {
2
3 @Override
4 public Executor getExecutor(URL url) {
5 String name = url.getParameter(THREAD_NAME_KEY, DEFAULT_THREAD_NAME);
6 int cores = url.getParameter(CORE_THREADS_KEY, DEFAULT_CORE_THREADS);
7 int threads = url.getParameter(THREADS_KEY, Integer.MAX_VALUE);
8 int queues = url.getParameter(QUEUES_KEY, DEFAULT_QUEUES);
9 int alive = url.getParameter(ALIVE_KEY, DEFAULT_ALIVE);
10 return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
11 queues == 0 ? new SynchronousQueue() :
12 (queues < 0 ? new LinkedBlockingQueue()
13 : new LinkedBlockingQueue(queues)),
14 new NamedInternalThreadFactory(name, true), new AbortPolicyWithReport(name, url));
15 }
16 }

2)、doOpen方法

实现逻辑在NettyClient中,都是正常的封装,变化的地方是将NettyClientHandler放入pipeline中。注意此处只是将Bootstrap初始化,但并未触发与服务端的连接。

3)、connect方法

该方法最终在NettyClient的doConnect方法中调用了bootstrap的connect方法,完成与服务端的连接。

2、客户端调用

在消费端调用服务端接口或者接收到服务端的返回结果时,均会触发NettyClientHandler中对应的方法,而此处跟NettyServerHandler类似,最终都是在AllChannelHandler中获取之前创建的客户端线程池(Cached类型的),用该线程池进行后续操作。

最后,来一张示意图做个调用的总结:

学习Dubbo-带你通过源码看看dubbo对netty的使用

一、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调用的方法栈如下所示:

学习Dubbo-带你通过源码看看dubbo对netty的使用

进入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的类图:

学习Dubbo-带你通过源码看看dubbo对netty的使用

有经验的园友看到类图估计就能猜到,此处是源码框架中常用的分层抽象,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。简图如下:

学习Dubbo-带你通过源码看看dubbo对netty的使用

搞清楚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-ccc763-1614246837626)]

了解清楚了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”, true));
6 // workerGroup线程数取的CPU核数+1与32的小值
7 workerGroup = new NioEventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
8 new DefaultThreadFactory(“NettyServerWorker”, true));
9 // ***1、此处将NettyServer封装进NettyServerHandler中,实现了netty和dubbo的连接
10 final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
11 channels = nettyServerHandler.getChannels();
12 // netty封装
13 bootstrap.group(bossGroup, workerGroup)
14 .channel(NioServerSocketChannel.class)
15 .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
16 .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
17 .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
18 .childHandler(new ChannelInitializer() {
19 @Override
20 protected void initChannel(NioSocketChannel ch) throws Exception {
21 int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
22 NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
23 if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
24 ch.pipeline().addLast(“negotiation”,
25 SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
26 }

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ch.pipeline().addLast(“negotiation”,
25 SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
26 }

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-pj5cgOwo-1713464933018)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值