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