1. NioEventLoop简介
Netty的线程模型是基于Reactor线程模型的,而NioEventLoop类则是Reactor线程模型的核心,NioEventLoop类的run()方法负责监听IO事件并分发handler进行业务逻辑的处理。
2. NioEventLoop类图

3. 构造函数
NioEventLoop类的构造函数,大概流程为
- 初始化
MPSC队列作为工作线程的任务队列。 - 通过
Selector.open()创建多路复用选择器Selector。 - 默认优化选择器
Selector,即SelectedSelectionKeySetSelector。
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory queueFactory) {
super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
}
if (strategy == null) {
throw new NullPointerException("selectStrategy");
}
provider = selectorProvider; // 默认是SelectorProvider.provider()
final SelectorTuple selectorTuple = openSelector();
selector = selectorTuple.selector; // 多路复用选择器,包装多的selector
unwrappedSelector = selectorTuple.unwrappedSelector; // NIO中原始的selector
selectStrategy = strategy;
}
// 优化多路复用选择器Selector
private SelectorTuple openSelector() {
final Selector unwrappedSelector;
try {
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
}
// 是否优化
if (DISABLE_KEY_SET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
}
// 加载类Class
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
// 加载不了Class则不优化,返回NIO的Selector
if (!(maybeSelectorImplClass instanceof Class) ||
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable) maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return new SelectorTuple(unwrappedSelector);
}
// 通过反射将sun.nio.ch.SelectorImpl类的成员变量publicSelectedKeys由HashSet类
// 替换为SelectedSelectionKeySet类
final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
if (PlatformDependent.javaVersion() >= 9 && PlatformDependent.hasUnsafe()) {
long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
long publicSelectedKeysFieldOffset =
PlatformDependent.objectFieldOffset(publicSelectedKeysField);
if (selectedKeysFieldOffset != -1 && publicSelectedKeysFieldOffset != -1) {
PlatformDependent.putObject(
unwrappedSelector, selectedKeysFieldOffset, selectedKeySet);
PlatformDependent.putObject(
unwrappedSelector, publicSelectedKeysFieldOffset, selectedKeySet);
return null;
}
}
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
if (cause != null) {
return cause;
}
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
});
if (maybeException instanceof Exception) {
selectedKeys = null;
Exception e = (Exception) maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return new SelectorTuple(unwrappedSelector);
}
// 优化成功
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector,
new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
继续查看父类SingleThreadEventExecutor构造函数,这里的executor是NioEventLoopGroup创建的new ThreadPerTaskExecutor(threadFactory)内部的execute()方法作用是用来新建一个线程并启动threadFactory.newThread(command).start();。
每个工作线程维护了一个任务队列,使用MSCP队列替换LinkedBlockingQueue队列,在高并发下效率高,但基于CAS的无锁编程会一定程度上占用CPU资源。
protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
boolean addTaskWakesUp, Queue<Runnable> taskQueue,
RejectedExecutionHandler rejectedHandler) {
super(parent);
this.addTaskWakesUp = addTaskWakesUp;
this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS;
this.executor = ThreadExecutorMap.apply(executor, this); // 用当前线程池工厂创建线程
this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue"); // Mpsc队列,适用于当消费者多生产者场景
rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
}
3.1 selectedKeys性能优化
Netty将已就绪的IO事件集合由HashSet类替换为SelectedSelectionKeySet类,HashSet类内部是HashMap实现,而SelectedSelectionKeySet内部是默认容量为1024的数组实现,结构简单且数组的遍历效率比链表高,提高了性能。
Netty的工作线程会通过JAVA NIO的Selector.selectedKeys()方法监听获取到已就绪的IO事件集合,查看Selector类的子类sun.nio.ch.SelectorImpl可知,selectedKeys()方法返回的是的sun.nio.ch.SelectorImpl类成员变量publicSelectedKeys,而变量publicSelectedKeys的值是一个HashSet类,Netty的工作线程通过反射机制将变量publicSelectedKeys的值替换为SelectedSelectionKeySet。
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
SelectionKey[] keys;
int size;
SelectedSelectionKeySet() {
keys = new SelectionKey[1024];
}
... // 省略其他
}
3.2 并发队列的优化
Netty的工作线程使用MPSC队列作为线程的并发任务队列,MPSC队列是JCTools提供的并发工具类,JCTools提供了四种并发队列SPSC、MPSC、SPMC、MPMC,通过底层无锁的CAS操作方式实现了生产者消费者队列的线程安全模型,在高并发下效率比BlockingQueue高,性能高,同时基于CAS的无锁编程也会一定程度上占用CPU资源。
4. 工作线程的入口SingleThreadEventExecutor.execute()
Netty提交任务到NioEventLoop线程时,如果线程未启动,则会提交任务后立刻启动线程。
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
addTask(task); // 添加任务到任务队列中
if (!inEventLoop) { // 如果线程未启动,启动线程
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
}
if (reject) {
reject();
}
}
}
// 唤醒阻塞在Selector.select()方法上的线程
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
// CAS启动线程
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
// 每次启动线程工厂类启动线程
private void doStartThread() {
assert thread == null;
// executor内部维护一个线程工厂类,用于创建线程并启动。
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// 执行的是NioEventLoop.run()方法。
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = state;
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
if (success && gracefulShutdownStartTime == 0) {
if (logger.isErrorEnabled()) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
"be called before run() implementation terminates.");
}
}
try {
// Run all remaining tasks and shutdown hooks.
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
cleanup();
} finally {
FastThreadLocal.removeAll();
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.countDown();
if (logger.isWarnEnabled() && !taskQueue.isEmpty()) {
logger.warn("An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
terminationFuture.setSuccess(null);
}
}
}
}
});
5. 工作线程的入口NioEventLoop.run()
主要处理三件事:
- 轮询IO事件。
- 处理IO事件。
- 处理非IO事件。
@Override
protected void run() {
for (;;) {
try {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
// 轮询IO事件
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
} catch (IOException e) {
rebuildSelector0();
handleLoopException(e);
continue;
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
5.1 轮询IO事件
大概流程:
- 计算本次for的超时截止时间deadline,如果没有定时任务则为1秒。
- 如果超时或者任务队列中有任务,则进行非阻塞
selectNow()后跳出循环。 - 进行阻塞的
select(time)。 - 如果满足有IO事件、被唤醒、任务队列有任务、定时任务队列有任务、线程中断等条件,则跳出循环。
- Netty重建Selector修复JDK的Selector空轮询BUG。
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
long normalizedDeadlineNanos = selectDeadLineNanos - initialNanoTime();
if (nextWakeupTime != normalizedDeadlineNanos) {
nextWakeupTime = normalizedDeadlineNanos;
}
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely because " +
"Thread.currentThread().interrupt() was called. Use " +
"NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
}
selectCnt = 1;
break;
}
long time = System.nanoTime();
// 空轮询判断
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
// 修复空轮询BUG
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
selector = selectRebuildSelector(selectCnt);
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
if (logger.isDebugEnabled()) {
logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
selectCnt - 1, selector);
}
}
} catch (CancelledKeyException e) {
if (logger.isDebugEnabled()) {
logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
selector, e);
}
}
}
JDK的Selector空轮询BUG及Netty的解决方案
JDK的Selector空轮询BUG,指的是Selector的select方法会一直阻塞,直到IO事件达到或超时,但是在某些场景下select方法会直接返回,即使没有超时并且也没有IO事件到达。
Netty通过select方法提前返回且没有IO事件判断是否发生了空轮询BUG,如果空轮询的次数达到了指定的阈值SELECTOR_AUTO_REBUILD_THRESHOLD默认为512次,则开启新的Selector替换原来的Selector。
5.2 处理IO事件
大概流程:
- 优化过的SelectedKeys使用数组遍历,否则使用迭代器遍历。
- 通知
unsafe处理IO事件,服务端对应的是NioMessageUnsafe,客户端对应的是NioSocketChannelUnsafe。
private void processSelectedKeys() {
// 优化过的SelectedKeys,使用数组遍历
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
// 使用迭代器iterator遍历
processSelectedKeysPlain(selector.selectedKeys());
}
}
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
selectedKeys.keys[i] = null;
// 附加的对象
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
5.3 处理非IO事件
- 根据IO事件与非IO事件处理时间的比例
ioRatio、IO事件的处理时间计算出非IO事件的处理时间,如果ioRatio=100则处理非IO事件没有时间限制,否则限制处理非IO事件的时间。默认ioRatio=50,即IO事件与非IO事件处理时间的各占50%。 - 将已经到执行时间的定时任务添加到任务队列中。
- 遍历任务队列执行任务。
- 每处理64个任务就判断处理非IO事件的时间是否超时。
protected void run() {
for (;;) {
...
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
...
}
}
protected boolean runAllTasks(long timeoutNanos) {
// 将已经到执行时间的定时任务添加到任务队列中
fetchFromScheduledTaskQueue();
Runnable task = pollTask();
if (task == null) {
afterRunningAllTasks();
return false;
}
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
for (;;) {
safeExecute(task);
runTasks ++;
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
if (lastExecutionTime >= deadline) {
break;
}
}
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
afterRunningAllTasks();
this.lastExecutionTime = lastExecutionTime;
return true;
}
本文详细解析了NioEventLoop在Netty中的核心作用,包括其构造过程、优化的Selector实现、并发队列MPSC的选择以及工作流程。重点介绍了如何通过SelectedSelectionKeySet提升性能和空轮询问题的解决策略。
9334





