一、操作系统中的多路复用IO模型

Java本身并不实现底层IO,而是通过native方法封装操作系统IO指令。

Linux(Unix)作为Java应用部署的主流操作系统,有五种IO模型:

  • 阻塞(blocking) IO
  • 非阻塞(nonblocking) IO
  • 多路复用(multiplexing) IO
  • 信号驱动(signal driven) IO
  • 异步(asynchronous) IO

其中,操作系统底层的多路复用IO是Java NIO与Netty实现请求多路复用的基础。

Linux操作系统多路复用IO的主要实现方式有:selectpollepoll等。

在Linux的JDK8中,epoll是主要实现方式,poll则是作为epoll不可用时的备选(主要是用来兼容非SunOS与Linux的其它操作系统,详见Selector的初始化原理中的DefaultSelectorProvider类),select已被淘汰。在Liniux的JDK17中,epoll是唯一的实现方式。预计在后续的JDK版本中,epoll也将作为Linux平台支持多路复用IO的默认选项。

二、Java原生NIO中的多路复用

Java中存在三种IO体系,分别是:BIO(OIO)、NIO、AIO,它们的名词解释如下:

  • BIO = Blocking IO,阻塞式IO。OIO = Old Blocking IO,旧的阻塞式IO,与BIO等价。
  • NIO = Nonblocking IO = New IO,非阻塞式IO(新IO)。JDK1.4开始支持。
  • AIO = Asynchronous IO,异步IO。JDK1.7开始支持。

Java NIO通过java.nio.*下的ChannelBufferSelector等类,来支持高性能的请求多路复用。其中,Selector类是实现多路复用IO的关键。

虽然NIO又被称之为非阻塞式IO,但是需要注意的是,NIO和Linux(Unix)五种IO模型中的非阻塞IO不是同一个东西,NIO的底层实现更偏向于IO模型中的多路复用IO。

(一)Selector的初始化

Selector类的初始化很简单,只需一行代码,如下:

Selector selector = Selector.open();

其内部细节详见Selector的初始化原理

(二)Selector的使用

Selector的相关API有:

  • SelectableChannel.register():Selector注册
  • Selector.selectedKeys():获取Selector中所有的事件
  • SelectionKey.isXXX():判断事件的状态

一个Selector类的使用代码片段如下:

while (true) {
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while (iterator.hasNext()) {
        SelectionKey selectionKey = iterator.next();
        if (selectionKey.isAcceptable()) {
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_READ);
        } else if(selectionKey.isReadable()) {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            System.out.println(readDataFromSocketChannel(socketChannel));
            socketChannel.close();
        }
        iterator.remove();
    }
}

(三)Selector的初始化原理

下述代码以JDK8为例,JDK17及后续版本有所不同。

进入Selectoropen()方法中:

public abstract class Selector implements Closeable {
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }
}

Selector是通过SelectorProvider创建出来的,SelectorProviderprovider()如下所示:

public abstract class SelectorProvider {
    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                        if (loadProviderFromProperty())
                            return provider;
                        if (loadProviderAsService())
                            return provider;
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
        }
    }
}

如上述代码所示,将依次从以下位置获取SelectorProvider实例:

  • 系统属性
  • SPI机制(ServiceLoader)
  • 默认的DefaultSelectorProvider

下面进入DefaultSelectorProvidercreate()方法,如下所示:

public class DefaultSelectorProvider {
    public static SelectorProvider create() {
        String osname = AccessController.doPrivileged(new GetPropertyAction("os.name"));
        if (osname.equals("SunOS"))
            return createProvider("sun.nio.ch.DevPollSelectorProvider");
        if (osname.equals("Linux"))
            return createProvider("sun.nio.ch.EPollSelectorProvider");
        return new sun.nio.ch.PollSelectorProvider();
    }    
}

如上所示,在Linux系统中,SelectorProvider会默认实例化为EPollSelectorProviderEPollSelectorProvider中的openSelector()方法会创建一个类型为EPollSelectorImplSelector实例,如下所示:

public class EPollSelectorProvider extends SelectorProviderImpl {
    public AbstractSelector openSelector() throws IOException {
        return new EPollSelectorImpl(this);
    }
}

见名知意,EPollSelectorProvider底层使用epoll来实现多路复用IO。

三、Netty中的多路复用

Netty同样基于Selector类完成请求的多路复用。

(一)Netty中Selector的初始化

