netty源码解析(4.0)-7 线程模型-IO线程EventLoopGroup和NIO实现(二)

19 篇文章 0 订阅
18 篇文章 0 订阅

把NIO事件转换成对channel unsafe的调用或NioTask的调用

processSelectedKeys()方法是处理NIO事件的入口:

private void processSelectedKeys() {

if (selectedKeys != null) {

processSelectedKeysOptimized();

} else {

processSelectedKeysPlain(selector.selectedKeys());

}

}

这个方法会调用processSelectedKeysOptimized或processSelectedKeysPlain开真正的NIO事件处理,这个两个方法的功能大致一样,不同的是前者是后者的优化版,优化点就在于它每次不用调用selector#selectedKeys()就能得到触发事件的SelectionKey。在processSelectedKeysOptimized中是通过遍历selectedKeys得到SelectionKey:

for (int i = 0; i < selectedKeys.size; ++i) {

final SelectionKey k = selectedKeys.keys[i];

selectedKeys.keys[i] = null;

final Object a = k.attachment();

if (a instanceof AbstractNioChannel) {

processSelectedKey(k, (AbstractNioChannel) a);

} else {

@SuppressWarnings("unchecked")

NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;

processSelectedKey(k, task);

}

}

标红的代码就是processSelectedKeysOptimized和processSelectedKeysPlain的不同之处。

processSelectedKey(SelectionKey k, AbstractNioChannel ch) 方法会把NIO转换成Channel Unsafe方法的调用,转换规则如下:

NIO事件

Channel Unsafe方法

异常

close

SelectionKey.OP_CONNECT

finishConnect

SelectionKey.OP_WRITE

forceFlush

SelectionKey.OP_READ, SelectionKey.OP_ACCEPT

read

processSelectedKey(SelectionKey k, NioTask<SelectableChannel> task) 方法会把NIO事件转成对NioTask的方法调用:

NIO事件

Channel Unsafe方法

所有正常的NIO事件

channelReady

异常

channelUnregistered

控制线程执行I/O操作和排队任务的用时比例

在run方法中,通过ioRatio属性值来控制事件NIO和executor任务的时间比例。可以调用setIoRatio(int ioRatio)方法设置ioRatio的值,它的取值范围是[0, 100], 当它的值是100时:

try {

processSelectedKeys();

} finally {

runAllTasks();

}

此时会先处理完所有的NIO事件再执行所有的executor任务,等于完全没有用时控制。当它的值是[0, 100)时:

final long ioStartTime = System.nanoTime();

try {

processSelectedKeys();

} finally {

final long ioTime = System.nanoTime() - ioStartTime;

runAllTasks(ioTime * (100 - ioRatio) / ioRatio);

}

此时会以处理NIO事件的时间为基准计算执行exeuctor任务的期望时间,之所以叫期望时间,原因是runAllTasks并不能有效地控制自己的执行时间,它每执行64个任务才会检查一次用时,如果这64个任务中有一个任务的执行时间过大,runAllTasks执行时间就会远大于期望时间。只有所有的executor任务执行时间足够短,runAllTasks才能较精确地控制自己的执行时间。为了能让这个时间控制机制有效地发挥作用,提交给NioEventLoop的任务应该是一些简单的任务,任务中尤其不能有导致线程阻塞的操作。

处理epoll selector cpu 100%的bug

在select方法中,如果调用selector.select(timeoutMillis)的调用次数大于SELECTOR_AUTO_REBUILD_THRESHOLD(它的值必须>0, 才有效),可以认为selector出现异常,此时会调用rebuildSelector方法重新创建selector。

SELECTOR_AUTO_REBUILD_THRESHOLD的值由-Dio.netty.selectorAutoRebuildThreshold决定,如果没有设置这个属性,SELECTOR_AUTO_REBUILD_THRESHOLD的默认值是512, 如这个值<0, SELECTOR_AUTO_REBUILD_THRESHOLD被设置成0。因此如果要SELECTOR_AUTO_REBUILD_THRESHOLD生效-Dio.netty.selectorAutoRebuildThreshold值必须>2或不设置这个属性。

正常情况下,在一次select调用中selector.select(timeoutMillis)被调用的次数不会大于2次,一次是正常的由于NIO事件或超时导致,另一次是在run方中的selector.wakeup()导致。如果selector.select(timeoutMillis)调用次数大于2,很有可能触发了JDK epoll selector cpu 100%的bug, NioEventLoop解决这个问题的办法是重新创建selector。

rebuildSelector方法是重新创建selector的入口,它调用rebuildSelector0方法执行真正的重建selector的操作,重建步骤如下:

1. 保存旧的selector

final Selector oldSelector = selector;

2. 调用openSelector方法创建新的selector

newSelectorTuple = openSelector();

3. 把旧selector上注册的Channel转移到新的selector上

for (SelectionKey key: oldSelector.keys()) {

Object a = key.attachment();

int interestOps = key.interestOps();

key.cancel();

SelectionKey newKey = key.channel().register(newSelectorTuple.unwrappedSelector, interestOps, a);

}

4. 关闭旧的selector

oldSelector.close();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值