netty是如何工作的:EventLoopGroup是做什么用的?
在上一篇文章中,我们了解了如何通过 netty 实现一个echo server。你应该还记得 ServerBootstrap
启动类,它负责 server 的启动管理,在启动前我们需要为其配置 EventLoopGroup
。EventLoopGroup
有配套的 ServerSocketChannel
,比如通常使用最多的是 NioEventLoopGroup
,它就需要和 NioServerSocketChannel
搭配起来工作。
1 | // NioServerSocketChannel和NioEventLoopGroup搭配干活 |
你可能会有疑问:EventLoopGroup
是什么?它的作用什么?本文就来探讨下这个问题。
要使用简单的方式讲清楚这个问题,确实有一定的难度。我想了好久,最后决定从 EventLoop
的用法讲起,逐步去深入分析。
因此,我们暂且搁置下 EventLoopGroup
,先来看一下 EventLoop
的常见用法。如果你看过 netty 的源码,你将会看到很多类似下面的代码:
1 | channel.eventLoop().execute(new Runnable() { |
这段代码看起来很简单:首先从 Channel
中获取到 EventLoop
,然后通过 execute
方法往 EventLoop
中提交一个 runnable
任务,直观上的感觉就像使用线程池一样。
这里面有两个问题:1.Channel
与 EventLoop
的关系是什么?2.runnable 是提交到线程池里了吗?它是如何通过 EventLoop
执行的呢?
先来看第一个问题:Channel
与 EventLoop
的关系是什么?从 AbstractChannel
的实现中可以看到,eventLoop
是它的一个属性,也就是说,Channel
上绑定了一个 EventLoop
对象
1 | private volatile EventLoop eventLoop; |
再来看第二个问题:任务是如何在 EventLoop
中执行的?
1 | private void execute(Runnable task, boolean immediate) { |
当我们往 EventLoop
中提交 runnable
任务的时候,它首先会提交到 SingleThreadEventExecutor
的一个任务队列中,SingleThreadEventExecutor
按照一定的规则,在合适的时候将任务取出来执行。
SingleThreadEventExecutor
是什么?它是所有 EventLoop
实现类的一个父类,用于通过单线程的方式执行所有提交过来的任务。
对上面的分析做一个总结:
channel
上绑定了一个EventLoop
实例,它以单线程的方式执行任务。外部程序调用execute
方法将任务提交到EventLoop
的队列中排队,所有的任务按提交的顺序执行。
EventLoop
只做了这些事吗?当然不是,不要忘了,netty
是一个网络框架,它还要处理网络数据的收发。我们以 NioEventLoop
为例接着分析 EventLoop
的职责。
每一个 EventLoop
都需要实现 SingleThreadEventExecutor
的 run
方法,它里面用于执行具体实现类的业务逻辑。例如 DefaultEventLoop
就只是取出任务并执行
1 | protected void run() { |
对于 NioEventLoop
来说,它的执行逻辑如下:
- 执行
select
操作 - 处理已就绪的 IO 事件
- 根据设置的 IO 比例、第 2 步的执行耗时这两个因素计算本次处理任务的时间,从队列中获取任务并执行
1 | for (;;) { |
讲到这里,你应该对 EventLoop
做的事有所了解了。回到开头的问题,EventLoopGroup
是做什么的?它和 EventLoop
是什么关系?
我们接着来看。前面说过,每个 channel
上会绑定一个 EventLoop
实例,那么它是怎么绑定上去的呢?
在通过 ServerBootStrap
启动 server
时,它会将初始化好的 channel
注册到 EventLoopGroup
上
1 | final ChannelFuture initAndRegister() { |
EventLoopGroup
所谓的注册 channel
,其实就是通过一定的策略选择一个 EventLoop
,然后把 channel
注册到 EventLoop
上,最终会执行到如下代码,完成 channel
和 EventLoop
关系的绑定
1 | AbstractChannel.this.eventLoop = eventLoop; |
最后做个总结:server
启动时,需要从用户配置的 EventLoopGroup
中选择一个 EventLoop
并且和 channel
绑定,一个 channel
只会绑定到一个 EventLoop
上。后续 EventLoop
负责处理该 channel
上的网络 IO 事件和 其他提交过来的任务,所有任务按提交的顺序执行。
- 2021-01-02
netty 中的 channel 在完成创建和初始化之后,需要注册到 EventLoopGroup 上,这本质上是交给 EventLoop 管理 channel 的各种事件。一个 EventLoopGroup 管理了多个 EventLoop,那么在注册 channel 时,EventLoopGroup 就需要选择一个 EventLoop,然后将其和 channel 关联起来。选择 EventLoop 是一个很高频的操作,该操作是否高效会直接影响 netty 的性能,本文就来聊下 EventLoop 的选择策略。
- 2021-02-05
netty 是一个基于异步事件驱动实现的网络编程框架,它的内部使用了大量的异步编程方法,这是它性能高效的一个原因,但同时也使得代码阅读起来更加困难,本文就尝试分析下它的启动过程
- 2021-01-26
ServerBootstrap 启动时需要初始化 ServerSocketChannel 并将其绑定到 EventLoop 上,用于处理该 channel 上产生的各种事件。那么 ServerSocketChannel 是如何完成创建和初始化的?又是如何绑定到 EventLoop 上的?
- 2021-02-26
对于一个 Netty client 来说,在配置好
Bootstrap
之后,通过调用其connect
方法来连接到远程服务端,如下所示1
2
3
4Bootstrap b = new Bootstrap();
b.group(group)
...
ChannelFuture f = b.connect(HOST, PORT).sync(); - 2022-01-07
兴趣是最好的老师,当你对一件事情感兴趣的时候,你就更容易长久地坚持下去,从而达到一个更高的高度,或许还能够取得意想不到的效果。接下来我会从实现一个 echo server 开始 netty 的探索旅程,如果你也对 netty 感兴趣,那么我们就开始吧。