从源码的角度带你一步步剖析NioEventLoop(反应器)的三步曲

文章详细阐述了Netty中的NioEventLoop如何处理和注册NioSocketChannel,包括注册过程、轮询机制以及事件分发。NioEventLoop通过一个线程执行I/O任务和非I/O任务,利用Selector监听并处理通道的就绪事件。整个流程涉及通道注册、轮询选择器和事件分发到处理器handler进行实际处理,体现了Netty的高效率和无锁化设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Netty 中的反应器组件有多个实现类,这些实现类与其通道类型相互匹配. 对应于NioSocketChannel通道,Netty的反应器类为NioEventLoop(Nio事件轮询).

NioEventLoop 与Nio 原生selector的关系

NioEventLoop类有两个重要的成员属性:一个是Thread线程类的成员,一个是Java NIO选择器的成员属性.
在这里插入图片描述

一个NioEventLoop拥有一个线程Thread,主要用于轮询Nio Selector并处理IO事件,处理其他的非IO任务。Reactor 线程一般是在channel 在selector上注册之前启动的.

一个EventLoop反应器和NettyChannel通道是一对多的关系:一个反应器可以注册成千上万的通道.如图所示:

NioEventLoop 与Nio 原生channel的关系

以NioSocketChannel 类作为channel 通道的代表进行说明,
在这里插入图片描述

Reactor三步曲

第一步: 注册

将channel 通道的就绪事件,注册到选择器Selector.

一个Reactor 对应一个选择器Selector,一个Reactor拥有一个Selector成员属性.

以Netty的Client端为例

入口代码为:

//通道连接
 channelFuture = bootstrap.connect("127.0.0.1",8080).sync();

启动类AbstractBootstrap类的绑定端口方法

public ChannelFuture connect(String inetHost, int inetPort) {
    return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}


public ChannelFuture connect(SocketAddress remoteAddress) {
    ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
    this.validate();
    return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
}

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    ChannelFuture regFuture = this.initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.isDone()) {
        return !regFuture.isSuccess() ? regFuture : this.doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    Bootstrap.this.doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                }

            }
        });
        return promise;
    }
}


channel注册选择器Selector 入口方法initAndRegister():

final ChannelFuture initAndRegister() {
    Channel channel = null;

    try {
        channel = this.channelFactory.newChannel();
        this.init(channel);
    } catch (Throwable var3) {
        if (channel != null) {
            channel.unsafe().closeForcibly();
            return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
    }

    ChannelFuture regFuture = this.config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }

    return regFuture;
}


MultithreadEventLoopGroup.register()方法:

 @Override
public ChannelFuture register(ChannelPromise promise) {
    return next().register(promise);
}

@Deprecated
@Override
public ChannelFuture register(Channel channel, ChannelPromise promise) {
    return next().register(channel, promise);
}

SingleThreadEventLoop.register()方法

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

AbstractChannel.AbstractUnsafe.register()

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ObjectUtil.checkNotNull(eventLoop, "eventLoop");
    if (isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    if (!isCompatible(eventLoop)) {
        promise.setFailure(
                new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    AbstractChannel.this.eventLoop = eventLoop;

    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            //如果线程没有启动,启动线程
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    // 通道注册到选择器,向流水线发送通道激活事件
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            safeSetFailure(promise, t);
        }
    }
}

AbstractChannel.AbstractUnsafe.register0()

private void register0(ChannelPromise promise) {
    try {
       
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        boolean firstRegistration = neverRegistered;
        doRegister();
        neverRegistered = false;
        registered = true;

        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

AbstractNioChannel.doRegister()

 @Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
        //重点: 首先拿到原生的选择器eventLoop,在拿到原生的通道channel, 最后完成通道的注册工作,
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
              
                eventLoop().selectNow();
                selected = true;
            } else {
                SelectionKey is still cached
                throw e;
            }
        }
    }
}

AbstractNioChannel 通道类有一个本地java通道成员ch,在AbstractNioChannel 的构造函数中,被初始化.

