前言
在Java NIO
中有一个著名的 bug epoll
, 这个 bug 会导致Reactor
线程被唤醒, 进行空轮询, 最终COU 100%爆满, 那么Netty
是怎么解决epoll
bug 的呢
在上篇文章中我们有说过, NioEventLoop.run()
方法是基于Selector
的轮询方法, 在方法内部实现了死循环去获取网络IO事件并执行
也是在该方法中Netty
巧妙地解决了epoll
bug, 接下来让我们定位到该方法
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
重建