NioEventLoop处理io的一些操作方法
io.netty.channel.nio.NioEventLoop#run
/**
* 启动服务 轮询selector
*/
@Override
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
// 重置 wakenUp 标记为 false
//wakenUp标识当前selector是否是唤醒状态
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
//处理轮询到的key
processSelectedKeys();
} finally {
// Ensure we always run tasks.
//启动task
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) {
...
}
}
这个run方法是在NioEventLoop线程启动的时候调用的,这里面主要:
- 轮询准备好的key
- 处理轮训出来的key
- 运行task任务
io.netty.channel.nio.NioEventLoop#select
/**
* 轮询selector方法
* @param oldWakenUp
* @throws IOException
*/
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
//delayNanos(currentTimeNanos)计算当前定时任务队列第一个任务的延迟时间
//select的时间不能超过selectDeadLineNanos这个时间
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
if (timeoutMillis <= 0) {
//如果超时 并且第一次轮询
//那么就再进行一次非阻塞的select 然后break结束轮询
if (selectCnt == 0) {
selector.selectNow();
selectCnt = 1;
}
break;
}
//nioEventLoop有任务在进行,那么就进行一次非阻塞的select 然后break结束轮询
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
selector.selectNow();
selectCnt = 1;
break;
}
//定时任务为空 截止时间没到,进行阻塞select
int selectedKeys = selector.select(timeoutMillis);
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
// - Selected something,
// - waken up by user, or
// - the task queue has a pending task.
// - a scheduled task is ready for processing
break;
}
if (Thread.interrupted()) {
// Thread was interrupted so reset selected keys and break so we not run into a busy loop.
// As this is most likely a bug in the handler of the user or it's client library we will
// also log it.
//
// See https://github.com/netty/netty/issues/2426
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();
/**
* time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos
* 属于正常的 证明我进行了一次阻塞select
* else :属于空轮训 就是进行了阻塞select但是立即返回了,并没有阻塞
*/
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
// timeoutMillis elapsed without anything selected.
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
//当轮询次数超过一个阈值 512 要解决jdk的空轮训bug了
// The selector returned prematurely many times in a row.
// Rebuild the selector to work around the problem.
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
//重新创建一个selector 把oldSelector上的key赋值给他
rebuildSelector();
selector = this.selector;
// Select again to populate selectedKeys.
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
...
}
这个方法又一个jdk空轮训的方法,这个是nio原生就存在的bug,就是进行select操作立即返回,并没有阻塞式的select,因此for循环方法会一直走下去,这样就会导致cpu占用率很高,当轮询次数超过一个阈值 512 要解决jdk的空轮训bug了,下面我们来分析解决空轮训怎么实现
/**
* Replaces the current {@link Selector} of this event loop with newly created {@link Selector}s to work
* around the infamous epoll 100% CPU bug.
* 解决jdk空轮训导致cpu占用100%的bug
* 方案:
* 创建新的selector 把oldSelector上面的key都迁移到新的上面
*/
public void rebuildSelector() {
if (!inEventLoop()) {
execute(new Runnable() {
@Override
public void run() {
rebuildSelector0();
}
});
return;
}
rebuildSelector0();
}
这里会判断当前执行的线程是否在NioEventLoop的线程中,如果不在NioEventLoop线程,则添加到task队列等待执行
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
//判断正在执行任务的线程是否是eventloop已经保存的线程
boolean inEventLoop = inEventLoop();
addTask(task);
//如果不是在eventloop线程证明还没启动轮询和任务调度run方法
//则启动线程
if (!inEventLoop) {
//启动线程
startThread();
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
这里判断不是在NioEventLoop线程的话就去启动线程,那么会每个不是在NioEventLoop线程执行的任务都去启动线程么?答案是否定的,且看startThread方法
private void startThread() {
//状态值是未启动过
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
try {
//真正启动的逻辑
doStartThread();
} catch (Throwable cause) {
STATE_UPDATER.set(this, ST_NOT_STARTED);
PlatformDependent.throwException(cause);
}
}
}
}
下面我们来看真正启动线程的逻辑
private void doStartThread() {
assert thread == null;
//交给线程执行器去执行代码 ThreadPerTaskExecutor
executor.execute(new Runnable() {
@Override
public void run() {
//保存当前线程异步任务的线程
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
//启动NioEventLoop的run()方法 进行selector轮询
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
.....
}
这里会把当前启动的线程保存到成员变量thread,后续判断是否是NioEventLoop线程也是比较线程是否是一个,然后调用NioEventLoop的run方法,所以run方法执行的代码都是在NioEventLoop线程里面运行的
回头再看io.netty.channel.nio.NioEventLoop#rebuildSelector0
/**
* 重新构建selector
*/
private void rebuildSelector0() {
//保存旧的selector
final Selector oldSelector = selector;
final SelectorTuple newSelectorTuple;
if (oldSelector == null) {
return;
}
try {
//创建新的selector 创建过程也会走优化的流程
newSelectorTuple = openSelector();
} catch (Exception e) {
logger.warn("Failed to create a new Selector.", e);
return;
}
// 把所有的 channels 注册到新的 Selector.
int nChannels = 0;
for (SelectionKey key: oldSelector.keys()) {
Object a = key.attachment();
try {
//key失效 跳过
if (!key.isValid() || key.channel().keyFor(newSelectorTuple.unwrappedSelector) != null) {
continue;
}
//注册的关心事件
int interestOps = key.interestOps();
key.cancel();
//取出来key里面的channel 注册到新的selector里面 返回新的selectionKey
SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
if (a instanceof AbstractNioChannel) {
// Update SelectionKey 如果attachment是AbstractNioChannel
//那么将AbstractNioChannel上的selectionKey也更新一下
((AbstractNioChannel) a).selectionKey = newKey;
}
//channel的数量增加
nChannels ++;
} catch (Exception e) {
....
}
//把新的selector引用传递给成员变量
selector = newSelectorTuple.selector;
unwrappedSelector = newSelectorTuple.unwrappedSelector;
try {
// time to close the old selector as everything else is registered to the new one
//关闭旧的selector
oldSelector.close();
} catch (Throwable t) {
....
}
}
这里主要就是为了解决空轮训的方法,创建一个新的selector,把oldSelector上面的key都注册到新的上面,这样就解决了
承接上文,解决完空轮训,也轮询除了key,那么接着就是处理key了
io.netty.channel.nio.NioEventLoop#processSelectedKeys
/**
* selectedKeys不为null证明我们用的是优化后的selector
* 那么就按照优化后的逻辑去处理
*/
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
这里主要是对优化和未优化的selector分别做的处理,大致逻辑都一样,只不过因为底层selectKeys的数据结构不一样,遍历方法不一样,我们只分析processSelectedKeysOptimized
/**
* 处理优化后的selector,selectedKeys是一个数组
*/
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
// null out entry in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
//拿到selectedKey,把数组的引用置为null
selectedKeys.keys[i] = null;
//拿到key的attachment 也就是AbstractNioChannel
final Object a = k.attachment();
//如果是AbstractNioChannel
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
selectedKeys.reset(i + 1);
selectAgain();
i = -1;
}
}
}
NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey,io.netty.channel.nio.AbstractNioChannel)
/**
* 处理select出来的key
* @param k SelectionKey
* @param ch AbstractNioChannel注册进去attachment
*/
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//拿到Unsafe对象 NioMessageUnsafe是服务端的channel操作对象
//NioByteUnsafe 客户端的channel操作对象
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
//key失效
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 {
//拿到key的事件标识
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) {
ch.unsafe().forceFlush();
}
/**
* 读事件和 accept事件都会经过这里,但是拿到的unsafe对象不同 所以后续执行的read操作也不一样
* NioServerChannel进行accept操作
* NioChannel进行read操作
*/
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
我们分析当服务端channel accept到新的连接时候unsafe.read();走的是NioMessageUnsafe,当客户端连接read事件到来时候,走的是NioByteUnsafe.read()方法
private final class NioMessageUnsafe extends AbstractNioUnsafe {
private final List<Object> readBuf = new ArrayList<Object>();
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
//拿到channel处理的pipline
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
//readBuf是一个集合 来保存accept出的channel
//具体read操作
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
//调用pipline的read方法通知事件
pipeline.fireChannelRead(readBuf.get(i));
}
//清除readBuf集合
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
.....
} finally {
.....
}
}
}
- 把accept到的新连接channel添加到集合
- 调用pipline的的通知事件pipeline.fireChannelRead(readBuf.get(i));
- 通知channel read完毕pipeline.fireChannelReadComplete();
接着我们看doReadMessages方法
- 服务端io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages处理的是accept到的channel,并且进行后续的注册到worker group中的NioEventLoop中去处理
客户端的NioSocketChannel#doReadMessages处理的是worker group selector 轮询到的read事件
io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
//拿到新连接的channel
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
//添加到集合中去
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}