Netty在创建NioEventLoopGroup的过程中,其构造函数会调用SelectorProvider.provider()创建SelectorProvider实例,如下所示:

public class NioEventLoopGroup extends MultithreadEventLoopGroup {
    public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());
    }
}

SelectorProvider.provider()的过程与NIO中Selector的初始化原理完全一样。

NioEventLoopGroup后续创建对应的NioEventLoop实例时,会将SelectorProvider作为构造方法属性传递下去,如下所示:

public class NioEventLoopGroup extends MultithreadEventLoopGroup {
    @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        // 获取SelectorProvider
        SelectorProvider selectorProvider = (SelectorProvider) args[0];
        
        // ...其它逻辑

        // 创建新的NioEventLoop,将SelectorProvider作为构造方法函数传递给NioEventLoop
        return new NioEventLoop(this, executor, selectorProvider,
                selectStrategyFactory.newSelectStrategy(),
                rejectedExecutionHandler, taskQueueFactory, tailTaskQueueFactory);
    }
}

NioEventLoop的构造方法如下:

public final class NioEventLoop extends SingleThreadEventLoop {
    private Selector selector;
    private Selector unwrappedSelector;
    private SelectedSelectionKeySet selectedKeys;

    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
                 EventLoopTaskQueueFactory taskQueueFactory, EventLoopTaskQueueFactory tailTaskQueueFactory) {
        super(parent, executor, false, newTaskQueue(taskQueueFactory), newTaskQueue(tailTaskQueueFactory),
                rejectedExecutionHandler);
        this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
        // 创建Selector元祖,其中包含selector与unwrappedSelector
        final SelectorTuple selectorTuple = openSelector();
        this.selector = selectorTuple.selector;
        this.unwrappedSelector = selectorTuple.unwrappedSelector;
    }
}

至此,NioEventLoop中的Selector创建完成。上述openSelector()方法内部对Selector做了优化,有关优化细节详见Netty中Selector的优化

(二)Netty中Selector的优化

Netty中的NioEventLoop在创建Selector时,会根据DISABLE_KEY_SET_OPTIMIZATION属性判断是否需要优化Selector,详见NioEventLoopopenSelector()方法:

public final class NioEventLoop extends SingleThreadEventLoop {

    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) {
            // 如果禁用优化,不对selector做任何优化处理
            return new SelectorTuple(unwrappedSelector);
        }

        // 通过反射拿到SelectorImpl类
        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;
                }
            }
        });

        final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
        final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();

        Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

                // 通过反射重置selectedKeys与publicSelectedKeys
                selectedKeysField.set(unwrappedSelector, selectedKeySet);
                publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                return null;
            }
        });

        selectedKeys = selectedKeySet;

        // 将selector实例化为SelectedSelectionKeySetSelector
        return new SelectorTuple(unwrappedSelector,
                                 new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
    }
}

在开启优化后(默认开启),上述代码主要做了两件事:

  • selector中的publicKeyspublicSelectedKeysHashMap通过反射的方式替换为自定义的SelectedSelectionKeySet(即selectedKeys属性)。该Set重写优化了Setadd()iterator()reset()等方法。
  • selector本身实例化为SelectedSelectionKeySetSelector。该Selector中有一个原始unwrappedSelector的委托属性,大部分方法依旧调用委托实例中的方法,但针对selectNow()select()方法,则重写优化,改为调用上述SelectedSelectionKeySet中的reset()方法。

(三)Netty中Selector的执行

在执行NioEventLooprun()后,会调用processSelectedKeys(),该方法是处理IO事件的核心,如下:

public final class NioEventLoop extends SingleThreadEventLoop {

    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }
}

由于默认开启了优化,在openSelector()中一定为selectedKeys赋了值(详见Netty中Selector的优化),因此会走到processSelectedKeysOptimized()processSelectedKeysOptimized则会根据优化后的selectedKeys执行后续的processSelectedKey()。有关后续更多细节,可进一步阅读源码。

四、总结

Linux操作系统的五种IO模型中的多路复用IO模型是后续NIO和Netty实现请求复用的基础,其实现方式主要是epoll

Java原生NIO中实现多路复用的关键组件是ChannelBufferSelector等类。Selector封装了操作系统底层的epoll指令来实现IO多路复用。

Netty同样采用了Java原生NIO的Selector组件来实现多路复用,只不过在其基础上做了部分优化。

参考文档

  1. Unix IO 模型
  2. Netty官网
  3. Netty(二):io请求处理过程解析
  4. netty系列之:NIO和netty详解