private final SelectableChannel ch;
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            logger.warn(
                        "Failed to close a partially initialized socket.", e2);
        }

        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}


Netty 的channel 通过javaChannel()方法取得了java本地Channel. 它返回的是一个java NIO SocketChannel. 代码如下:

protected SelectableChannel javaChannel() {
    return ch;
}

最后,这个SocketChannel 注册到与eventLoop 关联的selector上了.

  selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

channel.register 的第三个参数是设置selectionKey 的附加对象的,和调用selectionKey.attach(object)的效果一样.而doRegister()调用javaChannel().register()方法传递的第三个参数是this,它是一个MioSocketChannel 的实例, 意思是SocketChannel对象自身,以附加字段的方式添加到selectionKey中,供事件就绪后使用.

总结,注册时序图如下:

在这里插入图片描述

第二步轮询

轮询的代码,是Reactor 重要的一个组成部分,轮询选择器是否有就绪事件.

NioEventLoop 的父类SingleThreadEventExecutor中,有一个Thread thread 属性,存储了一个本地java线程,

 private volatile Thread thread;

此java 线程是在SocketChannel 注册到eventLoop的selector之前启动的,在Execute的方法中,去调用startThread()启动线程,
.AbstractChannel.AbstractUnsafe.register()中有一个方法调用 eventLoop.execute(new Runnable() ),此调用就是启动EventLoop线程的入口,execute()方法的实现在SingleThreadEventExecutor类中,具体源码如下:

private void execute(Runnable task, boolean immediate) {
    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();
            }
        }
    }

    if (!addTaskWakesUp && immediate) {
        wakeup(inEventLoop);
    }
}

将通道注册的任务,添加到双向队列taskqueue中,

protected void addTask(Runnable task) {
    ObjectUtil.checkNotNull(task, "task");
    if (!offerTask(task)) {
        reject(task);
    }
}
final boolean offerTask(Runnable task) {
    if (isShutdown()) {
        reject();
    }
    return taskQueue.offer(task);
}

如果reactor的线程未启动,则调用startThread()方法启动线程,启动线程的源码如下:

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);
                }
            }
        }
    }
}

STATE_UPDATER是SingleThreadEventExecutor内部维护的一个属性,它的作用是标识当前thread的状态,在初始的时候,state == ST_NOT_STARTED,因此第一次调用startThread() 方法时,就会计入到if语句中,进而调用到doStartThread()方法.doStartThread() 方法代码如下:

private void doStartThread() {
    assert thread == null;
    //通过线程池执行,通过线程工厂,每提交一个任务就会创建一个线程
    executor.execute(new Runnable() {
        @Override
        public void run() {
            //绑定第一条线程
            thread = Thread.currentThread();
            if (interrupted) {
                thread.interrupt();
            }

            boolean success = false;
            updateLastExecutionTime();
            try {
                //启动事件轮询和任务执行
                SingleThreadEventExecutor.this.run();
                success = true;
            } catch (Throwable t) {
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
                ...
            }
        }
    });
}

接下来我们看下上述代码中的线程的创建来自于ExecuteGroup 的线程工厂,在MultithreadEventExecutorGroup类的构造函数中

 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        checkPositive(nThreads, "nThreads");

        if (executor == null) {
            //线程工厂,执行一个线程,创建一个线程,特点是每执行一个任务创建一个新的线程
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

        //线程数组,创建的线程数量是由装配引导bossGroup和 workerGroup时决定的
        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                //在eventLoop的startthread中创建线程
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
              ....
            }
        }


到目前为止,NioEventLoop.run()就可以执行了,
总结: NioEventLoop中维护了一个线程,线程启动时会调用NioEventLoop 的run方法,执行I/O任务和非I/O任务; I/O任务即selectionKey中ready的事件,如accept,connect,read,write等,由processSelectionKey方法触发. 非I/O 任务,添加到taskQueue的任务 ,如register0,bind0等任务,由runAllTasks方法触发.

