前言
在Java NIO中有一个著名的 bug epoll, 这个 bug 会导致Reactor线程被唤醒, 进行空轮询, 最终COU 100%爆满, 那么Netty是怎么解决epollbug 的呢
在上篇文章中我们有说过, NioEventLoop.run()方法是基于Selector的轮询方法, 在方法内部实现了死循环去获取网络IO事件并执行
也是在该方法中Netty巧妙地解决了epollbug, 接下来让我们定位到该方法
Netty 解决 epoll
在这个方法中, 我将重点的步骤进行了标注, 先说流程, 然后在进行针对性的讲解
- 初始化
selectCnt这个变量是解决epoll的关键 - 定义变量
strategy, 他是int类型, 默认是0 - 执行
selectStrategy.calculateStrategy()方法获取当前网络IO事件数量 - 根据
strategy来执行不同的操作, 这个后面贴详细代码讲一下 - 这个时候我们的变量
selectCnt++ - 我们会对当前执行的任务和
strategy进行判断, 对selectCnt进行归零操作 - 否则, 我们会对执行方法
unexpectedSelectorWakeup()对selectCnt进行边界值判断
Switch
在switch中, 一共case了三个常量
SelectStrategy.CONTINUE: 继续, 直接跳过, 这里不做讲解SelectStrategy.BUSY_WAIT: NIO不支持 BUSY_WIAT, 所以和 SELECT 是一样的SelectStrategy.SELECT: 判断当前 IO事件 是否就绪, 如果没有就绪就让线程休息, 不进行唤醒
SelectStrategy.SELECT
具体判断线程是否要苏醒的地方是下面两个红框
unexpectedSelectorWakeup()方法
实际上解决空轮询问题的地方就是在这里
在这个方法中我们可以看到, 方法返回值是判断selectCnt是否大于静态变量SELECTOR_AUTO_REBUILD_THRESHOLD, 那么我们看一下这个变量的赋值过程
跟着序号看起, 先是定义了变量, 然后将selectorAutoRebuildThreshold赋值于SELECTOR_AUTO_REBUILD_THRESHOLD, 可以看到执行了SystemPropertyUtil.getInt方法, 这个方法是获取你配置文件中的值, 如果你的配置文件中没有进行配置, 那么就使用后面的默认值512
这也就是为什么, 我们经常看到说: 当Netty空轮询阈值是512
假设我们的selectCnt已经达到了512, 还会执行一个方法rebuildSelector();顾名思义, 重建Selector
这个方法的作用是将当前这个发生空轮训的selector上的selectedKeys挪到新建的selector选择器上,从而完美解决空轮训的问题
然后我们的unexpectedSelectorWakeup返回true, 回到之前的run()方法中可以看到, 作者将selectCnt重新赋值为了0, 所以作者也不算是解决了这个bug, 但是巧妙的将其绕过了
总结
总结就简单写写, 初始化变量selectCnt, 每次轮询进行++操作, 后面会将其进行归零, 如果没有归零selectCnt, 会执行unexpectedSelectorWakeup()方法, 如果达到了512, 会进行selector重建