netty 的 EventLoop 选择策略
netty 中的 channel 在完成创建和初始化之后,需要注册到 EventLoopGroup 上,这本质上是交给 EventLoop 管理 channel 的各种事件。一个 EventLoopGroup 管理了多个 EventLoop,那么在注册 channel 时,EventLoopGroup 就需要选择一个 EventLoop,然后将其和 channel 关联起来。选择 EventLoop 是一个很高频的操作,该操作是否高效会直接影响 netty 的性能,本文就来聊下 EventLoop 的选择策略。

image-20210102175908637
netty 使用 EventExecutorChooserFactory.EventExecutorChooser
选择要使用的 EventLoop
1 | private final EventExecutorChooserFactory.EventExecutorChooser chooser; |
EventExecutorChooser
是一个接口,它只有一个 next()
方法,用于获取一个 EventExecutor
(对于 EventLoopGroup
来说,就是获取一个 EventLoop
)
1 | interface EventExecutorChooser { |
EventExecutorChooser
共有两种实现,分别是 GenericEventExecutorChooser
和 PowerOfTwoEventExecutorChooser
,本质上这两种策略都是最简单的轮询策略,他们的区别在于具体的实现。
GenericEventExecutorChooser
GenericEventExecutorChooser
是一个通用的选择器,它的代码如下:
1 | private static final class GenericEventExecutorChooser implements EventExecutorChooser { |
它内部使用一个数组保存了所有可用的 executor,使用一个 AtomicLong 类型的元素作为 counter 计数器。每次取元素时根据 counter 和数组长度取余计算数组下标,取完数据后 counter 加一。其中 getAndIncrement 保证了操作的原子性,避免了并发问题。
1 | index = id % length # 计算下标 |
PowerOfTwoEventExecutorChooser
PowerOfTwoEventExecutorChooser
是另外一种选择器,它也是采用的轮询的策略。对于轮询策略来说,当数组长度是 2 的幂次方时,我们可以通过位运算计算数组下标。
1 | index = id & (length - 1) |
和取余操作相比,位运算是一种更高效的计算方式,这对于性能的提升有一定的效果。
1 | private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser { |
PowerOfTwoEventExecutorChooser
的缺点是对数组的长度有要求,这样对于不符合要求的情况就只能使用 GenericEventExecutorChooser
,这也是 EventExecutorChooserFactory
要做的工作
EventExecutorChooserFactory
1 | public interface EventExecutorChooserFactory { |
EventExecutorChooserFactory
用于生成 EventExecutorChooser
,它考虑的因素是 executors 数组的长度,当长度是 2 的幂次方时使用 PowerOfTwoEventExecutorChooser
,否则使用 GenericEventExecutorChooser
1 | // DefaultEventExecutorChooserFactory.newChooser |
判断是否为 2 的幂次方
2 的幂次方一定大于 0,当确认数字大于 0 时,可以用以下方法判断
方法一
1 | return (val & -val) == val; |
示例
1 | 16 = 00000000000000000000000000010000 |
方法二
1 | return (val & val - 1 == 0) |
示例
1 | 16 = 00000000000000000000000000010000 |
- 2022-01-30
在上一篇文章中,我们了解了如何通过 netty 实现一个 echo server。你应该还记得
ServerBootstrap
启动类,它负责 server 的启动管理,在启动前我们需要为其配置EventLoopGroup
。EventLoopGroup
有配套的ServerSocketChannel
,比如通常使用最多的是NioEventLoopGroup
,它就需要和NioServerSocketChannel
搭配起来工作。 - 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(); - 2021-02-03
netty 使用
DefaultPromise
完成异步操作,它对 jdk 的 Future 进行了扩展,提供了更丰富的功能。
预览: