一、操作系统中的多路复用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的主要实现方式有:select
、poll
、epoll
等。
在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.*
下的Channel
、Buffer
、Selector
等类,来支持高性能的请求多路复用。其中,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及后续版本有所不同。
进入Selector
的open()
方法中:
public abstract class Selector implements Closeable {
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
}
Selector
是通过SelectorProvider
创建出来的,SelectorProvider
的provider()
如下所示:
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
。
下面进入DefaultSelectorProvider
的create()
方法,如下所示:
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
会默认实例化为EPollSelectorProvider
。EPollSelectorProvider
中的openSelector()
方法会创建一个类型为EPollSelectorImpl
的Selector
实例,如下所示:
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
,详见NioEventLoop
的openSelector()
方法:
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
中的publicKeys
与publicSelectedKeys
的HashMap
通过反射的方式替换为自定义的SelectedSelectionKeySet
(即selectedKeys
属性)。该Set重写优化了Set
的add()
、iterator()
、reset()
等方法。 - 将
selector
本身实例化为SelectedSelectionKeySetSelector
。该Selector中有一个原始unwrappedSelector
的委托属性,大部分方法依旧调用委托实例中的方法,但针对selectNow()
与select()
方法,则重写优化,改为调用上述SelectedSelectionKeySet
中的reset()
方法。
(三)Netty中Selector的执行
在执行NioEventLoop
的run()
后,会调用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中实现多路复用的关键组件是Channel
、Buffer
、Selector
等类。Selector
封装了操作系统底层的epoll
指令来实现IO多路复用。
Netty同样采用了Java原生NIO的Selector
组件来实现多路复用,只不过在其基础上做了部分优化。
参考文档