管你 JDK 還是 Linux,我 Netty 穩(wěn)坐釣魚(yú)臺(tái)!
你好,我是yes。
今天聊的也是一個(gè)老生常談的問(wèn)題了:JDK Selector 的空輪詢(xún) bug。
今天來(lái)簡(jiǎn)單的扒一下,這玩意大概能追溯到 06 年,并且基于這個(gè) BUG 我們?cè)侔l(fā)散下,看看能給我們什么啟發(fā)。
追溯
最近我不是一直在寫(xiě) Netty 系列嘛,我想談到 Netty ,但凡你在網(wǎng)上看過(guò)相關(guān)資料,那肯定會(huì)提到 JDK NIO 在 Linux 系統(tǒng)下空輪詢(xún)的 bug,就是調(diào)用 Selector.select(timeout),即使沒(méi)事件發(fā)生,也不會(huì)阻塞 timeout 時(shí)間,而是立馬 return,這樣的空輪詢(xún)導(dǎo)致 CPU 100%。
產(chǎn)生這個(gè) bug 大致的原因我講下:連接突然中斷,poll 和 epoll 會(huì)被 POLLHUP 或者 POLLERR 事件喚醒,于是 Selector 就被喚醒了,但是 JDK Selector 一看,沒(méi)事件(JDK只定義了CONNECT、READ、WRITE、ACCEPT這幾個(gè)事件)需要處理啊?
然后就又循環(huán)了....沒(méi)事件要處理啊,然后就又循環(huán)了....沒(méi)事件要處理啊,然后就又循環(huán)了....
如此往復(fù),空輪詢(xún)使得 CPU 100%。
這個(gè) BUG 真算是個(gè)老黃歷了,我查了查 JDK 的 bug 庫(kù),大致 13 年 3 月份之后就沒(méi)提相關(guān)的 bug 了,而且按照官方的說(shuō)法這也和 Linux 版本有關(guān),至今應(yīng)該沒(méi)這問(wèn)題了?(我不確定)。
我們來(lái)回顧一下 bug 庫(kù)的歷史。
我查了下最早提到 Selector(底層用的是 poll 或者 epoll) 在 Linux 不會(huì)阻塞的 BUG 是在 06 年 3 月 24 日。
可以看到,這 Resolved 的日期有點(diǎn)長(zhǎng),隔了一年,也就是 06 提的 07 年才說(shuō)修復(fù)了,不過(guò)當(dāng)天是給了個(gè)解決方案的:
解決方案簡(jiǎn)單粗暴,就是把抽風(fēng)不阻塞的 Selector 刪了,然后創(chuàng)建個(gè)新的,取而代之。(有時(shí)候簡(jiǎn)單粗暴的就是最好的)
我再往后找了找 Selector 的 bug ,發(fā)現(xiàn) 08 年還有 bug(不是說(shuō)修復(fù)了嗎?),并且處理的日期是 13 年!最終結(jié)果是無(wú)法修復(fù),相關(guān)的是 JDK 6 版本。
同 13 年還有類(lèi)似的 bug ,不過(guò)是在 JDK 7 版本上,最終 resolution 是不完整的修復(fù)!
從這個(gè)處理時(shí)間和結(jié)果來(lái)看,我個(gè)人推斷 JDK 對(duì)此 bug 的態(tài)度是消極的,認(rèn)為這是 Linux 自身的 bug 導(dǎo)致的現(xiàn)象(同為程序員習(xí)慣性地甩)。
從 JDK-6670302 的評(píng)價(jià)就可以看出:
大致的意思就是:升級(jí)下 Linux 內(nèi)核版本(2.4版本有這個(gè)問(wèn)題)就好了,2.6 版本發(fā)布已經(jīng) 4 年了都,這個(gè)修復(fù)也沒(méi)啥必要了,需求很小。
這其實(shí)可以理解。
站在 JDK 開(kāi)發(fā)者的角度來(lái)看:我這代碼在 windows 下運(yùn)行的好好的,到你 Linux 就不行了?嗯?我的問(wèn)題?Linux 為什么給我這樣莫名其妙的事件?你中斷喚醒了個(gè)什么玩意?
但是站 Linux 開(kāi)發(fā)者角度來(lái)看就不一樣了:嗯?甩鍋給我?明明是你寫(xiě)代碼沒(méi)考慮到這種特殊情況,擱著跟我甩鍋呢?
那站我們 Java 開(kāi)發(fā)而言:你擱著跟我擱那呢?有 bug 還擱這甩,我管你 JDK 什么情況,你就得給我修!(我相信 JDK 開(kāi)發(fā)者也是這樣看 Linux 開(kāi)發(fā)者的)
哈哈哈,真不真實(shí)?
總之,我個(gè)人覺(jué)得這個(gè) bug 之所以會(huì)被網(wǎng)上的文章拿出來(lái)反復(fù)鞭尸.
一是,因?yàn)?netty 通過(guò)曲線(xiàn)救國(guó)的方式,確實(shí)避免了那個(gè)時(shí)間段部署在 Linux 的應(yīng)用直接用 Java Selector 產(chǎn)生的空輪詢(xún) bug,所以在當(dāng)時(shí)談到 Netty 就值得拿這個(gè)說(shuō)事兒。
二是,天下文章一大抄嘛,懂的都懂。
對(duì)了,雖然我查 bug 庫(kù)發(fā)現(xiàn)后面確實(shí)都沒(méi)再有類(lèi)似的bug,但是網(wǎng)上有文章說(shuō)在 JDK8 還是重現(xiàn)了這個(gè) bug!
鏈接:https://juejin.cn/post/6844903491505242119
嘖嘖,俗話(huà)說(shuō)得好,靠人不如靠己,Netty 就是靠己來(lái)解決這個(gè) bug,也就是上面提到的簡(jiǎn)單粗暴的解決辦法!
Netty :空循環(huán)是吧,我數(shù)數(shù)你循環(huán)幾次,只要到達(dá)一定次數(shù),我就認(rèn)為你產(chǎn)生 bug 了,于是我就重建一個(gè) Selector,廢棄以前那個(gè)已經(jīng)抽風(fēng)了的 Selector!這樣我管你 JDK 還是 Linux 會(huì)不會(huì)處理,我這波就是穩(wěn)坐釣魚(yú)臺(tái)!
這就是 Netty 的解決辦法~所以也不能說(shuō)是 Netty 修復(fù)了 JDK NIO 的 bug ,它只是曲線(xiàn)救國(guó),變相避免了這個(gè) bug 罷了。
這其實(shí)能給我們?nèi)粘5拈_(kāi)發(fā)提供一點(diǎn)思路,有時(shí)候求人不如求己,對(duì)于二方、三方的接口還是報(bào)以質(zhì)疑的態(tài)度去看待,不要過(guò)度的相信他們,特別是三方的接口,一定要做好對(duì)方掛掉或者返回奇怪結(jié)果的準(zhǔn)備。
我之前對(duì)接某大廠(chǎng)的接口,返回值就無(wú)聲無(wú)息的變了,沒(méi)有任何公告和通知,就是那種你認(rèn)為不可能會(huì)變的值。比如一個(gè)返回城市名的接口,正常返回叫杭州市,它莫名其妙變了個(gè)杭州市(常用)。當(dāng)然我只是舉個(gè)例子哈,具體是啥不方便說(shuō)。
還有之前對(duì)接過(guò)另一個(gè)大廠(chǎng)的接口,那時(shí)候他們的服務(wù)幾乎屬于要掛的情況,返回的賊慢,經(jīng)常超時(shí),這種我還是有經(jīng)驗(yàn)的,起初就我設(shè)置了接口超時(shí)等待響應(yīng)時(shí)間是 3s,而對(duì)方服務(wù)有問(wèn)題,往往都超過(guò)了 3s,導(dǎo)致我們拉不到數(shù)據(jù)。
然后我向?qū)Ψ教崃斯危瑢?duì)方竟然讓我把超時(shí)等待時(shí)間調(diào)整到 10s,我聽(tīng)得都傻了,正常返回 100ms 的接口,讓我設(shè)個(gè) 10s,這是讓我跟著他們的服務(wù)一起掛是嗎。
遇到這種情況可千萬(wàn)別聽(tīng)對(duì)方的,你得想到這就是在拖垮你的應(yīng)用,你設(shè)置的超時(shí)等待的時(shí)間越久,線(xiàn)程被占用時(shí)間就的越長(zhǎng),那其他被的請(qǐng)求不就沒(méi)線(xiàn)程去處理了嘛,然后請(qǐng)求就堆積了,最終你的應(yīng)用就全部崩盤(pán)了。
也虧對(duì)方想得出來(lái)這種回復(fù),遇到這種類(lèi)似的情況,如果你個(gè)人拿捏不準(zhǔn),及時(shí)找你同事或者領(lǐng)導(dǎo)討論下,別傻傻的聽(tīng)他的就改了。
你看看所謂的大廠(chǎng)的接口也都這樣,總而言之,對(duì)待二方、三方接口,要多加個(gè)心眼,一定要做好判空、降級(jí)等等情況。
我發(fā)現(xiàn)有些新同學(xué)就很不喜歡判空,因?yàn)橛X(jué)得多寫(xiě)一個(gè) if 很丑陋,嘖嘖,年輕還是太年輕了,沒(méi)遭受過(guò)毒打!
所以,你們遇到三方最?lèi)盒牡膱?chǎng)景是什么?拿到留言區(qū)給大家樂(lè)呵樂(lè)呵?
好了,今天就扯到這兒~
我是yes,從一點(diǎn)點(diǎn)到億點(diǎn)點(diǎn),我們下篇見(jiàn)~