掘金 后端 ( ) • 2024-04-12 17:15

前言

Netty的线程模型采用了Reactor模式,这是一种高性能的IO设计模式,它基于NIO多路复用。Netty支持多种线程模型,包括单线程模型、多线程模型和混合线程模型。


NIO的三种reactor模式

NIO(Java Non-blocking IO,即Java非阻塞I/O)的三种Reactor模式主要包括:单Reactor单线程模式、单Reactor多线程模式和多Reactor多线程模式。

  1. 单Reactor单线程模式:这是最简单的模式,其中只有一个Reactor线程负责监听和处理所有的客户端连接和IO事件。当Reactor线程收到IO事件后,会自行完成事件的读取、业务逻辑处理和响应发送。这种模式虽然简单,但存在性能瓶颈,因为所有的事件都在一个线程中处理,无法充分利用多核CPU的性能。 在这里插入图片描述
  1. 单Reactor多线程模式:为了提升性能,这种模式引入了多线程来处理业务逻辑。Reactor线程仍然负责监听和接收客户端的连接和IO事件,但当事件到来时,它会将事件分发给后续的Worker线程池进行处理。这样,Reactor线程可以专注于事件的监听和分发,而Worker线程池可以并行处理多个事件,从而提高了系统的吞吐量。 在这里插入图片描述
  1. 多Reactor多线程模式:这种模式是对单Reactor多线程模式的进一步优化。它引入了多个Reactor线程,每个Reactor线程负责监听和处理一部分客户端的连接和IO事件。当某个Reactor线程收到事件后,它会将事件分发给对应的Worker线程池进行处理。这种模式可以更好地利用多核CPU的性能,进一步提高系统的吞吐量和处理能力。 在这里插入图片描述

总的来说,NIO的三种Reactor模式各有优缺点,适用于不同的场景和需求。在选择时,需要根据系统的性能需求、硬件资源等因素进行综合考虑。

Netty对3种Reactor模式的支持

Netty中的3种Reactor模式与对应的Netty使用示例如下表所示

Reactor模式 Netty使用示例 Reactor 单线程模式 EventLoopGroup eventGroup = new NioEventLoopGroup(1); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(eventGroup); 非主从 Reactor 多线程模式 EventLoopGroup eventGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(eventGroup); 主从 Reactor 多线程模式 EventLoopGroup bossGroup= new NioEventLoopGroup(); EventLoopGroup workGroup= new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup);

Netty内部如何实现Reactor线程模型

Netty内部实现Reactor线程模型主要依赖于其事件驱动和异步非阻塞的特性。Netty通过EventLoopEventLoopGroup等核心组件来实现高效的线程管理和事件分发,从而支持Reactor模式。

以下是Netty内部实现Reactor线程模型的关键步骤和组件:

  1. EventLoopGroup的创建

    • EventLoopGroup是Netty中用于处理I/O操作的多线程事件循环。它实际上是一个线程池,每个线程都包含一个EventLoop。对于服务端,通常会有两个EventLoopGroup:一个用于接收客户端连接(bossGroup),另一个用于处理已接收连接上的I/O操作(workerGroup)。
  2. EventLoop的创建与注册

    • 每个EventLoop都绑定到一个Selector上,用于监听网络事件。当一个新的连接建立时,该连接会被注册到某个EventLoopSelector上。
    • EventLoop内部维护了一个任务队列,用于存放待处理的I/O任务和定时任务。它还负责处理这些任务,并在必要时将任务分发给其他线程执行。