两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等.

NioEventLoop 事件轮询包含两种策略:

  • 非阻塞的select策略
  • 阻塞的select策略

事件的轮询在NioEventLoop.run()方法中,其源码如下:

@Override
    protected void run() {
        int selectCnt = 0;
        for (;;) {
            try {
                int strategy;
                try {
                    // select 策略选择
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                        // 1.1 非阻塞的select策略,即重试IO循环
                    case SelectStrategy.CONTINUE:
                        continue;
                        //1.2非阻塞的新事件IO循环
                    case SelectStrategy.BUSY_WAIT:

                        //1.3 阻塞的select策略
                    case SelectStrategy.SELECT:
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            curDeadlineNanos = NONE; // nothing on the calendar
                        }
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            // This update is just to help block unnecessary selector wakeups
                            // so use of lazySet is ok (no race condition)
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // 1.4 不需要select,目前已经有可以执行的任务了
                    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
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }

                selectCnt++;
                //2.执行网络IO事件和任务调度
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) {
                    try {
                        if (strategy > 0) {
                            //2.1 处理网络IO事件,分发入口
                            processSelectedKeys();
                        }
                    } finally {
                        // Ensure we always run tasks.
                        //2.2 处理系统Task和自定义Task
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    //根据ioRatio 计算非IO最多执行的时间
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        final long ioTime = System.nanoTime() - ioStartTime;
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    //处理队列中的task任务
                    ranTasks = runAllTasks(0); // This will run the minimum number of tasks
                }

                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);
                    }
                    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 {
                    //执行shutdown后的善后逻辑
                    if (isShuttingDown()) {
                        closeAll();
                        if (confirmShutdown()) {
                            return;
                        }
                    }
                } catch (Error e) {
                    throw e;
                } catch (Throwable t) {
                    handleLoopException(t);
                }
            }
        }
    }

NioEventLoop的run方法,是netty中最核心的方法,没有之一。在该方法中,完成了对已注册的channel上来自底层操作系统的socket事件的处理(在服务端时事件包括客户端的连接事件和读写事件,在客户端时是读写事件)、单线程任务队列的处理(服务端的注册事件、客户端的connect事件等),当然还包括对NIO空轮询的规避、消息的编解码等。

总结: 轮询的时序图如下
在这里插入图片描述

第三步:分发

将就绪事件,分发到事件附件的处理器handler中,由handler完成实际的处理.

在NioEventLoop.run()方法中,调用了processSelectedKeys()方法处理IO事件,IO事件分为两种,第一种是优化过的,第二种是普通的, Netty会尝试获取权限去优化原生Selector,如果可以,selectedKeys不为null,都是走优化过的处理方式,

private void processSelectedKeys() {
    if (selectedKeys != null) {
        //优化的selector的key
        processSelectedKeysOptimized();
    } else {
        // 原生的seletor 的key
        processSelectedKeysPlain(selector.selectedKeys());
    }
}

两种方式主要是遍历selectionKey的方式不同,具体处理事件的调用是一样的.看下processSelectedKeysPlain()方法的处理过程:

private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {

    if (selectedKeys.isEmpty()) {
        return;
    }

    //事件迭代
    Iterator<SelectionKey> i = selectedKeys.iterator();
    for (;;) {
        final SelectionKey k = i.next();
        final Object a = k.attachment();
        i.remove();

        if (a instanceof AbstractNioChannel) {
          //处理事件
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        if (!i.hasNext()) {
            break;
        }

        if (needsToSelectAgain) {
            selectAgain();
            selectedKeys = selector.selectedKeys();

            // Create the iterator again to avoid ConcurrentModificationException
            if (selectedKeys.isEmpty()) {
                break;
            } else {
                i = selectedKeys.iterator();
            }
        }
    }
}

迭代selectedKeys 获取就绪的IO事件,为每个事件都调用processSelectedKey来处理它. 在前面的Channel 注册时,将NioSocketChannel 以附加字段的方式添加到了selectionKey中,在这里,通过k.attachment()取得通道对象,然后就调用processSelectedKey来处理IO事件和通道.事件处理函数processSelectedKey()源码如下:

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) {
            // close the channel if the key is not valid anymore
            unsafe.close(unsafe.voidPromise());
        }
        return;
    }

    try {
        int readyOps = k.readyOps();
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);

            unsafe.finishConnect();
        }

        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
           unsafe.forceFlush();
        }
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

