前言:基于netty4.0源码分析,不同版本差异较大,大家主要关注select和epoll模型,reactor工作方式,netty与javachannel的衔接等知识点
class NioEventLoop{
protected void run() {
自旋处理
for (;;) {
try {
step-1: hasTask 执行selector.selectNow,没有任务执行select(wakenUp)
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
step-2: 没有任务则执行NioEventLoop.select
select(wakenUp.getAndSet(false));
step-3: selector.wakeup防止并发问题
if (wakenUp.get()) {
selector.wakeup();selector未执行,执行了wakeup,下次执行selector.select(time)不会阻塞
}
default:
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
step-4: 如果io处理时间比例为100%
if (ioRatio == 100) {
try {
step-5.1: 处理io事件
processSelectedKeys();
} finally {
step-6.1: 处理加入taskQueue和scheduledTaskQueue中的任务
runAllTasks();
}
} else {
step-5.2: 处理io事件
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
step-6.2: 计算io事件处理时长 根据ioRatio计算队列普通任务的处理时长并执行runAllTasks
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
step-7: 关闭netty
try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) {
return;
}
}
} catch (Throwable t) {
handleLoopException(t);
}
}
}
}
前文介绍nio的selectionKeys迭代每一个key处理完后必须执行remove,
这里使用包装类屏蔽了相关nio易错点
@Override
public int selectNow() throws IOException {
清空selectionKeys
selectionKeys.reset();
return delegate.selectNow();
}
public int selectNow() throws IOException {
return this.lockAndDoSelect(0L);
}
private int lockAndDoSelect(long var1) throws IOException {
synchronized(this) {
if (!this.isOpen()) {
throw new ClosedSelectorException();
} else {
int var10000;
synchronized(this.publicKeys) {
synchronized(this.publicSelectedKeys) {
var10000 = this.doSelect(var1);
}
}
return var10000;
}
}
}
protected int doSelect(long var1) throws IOException {
boolean var3 = false;
if (this.closed) {
throw new ClosedSelectorException();
} else {
this.processDeregisterQueue();
int var7;
try {
this.begin();
调用kqueueWrapper实现多路复用选择
var7 = this.kqueueWrapper.poll(var1);
} finally {
this.end();
}
this.processDeregisterQueue();
更新selectKeys集合
return this.updateSelectedKeys(var7);
}
}
int poll(long var1) {
this.updateRegistrations();
int var3 = this.kevent0(this.kq, this.keventArrayAddress, 128, var1);
return var3;
}
private native int kevent0(int var1, long var2, int var4, long var5);
携带可唤醒机制的select,核心方法,下图较为复杂,了解其核心思想
如果有任务,则期望不要阻塞在select方法上 |
---|
如果没有任务,则期望阻塞在select方法上 |
怎么控制阻塞,通过wakeUp变量控制 |
wakeUp为true表示唤醒期望不阻塞 |
NioEventLoop添加任务,调用execute,则wakeup被置为true |
private void select(boolean oldWakenUp) throws IOException {
Selector selector = this.selector;
try {
表示执行过多少次select
int selectCnt = 0;
long currentTimeNanos = System.nanoTime();
没有定时任务返回1秒
有定时任务任务未到执行时间,返回任务还需要等待多久的执行时间
定时任务到期,返回0
long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);
for (;;) {
long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L/* 数学修正 超过半毫秒当做有定时任务 */) / 1000000L;
if (timeoutMillis <= 0) { 无定时任务或者自旋到timeoutMillis小于0
if (selectCnt == 0) { 没有自旋选择过
io事件处理完 立马处理taskscheduledTask
selector.selectNow();
selectCnt = 1;
}
没有select过则执行一次select,然后break,去处理scheduledTask
有select过则selectCnt>0,直接中断
currentTimeNanos更新到小于0
break;
}
if (hasTasks() && wakenUp.compareAndSet(false, true)) {
1 任务提交时正常都会将wakenUp设置成true,执行唤醒Selector.wakeup
2 但是wakenUp设置成true是cas操作,要求wakeUp前置状态必须是false
3 这就造成并发提交任务时,由于wakeUp值没有更新,也就没有触发Selector.wakeup
4 则在这里二次检查有任务 执行selector.selectNow 而不是含有阻塞的selector.select(timeoutMillis)
selector.selectNow();
selectCnt = 1;
break;
}
空轮询bug: selectedKeys= 0 并且不会发生timeoutMillis的wait 查询后立马返回
wakeup=true并且执行了selector.wakeup 则这里不会阻塞
int selectedKeys = selector.select(timeoutMillis);
选择器选择次数增加
selectCnt ++;
if (selectedKeys != 0 || oldWakenUp /* 需要唤醒则break */|| wakenUp.get()/*外部唤醒 比如关闭或者提交任务*/ || hasTasks() || hasScheduledTasks()) {
break;
}
if (Thread.interrupted()) {
...... 删除日志代码
selectCnt = 1;
break;
}
long time = System.nanoTime();
// currentTimeNanos------------------timeoutMillis时间
超过了超时时间则selectCnt=1打破重建selector的关键
if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
selectCnt = 1;
} else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 &&
selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
logger.warn(
"Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.",
selectCnt, selector);
如果select超过512,netty认为空轮询bug发生,重建selector
rebuildSelector();
selector = this.selector;
selector.selectNow();
selectCnt = 1;
break;
}
currentTimeNanos = time;
}
...... 删除日志代码
} catch (CancelledKeyException e) {
...... 删除日志代码
}
}
private void processSelectedKeys() {
if (selectedKeys != null) {
处理优化过的select数据结构[优化包括数组代替set提高遍历性能等]
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
selectedKeys.keys[i] = null;
NETTY的 NioSocketChannel或者NioServerSocketChannel
final Object a = k.attachment();
遍历处理
processSelectedKey(k, (AbstractNioChannel) a);
...... 删除其他代码
}
}
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final NioUnsafe unsafe = ch.unsafe();
...... 删除其他代码
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
底层调用firechannelActive
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
将netty缓冲区写入javachannel缓冲区
ch.unsafe().forceFlush();
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
处理客户端读事件或者服务端ACCEPT事件
客户端NioByteUnsafe 服务端NioMessageUnsafe
unsafe.read();
}
}
protected boolean runAllTasks(long timeoutNanos) {
scheduledTaskQueue队列任务转移到taskQueue
fetchFromScheduledTaskQueue();
判断taskQueue是否存在任务
Runnable task = pollTask();
if (task == null) {
return false;
}
计算任务执行到何时被打破
final long deadline = ScheduledFutureTask.nanoTime() + timeoutNanos;
long runTasks = 0;
long lastExecutionTime;
遍历处理任务
for (;;) {
task.run();
runTasks ++;
每运行64个任务检测下是否超时
if ((runTasks & 0x3F) == 0) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
超时中断任务执行
if (lastExecutionTime >= deadline) {
break;
}
}
无任务中断执行,有任务继续自旋
task = pollTask();
if (task == null) {
lastExecutionTime = ScheduledFutureTask.nanoTime();
break;
}
}
this.lastExecutionTime = lastExecutionTime;
return true;
}
private boolean fetchFromScheduledTaskQueue() {
Runnable scheduledTask = pollScheduledTask(nanoTime);
while (scheduledTask != null) {
if (!taskQueue.offer(scheduledTask)) {
scheduledTaskQueue().add((ScheduledFutureTask<?>) scheduledTask);
return false;
}
scheduledTask = pollScheduledTask(nanoTime);
}
return true;
}
protected final Runnable pollScheduledTask(long nanoTime) {
assert inEventLoop();
Queue<ScheduledFutureTask<?>> scheduledTaskQueue = this.scheduledTaskQueue;
ScheduledFutureTask<?> scheduledTask = scheduledTaskQueue == null ? null : scheduledTaskQueue.peek();
if (scheduledTask == null) {
return null;
}
到达处理时间
if (scheduledTask.deadlineNanos() <= nanoTime) {
scheduledTaskQueue.remove();
return scheduledTask;
}
return null;
}
为解决wakeup为true但依旧select挂起现象,采用以下方式
if (wakenUp.get()) {
// 如果在下次执行select(wakeup)有可能会走到selector.select(timeoutMillis),为确保wakeup=true不应该阻塞(并发干扰),这里在每次自旋前执行selector.wakeup
selector.wakeup();1 selector阻塞中会被唤醒 2 selector未执行,执行了wakeup,下次执行select.select(time)会被唤醒,不会阻塞
}
//
Q: cpu100%
A: 操作系统层面bug导致 selector.select(timeoutMillis) 返回0也不会阻塞,
造成select(wakeup)方法空转 导致cpu!00%,通过重建selector解决
private void rebuildSelector0() {
final Selector oldSelector = selector;
final SelectorTuple newSelectorTuple;
int nChannels = 0;
获取所有注册的key和其附件上的netty channel对象
for (SelectionKey key: oldSelector.keys()) {
NioServerSocketChannel或者 NioSocketChannel
Object a = key.attachment();
...... 删除其他代码
int interestOps = key.interestOps();
key.cancel();
重新注册
SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);
if (a instanceof AbstractNioChannel) {
更新channel的selectKey 相互持有
((AbstractNioChannel) a).selectionKey = newKey;
}
...... 删除其他代码
}
...... 删除其他代码
关闭老的选择器
oldSelector.close();
}