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 |