Netty「源码阅读」之怎么解决 Java 的 epoll 空轮询 bug

2,055 阅读3分钟

「我正在参与掘金会员专属活动-源码共读第一期,点击参与

前言

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进行边界值判断

image.png

Switch

switch中, 一共case了三个常量

  • SelectStrategy.CONTINUE: 继续, 直接跳过, 这里不做讲解
  • SelectStrategy.BUSY_WAIT: NIO不支持 BUSY_WIAT, 所以和 SELECT 是一样的
  • SelectStrategy.SELECT: 判断当前 IO事件 是否就绪, 如果没有就绪就让线程休息, 不进行唤醒

image.png

SelectStrategy.SELECT

具体判断线程是否要苏醒的地方是下面两个红框

image.png

unexpectedSelectorWakeup()方法

实际上解决空轮询问题的地方就是在这里

image.png

在这个方法中我们可以看到, 方法返回值是判断selectCnt是否大于静态变量SELECTOR_AUTO_REBUILD_THRESHOLD, 那么我们看一下这个变量的赋值过程

image.png

跟着序号看起, 先是定义了变量, 然后将selectorAutoRebuildThreshold赋值于SELECTOR_AUTO_REBUILD_THRESHOLD, 可以看到执行了SystemPropertyUtil.getInt方法, 这个方法是获取你配置文件中的值, 如果你的配置文件中没有进行配置, 那么就使用后面的默认值512

这也就是为什么, 我们经常看到说: 当Netty空轮询阈值是512

假设我们的selectCnt已经达到了512, 还会执行一个方法rebuildSelector();顾名思义, 重建Selector

image.png

这个方法的作用是将当前这个发生空轮训的selector上的selectedKeys挪到新建的selector选择器上,从而完美解决空轮训的问题

然后我们的unexpectedSelectorWakeup返回true, 回到之前的run()方法中可以看到, 作者将selectCnt重新赋值为了0, 所以作者也不算是解决了这个bug, 但是巧妙的将其绕过了

image.png

总结

总结就简单写写, 初始化变量selectCnt, 每次轮询进行++操作, 后面会将其进行归零, 如果没有归零selectCnt, 会执行unexpectedSelectorWakeup()方法, 如果达到了512, 会进行selector重建




本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见