从代码中可知,处理事件有可读(OP_READ),可写(OP_WRITE) 等事件,以可读(OP_READ)为例看下通道的可读事件,会调用 unsafe.read()方法,AbstractNioByteChannel.NioByteUnsafe.read()其源码如下:

@Override
        public final void read() {
            final ChannelConfig config = config();
            if (shouldBreakReadReady(config)) {
                clearReadPending();
                return;
            }
            final ChannelPipeline pipeline = pipeline();
            final ByteBufAllocator allocator = config.getAllocator();
            final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
            allocHandle.reset(config);

            ByteBuf byteBuf = null;
            boolean close = false;
            try {
                do {
                    //分配缓冲区,大小自适应
                    byteBuf = allocHandle.allocate(allocator);
                    //核心代码:从socket revbuf中接收数据.
                    allocHandle.lastBytesRead(doReadBytes(byteBuf));
                    if (allocHandle.lastBytesRead() <= 0) {
                        // nothing was read. release the buffer.
                        byteBuf.release();
                        byteBuf = null;
                        close = allocHandle.lastBytesRead() < 0;
                        if (close) {
                            // There is nothing left to read as we received an EOF.
                            readPending = false;
                        }
                        break;
                    }

                    allocHandle.incMessagesRead(1);
                    readPending = false;
                     //通过pipeline触发读事件
                    pipeline.fireChannelRead(byteBuf);
                    byteBuf = null;
                } while (allocHandle.continueReading()); //判断是否继续读

                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (close) {
                    closeOnRead(pipeline);
                }
            } catch (Throwable t) {
                handleReadException(pipeline, byteBuf, t, close, allocHandle);
            } finally {
                ...
            }
        }
    }

在核心代码中,使用doReadBytes()函数从socket revbuf 读取数据,但每次读取前都需要记录缓冲区中可写区域的大小,用于判断缓冲区是否读满,继而决定是否继续读取数据.NioSocketChannel.doReadBytes()其源代码如下:

@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
   // 每次读取数据前,记录缓冲区中可写域大小,判断是否将缓冲区读满
    allocHandle.attemptedBytesRead(byteBuf.writableBytes());
    //重点代码: 首先通过JavaChannel()方法拿到java nio的原生通道,然后把数据读取到byteBuf.接着读取量
    // 由allocHandle.attemptedBytesRead()计算出来
    return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}

总结: NioByteUnsafe.read 方法的工作:

  • 分配缓冲区: 默认1024 byte,之后根据最近几次请求的数据包大小,猜测下一次数据包大小,
  • 读取数据:直接调用java nio 的底层代码.
  • 触发pipeline.fireChannelRead(byteBuf):开始pipeline流水线的业务处理.
  • 判断是否继续读:有两个标准,一是不能超过最大的读取次数(默认16次);二是缓冲区的数据每次都要读满,比如分配2 KB byteBuf,则必须读取2KB的数据.

总结:分发的时序图如下:
在这里插入图片描述

到此为止NioEventLoop(反应器)的三步曲就已经完成.

EventLoop 性能高的原因是EventLoop采用了"无锁化"设计;EventLoop是一个Reactor模型的事件处理器,一个EventLoop对应一个线程,其内部会维护一个selector和taskQueue,负责处理网络IO请求和内部任务,这里的selector和taskQueue是线程内部的.

selector的IO事件处理,以及任务队列的任务处理,都在EventLoop线程串行的执行.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

弯_弯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值