在上一节NioEventLoop创建中, 我们提到了选择器EventExecurotChooser的创建. 这一节我们详细介绍下它.
我们从代码说起
io.netty.util.concurrent.DefaultEventExecutorChooserFactory#newChooser
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
从代码中我们看到, 其中executors就是我们创建的NioEventLoop数组. 根据数组长度是否是2^n创建不同的选择器.为啥要这样做呢?其实还是为了性能考虑,凡是可以提高性能的地方, Netty可是从不缺席.
选择器就是为了当有新连接进入时, 通过这个选择器选择一个NioEventLoop进行'服务'. 虽然创建了不同的选择器, 但是它们选择的思路的一样的, 就是从数组中循环选择.
比如NioEventLoop数组长度是8, 加入客户端连接到服务器之后, 服务器通过选择器EventExecutorChooser选择第一个NioEventLoop为其服务, 接着第二个客户端连接到服务器, 选择器EventExecutorChooser选择第二个NioEventLoop为其服务, 以此类推, 假如第八个客户端连接到服务器, 为其服务的是第八个NioEventLoop. 那么第九个客户端连接到服务器之后, 则又由第一个NioEventLoop为其服务了. 毕竟NioEventLoop封装的Selector是多路复用也就体现在此处.
只是选择器的两个实现类采取重复循环选择NioEventLoop的方式不同而已. 那么它们的不同之处在哪里呢? 我们通过代码说明
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
其实简单说, 就是一个通过按位与(&)操作符, 另一个通过取模(%)运算符进行选取. 因此为了效率, 大部分情况下, 我们在设置NioEventLoop数组大小时, 使用2^n可以提高性能. 但是我查看了Dubbo和RocketMQ, 它们也有使用3的情况. 这个没有统一规范.