protected void run() {
    //Select计数
    int selectCnt = 0;
    for (;;) {
        try {
            int strategy;
            try {
                //计算Select的策略 <1>
                strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                switch (strategy) {
                case SelectStrategy.CONTINUE:
                    continue;
​
                case SelectStrategy.BUSY_WAIT:
                    // fall-through to SELECT since the busy-wait is not supported with NIO
​
                case SelectStrategy.SELECT:
                    //当没有普通任务时,返回定时任务最近一次要执行的时间,如果有没有定时任务则返回-1
                    long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                    if (curDeadlineNanos == -1L) {
                        //如果没有定时任务,则将最近执行时间设置为Integer的最大值
                        curDeadlineNanos = NONE; // nothing on the calendar
                    }
                    //设置下一次的唤醒时间
                    nextWakeupNanos.set(curDeadlineNanos);
                    try {
                        if (!hasTasks()) {
                            //select看是否有新增的感兴趣的事件
                            strategy = select(curDeadlineNanos);
                        }
                    } finally {
                        // This update is just to help block unnecessary selector wakeups
                        // so use of lazySet is ok (no race condition)
                        //延迟设置线程的唤醒时间阻塞不必要的Select唤醒
                        nextWakeupNanos.lazySet(AWAKE);
                    }
                    // fall through
                default:
                }
            } catch (IOException e) {
                // If we receive an IOException here its because the Selector is messed up. Let's rebuild
                // the selector and retry. https://github.com/netty/netty/issues/8566
                //重建Selector
                rebuildSelector0();
                //重置计数
                selectCnt = 0;
                handleLoopException(e);
                continue;
            }
​
            selectCnt++;
            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            boolean ranTasks;
            if (ioRatio == 100) {
                try {
                    if (strategy > 0) {
                        //如果有新增的感兴趣的事件,则处理
                        processSelectedKeys();
                    }
                } finally {
                    // Ensure we always run tasks.
                    //所有的时间都用来处理IO事件,包括普通任务和定时任务,不限制时间
                    ranTasks = runAllTasks();
                }
            } else if (strategy > 0) {
                //记录当前时间
                final long ioStartTime = System.nanoTime();
                try {
                    //处理Channel的就绪事件
                    processSelectedKeys();
                } finally {
                    // Ensure we always run tasks.
                    //计算用来处理IO事件的时间,包括普通任务和定时任务,限制时间
                    //以处理Channel的就绪事件所需时间为基准计算执行所有任务需要的时间
                    final long ioTime = System.nanoTime() - ioStartTime;
                    ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            } else {
                ranTasks = runAllTasks(0); // This will run the minimum number of tasks
            }
            //如果有任务执行过了或者有任务待执行,则重置select计数
            if (ranTasks || strategy > 0) {
                if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                    logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                            selectCnt - 1, selector);
                }
                //有新增的事件,或者任务执行过,则将空轮询次数置0
                selectCnt = 0;
            } else if (unexpectedSelectorWakeup(selectCnt)) { // Unexpected wakeup (unusual case)
                //针对意外唤醒,重置计数
                selectCnt = 0;
            }
        } catch (CancelledKeyException e) {
            // Harmless exception - log anyway
            if (logger.isDebugEnabled()) {
                logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                        selector, e);
            }
        } catch (Error e) {
            throw e;
        } catch (Throwable t) {
            handleLoopException(t);
        } finally {
            // Always handle shutdown even if the loop processing threw an exception.
            try {
                if (isShuttingDown()) {
                    //如果EventLoop状态是正在关闭、已关闭、已终止,则执行关闭逻辑,关闭Channel和Selector的绑定,关闭Channel
                    closeAll();
                    //确认是否可以关闭了
                    if (confirmShutdown()) {
                        //退出NioEventLoop线程循环
                        return;
                    }
                }
            } catch (Error e) {
                throw e;
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }
}
​
  1. 事件监听与分发

    • Selector监听到网络事件(如连接建立、数据可读等)时,它会通知对应的EventLoop
    • EventLoop根据事件的类型,从已注册的Channel中查找对应的ChannelPipeline,并将事件分发给ChannelPipeline中的ChannelHandler进行处理。
  2. ChannelHandler的链式处理

    • ChannelHandler是Netty中处理网络事件的核心组件。它们以链式的方式组织在ChannelPipeline中,每个ChannelHandler都可以对事件进行拦截和处理。
    • 当事件到达某个ChannelHandler时,该ChannelHandler可以决定是继续将事件传递给下一个ChannelHandler,还是直接处理并结束事件的传递。
  3. 异步非阻塞操作

    • Netty的所有I/O操作都是异步非阻塞的。这意味着当发起一个I/O操作时,Netty不会等待操作完成就立即返回。相反,它会将操作的结果或异常通过回调的方式通知给调用者。
    • 这种异步非阻塞的特性使得Netty能够高效地处理大量并发连接和事件,提高了系统的吞吐量和响应速度。
  4. 线程模型优化

    • Netty还支持多种线程模型优化技术,如多Reactor模式、零拷贝等,以进一步提高性能。
    • 在多Reactor模式中,Netty可以创建多个EventLoopGroup来处理不同类型的任务或不同优先级的任务,从而更好地利用系统资源。

通过上述组件和机制,Netty内部实现了高效的Reactor线程模型,使得开发者能够轻松地构建高性能、高可靠性的网络应用。

在事件分发过程中,Netty如何避免竞争条件

Netty在事件分发过程中通过一系列机制来避免竞争条件,确保线程安全和高效的事件处理。以下是一些关键的避免竞争条件的策略:

  1. 单线程模型中的事件处理: 在单线程Reactor模型中,虽然存在竞争条件的潜在风险,但由于所有事件都在一个线程中处理,因此实际上避免了多线程间的竞争。然而,这种模型在处理高并发时可能会成为瓶颈,因为任何事件的处理都会阻塞其他事件的处理。
  2. 多线程模型中的线程隔离: 对于多线程模型(如主从多线程Reactor模型),Netty通过将不同类型的任务分配给不同的线程或线程组来减少竞争。例如,接受新连接的任务和处理已建立连接上I/O任务可以被分配给不同的线程池。这样,处理不同任务的线程之间就不会发生竞争。
  3. 无锁化设计: Netty在内部实现中尽可能使用无锁数据结构和算法,以减少锁竞争的开销。无锁数据结构通过原子操作和内存可见性保证线程安全,避免了传统锁机制带来的性能开销和潜在的死锁问题。
  4. 细粒度的锁策略: 当需要使用锁时,Netty会采用细粒度的锁策略,只对需要同步的共享资源加锁,以减少锁的粒度,从而降低锁竞争的可能性。这通常涉及到对关键部分的精确分析和优化。
  5. 事件队列的线程安全: 事件队列是Netty中事件分发的重要组件,它必须是线程安全的。Netty通过内部同步机制确保多个线程对事件队列的并发访问不会导致数据不一致或其他竞争条件。
  6. 事件分发的顺序性: Netty确保事件按照它们发生的顺序进行分发,这有助于避免由于事件乱序导致的竞争条件。例如,对于同一个Channel的事件,Netty会保证它们按照发生的顺序被处理。
  7. 避免共享状态: 尽量减少跨线程共享的状态可以大大降低竞争条件的风险。Netty鼓励开发者使用局部变量或线程局部变量来存储状态,而不是依赖全局变量或共享对象。