Netty源码剖析之二:NioEventLoop

本文详细解析了NioEventLoop在Netty中的核心作用,包括其构造过程、优化的Selector实现、并发队列MPSC的选择以及工作流程。重点介绍了如何通过SelectedSelectionKeySet提升性能和空轮询问题的解决策略。

1. NioEventLoop简介

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

2. NioEventLoop类图

在这里插入图片描述

3. 构造函数

NioEventLoop类的构造函数,大概流程为

  1. 初始化MPSC队列作为工作线程的任务队列。
  2. 通过Selector.open()创建多路复用选择器Selector
  3. 默认优化选择器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构造函数,这里的executorNioEventLoopGroup创建的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 NIOSelector.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()

主要处理三件事:

  1. 轮询IO事件。
  2. 处理IO事件。
  3. 处理非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事件

大概流程:

  1. 计算本次for的超时截止时间deadline,如果没有定时任务则为1秒。
  2. 如果超时或者任务队列中有任务,则进行非阻塞selectNow()后跳出循环。
  3. 进行阻塞的select(time)
  4. 如果满足有IO事件、被唤醒、任务队列有任务、定时任务队列有任务、线程中断等条件,则跳出循环。
  5. 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事件

大概流程:

  1. 优化过的SelectedKeys使用数组遍历,否则使用迭代器遍历。
  2. 通知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事件
  1. 根据IO事件与非IO事件处理时间的比例ioRatio、IO事件的处理时间计算出非IO事件的处理时间,如果ioRatio=100则处理非IO事件没有时间限制,否则限制处理非IO事件的时间。默认ioRatio=50,即IO事件与非IO事件处理时间的各占50%。
  2. 将已经到执行时间的定时任务添加到任务队列中。
  3. 遍历任务队列执行任务。
  4. 每处理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;
}
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值