netty 的 EventLoop 选择策略

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

netty 使用 EventExecutorChooserFactory.EventExecutorChooser 选择要使用的 EventLoop

1
2
3
4
5
6
7
8
9
10
11
12
13
private final EventExecutorChooserFactory.EventExecutorChooser chooser;

// MultithreadEventExecutorGroup
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
// 省略其他代码...
children = new EventExecutor[nThreads];
chooser = chooserFactory.newChooser(children);
}

public EventExecutor next() {
return chooser.next();
}

EventExecutorChooser 是一个接口,它只有一个 next() 方法,用于获取一个 EventExecutor(对于 EventLoopGroup 来说,就是获取一个 EventLoop

1
2
3
4
5
6
interface EventExecutorChooser {
/**
* Returns the new {@link EventExecutor} to use.
*/
EventExecutor next();
}

EventExecutorChooser 共有两种实现,分别是 GenericEventExecutorChooserPowerOfTwoEventExecutorChooser,本质上这两种策略都是最简单的轮询策略,他们的区别在于具体的实现。

GenericEventExecutorChooser

GenericEventExecutorChooser 是一个通用的选择器,它的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
// Use a 'long' counter to avoid non-round-robin behaviour at the 32-bit overflow boundary.
// The 64-bit long solves this by placing the overflow so far into the future, that no system
// will encounter this in practice.
private final AtomicLong idx = new AtomicLong();
private final EventExecutor[] executors;

GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}

@Override
public EventExecutor next() {
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
}
}

它内部使用一个数组保存了所有可用的 executor,使用一个 AtomicLong 类型的元素作为 counter 计数器。每次取元素时根据 counter 和数组长度取余计算数组下标,取完数据后 counter 加一。其中 getAndIncrement 保证了操作的原子性,避免了并发问题。

1
2
3
index = id % length       # 计算下标
id = id + 1 # 下标加一
return executors[index] # 返回数组元素

PowerOfTwoEventExecutorChooser

PowerOfTwoEventExecutorChooser 是另外一种选择器,它也是采用的轮询的策略。对于轮询策略来说,当数组长度是 2 的幂次方时,我们可以通过位运算计算数组下标。

1
index = id & (length - 1)

和取余操作相比,位运算是一种更高效的计算方式,这对于性能的提升有一定的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;

PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}

@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}

PowerOfTwoEventExecutorChooser 的缺点是对数组的长度有要求,这样对于不符合要求的情况就只能使用 GenericEventExecutorChooser ,这也是 EventExecutorChooserFactory 要做的工作

EventExecutorChooserFactory

1
2
3
4
5
6
7
public interface EventExecutorChooserFactory {

/**
* Returns a new {@link EventExecutorChooser}.
*/
EventExecutorChooser newChooser(EventExecutor[] executors);
}

EventExecutorChooserFactory 用于生成 EventExecutorChooser ,它考虑的因素是 executors 数组的长度,当长度是 2 的幂次方时使用 PowerOfTwoEventExecutorChooser ,否则使用 GenericEventExecutorChooser

1
2
3
4
5
6
7
8
9
10
11
12
// DefaultEventExecutorChooserFactory.newChooser
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}

private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}

判断是否为 2 的幂次方

2 的幂次方一定大于 0,当确认数字大于 0 时,可以用以下方法判断

方法一

1
return (val & -val) == val;

示例

1
2
3
16       = 00000000000000000000000000010000
-16 = 11111111111111111111111111110000
16 & -16 = 00000000000000000000000000010000

方法二

1
return (val & val - 1 == 0)

示例

1
2
3
16       = 00000000000000000000000000010000
15 = 00000000000000000000000000001111
16 & 15 = 00000000000000000000000000000000