管你 JDK 还是 Linux,我 Netty 稳坐钓鱼台
Posted 马小瑄
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了管你 JDK 还是 Linux,我 Netty 稳坐钓鱼台相关的知识,希望对你有一定的参考价值。
JDK NIO 在 Linux 系统下空轮询的 bug,就是调用 Selector.select(timeout),即使没事件发生,也不会阻塞 timeout 时间,而是立马 return,这样的空轮询导致 CPU 100%。
产生这个 bug 大致的原因我讲下:连接突然中断,poll 和 epoll 会被 POLLHUP 或者 POLLERR 事件唤醒,于是 Selector 就被唤醒了,但是 JDK Selector 一看,没事件(JDK只定义了CONNECT、READ、WRITE、ACCEPT这几个事件)需要处理啊?
然后就又循环了....没事件要处理啊,然后就又循环了....没事件要处理啊,然后就又循环了....
如此往复,空轮询使得 CPU 100%。
这个 BUG 真算是个老黄历了,我查了查 JDK 的 bug 库,大致 13 年 3 月份之后就没提相关的 bug 了,而且按照官方的说法这也和 Linux 版本有关,至今应该没这问题了?(我不确定)。
我们来回顾一下 bug 库的历史。
我查了下最早提到 Selector(底层用的是 poll 或者 epoll) 在 Linux 不会阻塞的 BUG 是在 06 年 3 月 24 日。
可以看到,这 Resolved 的日期有点长,隔了一年,也就是 06 提的 07 年才说修复了,不过当天是给了个解决方案的:
解决方案简单粗暴,就是把抽风不阻塞的 Selector 删了,然后创建个新的,取而代之。(有时候简单粗暴的就是最好的)
我再往后找了找 Selector 的 bug ,发现 08 年还有 bug(不是说修复了吗?),并且处理的日期是 13 年!最终结果是无法修复,相关的是 JDK 6 版本。
同 13 年还有类似的 bug ,不过是在 JDK 7 版本上,最终 resolution 是不完整的修复!
从这个处理时间和结果来看,我个人推断 JDK 对此 bug 的态度是消极的,认为这是 Linux 自身的 bug 导致的现象(同为程序员习惯性地甩)。
从 JDK-6670302 的评价就可以看出:
大致的意思就是:升级下 Linux 内核版本(2.4版本有这个问题)就好了,2.6 版本发布已经 4 年了都,这个修复也没啥必要了,需求很小。
这其实可以理解。
站在 JDK 开发者的角度来看:我这代码在 windows 下运行的好好的,到你 Linux 就不行了?嗯?我的问题?Linux 为什么给我这样莫名其妙的事件?你中断唤醒了个什么玩意?
但是站 Linux 开发者角度来看就不一样了:嗯?甩锅给我?明明是你写代码没考虑到这种特殊情况,搁着跟我甩锅呢?
那站我们 Java 开发而言:你搁着跟我搁那呢?有 bug 还搁这甩,我管你 JDK 什么情况,你就得给我修!(我相信 JDK 开发者也是这样看 Linux 开发者的)
哈哈哈,真不真实?
总之,我个人觉得这个 bug 之所以会被网上的文章拿出来反复鞭尸.
一是,因为 netty 通过曲线救国的方式,确实避免了那个时间段部署在 Linux 的应用直接用 Java Selector 产生的空轮询 bug,所以在当时谈到 Netty 就值得拿这个说事儿。
二是,天下文章一大抄嘛,懂的都懂。
对了,虽然我查 bug 库发现后面确实都没再有类似的bug,但是网上有文章说在 JDK8 还是重现了这个 bug!
链接:https://juejin.cn/post/6844903491505242119
啧啧,俗话说得好,靠人不如靠己,Netty 就是靠己来解决这个 bug,也就是上面提到的简单粗暴的解决办法!
Netty :空循环是吧,我数数你循环几次,只要到达一定次数,我就认为你产生 bug 了,于是我就重建一个 Selector,废弃以前那个已经抽风了的 Selector!这样我管你 JDK 还是 Linux 会不会处理,我这波就是稳坐钓鱼台!
这就是 Netty 的解决办法~所以也不能说是 Netty 修复了 JDK NIO 的 bug ,它只是曲线救国,变相避免了这个 bug 罢了。
这其实能给我们日常的开发提供一点思路,有时候求人不如求己,对于二方、三方的接口还是报以质疑的态度去看待,不要过度的相信他们,特别是三方的接口,一定要做好对方挂掉或者返回奇怪结果的准备。
我之前对接某大厂的接口,返回值就无声无息的变了,没有任何公告和通知,就是那种你认为不可能会变的值。比如一个返回城市名的接口,正常返回叫杭州市,它莫名其妙变了个杭州市(常用)。当然我只是举个例子哈,具体是啥不方便说。
还有之前对接过另一个大厂的接口,那时候他们的服务几乎属于要挂的情况,返回的贼慢,经常超时,这种我还是有经验的,起初就我设置了接口超时等待响应时间是 3s,而对方服务有问题,往往都超过了 3s,导致我们拉不到数据。
然后我向对方提了工单,对方竟然让我把超时等待时间调整到 10s,我听得都傻了,正常返回 100ms 的接口,让我设个 10s,这是让我跟着他们的服务一起挂是吗。
遇到这种情况可千万别听对方的,你得想到这就是在拖垮你的应用,你设置的超时等待的时间越久,线程被占用时间就的越长,那其他被的请求不就没线程去处理了嘛,然后请求就堆积了,最终你的应用就全部崩盘了。
也亏对方想得出来这种回复,遇到这种类似的情况,如果你个人拿捏不准,及时找你同事或者领导讨论下,别傻傻的听他的就改了。
你看看所谓的大厂的接口也都这样,总而言之,对待二方、三方接口,要多加个心眼,一定要做好判空、降级等等情况。
我发现有些新同学就很不喜欢判空,因为觉得多写一个 if 很丑陋,啧啧,年轻还是太年轻了,没遭受过毒打!
所以,你们遇到三方最恶心的场景是什么?拿到留言区给大家乐呵乐呵?
好了,今天就扯到这儿~
Netty和JDK源码来看Netty的NIO和JDK的NIO有什么不同
JDK底层提供了NIO实现,在Linux环境会调用内核epoll。
但是Netty通过JNI的方式提供了Native Socket Transport,为什么Netty要自己搞一套NIO呢?
这篇文章带你从jdk的源码和Netty的源码角度来分析为什么Netty要这么做。
JDK源码:openjdk-8u40
Netty源码:netty-4.1
1.先看JDK的NIO实现
Netty和JDK NIO都开始要调用内核的epoll,可以先看我之前写的另一篇打个基础:
从源码和内核角度分析redis和nginx以及java NIO可以支持多大的并发
JDK 1.8 NIO Selector在linux平台上的实现类是sun.nio.ch.EPollSelectorImpl,
这个类通过linux下的epoll系列系统调用实现NIO。
epoll主要包含3个系统调用:epollCreate,epollCtl,epollWait
这篇主要讲epollCtl
epoll_ctl函数用于管理文件描述符的事件集,使用此函数可以注册、修改、删除一个或多个事件
EPollSelectorImpl.java构造函数初始化epoll
以上是关于管你 JDK 还是 Linux,我 Netty 稳坐钓鱼台的主要内容,如果未能解决你的问题,请参考以下文章