線(xiàn)上四臺機器同一時(shí)間全部 OOM,到底發(fā)生了什么?
常見(jiàn)問(wèn)題
發(fā)布者:cya
2019-12-25 08:40
訪(fǎng)問(wèn)量:128
作者:碼海
鏈接:https://mp.weixin.qq.com/s/B1EplSO2hTEoDAcPNtsGwA
昨天晚上突然短信收到 APM (即 Application Performance Management 的簡(jiǎn)稱(chēng),我們內部自己搭建了這樣一套系統來(lái)對應用的性能、可靠性進(jìn)行線(xiàn)上的監控和預警的一種機制)大量告警 畫(huà)外音: 監控是一種非常重要的發(fā)現問(wèn)題的手段,沒(méi)有的話(huà)一定要及時(shí)建立哦緊接著(zhù)運維打來(lái)電話(huà)告知線(xiàn)上部署的四臺機器全部 OOM (out of memory, 內存不足),服務(wù)全部不可用,趕緊查看問(wèn)題!
首先運維先重啟了機器,保證線(xiàn)上服務(wù)可用,然后再仔細地看了下線(xiàn)上的日志,確實(shí)是因為 OOM 導致服務(wù)不可用第一時(shí)間想到 dump 當時(shí)的內存狀態(tài),但由于為了讓線(xiàn)上盡快恢復服務(wù),運維重啟了機器,導致無(wú)法 dump 出事發(fā)時(shí)的內存。所以我又看了下我們 APM 中對 JVM 的監控圖表 畫(huà)外音:一種方式不行,嘗試另外的角度切入!再次強調,監控非常重要!完善的監控能還原當時(shí)的事發(fā)現場(chǎng),方便定位問(wèn)題。不看不知道,一看嚇一跳,從 16:00 開(kāi)始應用中創(chuàng )建的線(xiàn)程居然每時(shí)每刻都在上升,一直到 3w 左右,重啟后(藍色箭頭),線(xiàn)程也一直在不斷增長(cháng)),正常情況下的線(xiàn)程數是多少呢,600!問(wèn)題找到了,應該是在下午 16:00 左右發(fā)了一段有問(wèn)題的代碼,導致線(xiàn)程一直在創(chuàng )建,且創(chuàng )建的線(xiàn)程一直未消亡!查看發(fā)布記錄,發(fā)現發(fā)布記錄只有這么一段可疑的代碼 diff:在 HttpClient 初始化的時(shí)候額外加了一個(gè) evictExpiredConnections 配置問(wèn)題定位了,應該是就是這個(gè)配置導致的!(線(xiàn)程上升的時(shí)間點(diǎn)和發(fā)布時(shí)間點(diǎn)完全吻合!),于是先把這個(gè)新加的配置給干掉上線(xiàn),上線(xiàn)之后線(xiàn)程數果然恢復正常了。那 evictExpiredConnections 做了什么導致線(xiàn)程數每時(shí)每刻在上升呢?這個(gè)配置又是為了解決什么問(wèn)題而加上的呢?于是找到了相關(guān)同事來(lái)了解加這個(gè)配置的前因后果最近線(xiàn)上出現不少 NoHttpResponseException 的異常, 而上面那個(gè)配置就是為了解決這個(gè)異常而添加的,那是什么導致了這個(gè)異常呢?在說(shuō)這個(gè)問(wèn)題之前我們得先了解一下 http 的 keep-alive 機制。可以看到每個(gè) TCP 連接都要經(jīng)過(guò)三次握手建立連接后才能發(fā)送數據,要經(jīng)過(guò)四次揮手才能斷開(kāi)連接,如果每個(gè) TCP 連接在 server 返回 response 后都立馬斷開(kāi),則發(fā)起多個(gè) HTTP 請求就要多次創(chuàng )建斷開(kāi) TCP, 這在 Http 請求很多的情況下無(wú)疑是很耗性能的, 如果在 server 返回 response 不立即斷開(kāi) TCP 鏈接,而是復用這條鏈接進(jìn)行下一次的 Http 請求,則無(wú)形中省略了很多創(chuàng )建 / 斷開(kāi) TCP 的開(kāi)銷(xiāo),性能上無(wú)疑會(huì )有很大提升。如下圖示,左圖是不復用 TCP 發(fā)起多個(gè) HTTP 請求的情況,右圖是復用 TCP 的情況,可以看到發(fā)起三次 HTTP 請求,復用 TCP 的話(huà)可以省去兩次建立 / 斷開(kāi) TCP 的開(kāi)銷(xiāo),理論上一個(gè)應用只要開(kāi)啟一個(gè) TCP 連接即可,其他 HTTP 請求都可以復用這個(gè) TCP 連接,這樣 n 次 HTTP 請求可以省去 n-1 次創(chuàng )建 / 斷開(kāi) TCP 的開(kāi)銷(xiāo)。這對性能的提升無(wú)疑是有巨大的幫助。回過(guò)頭來(lái)看 keep-alive (又稱(chēng)持久連接,連接復用)做的就是復用連接, 保證連接持久有效。畫(huà)外音: Http 1.1 之后 keep-alive 默認支持并開(kāi)啟,目前大部分網(wǎng)站都用了 http 1.1 了,也就是說(shuō)大部分都默認支持連接復用了天下沒(méi)有免費的午餐 ,雖然 keep-alive 省去了很多不必要的握手/揮手操作,但由于連接長(cháng)期?;?,如果一直沒(méi)有 http 請求的話(huà),這條連接也就長(cháng)期閑著(zhù)了,會(huì )占用系統資源,有時(shí)反而會(huì )比復用連接帶來(lái)更大的性能消耗。所以我們一般會(huì )為 keep-alive 設置一個(gè) timeout, 這樣如果連接在設置的 timeout 時(shí)間內一直處于空閑狀態(tài)(未發(fā)生任何數據傳輸),經(jīng)過(guò) timeout 時(shí)間后,連接就會(huì )釋放,就能節省系統開(kāi)銷(xiāo)。看起來(lái)給 keep-alive 加 timeout 是完美了,但是又引入了新的問(wèn)題(一波已平,一波又起!),考慮如下情況:如果服務(wù)端關(guān)閉連接,發(fā)送 FIN 包(注:在設置的 timeout 時(shí)間內服務(wù)端如果一直未收到客戶(hù)端的請求,服務(wù)端會(huì )主動(dòng)發(fā)起帶 FIN 標志的請求以斷開(kāi)連接釋放資源),在這個(gè) FIN 包發(fā)送但是還未到達客戶(hù)端期間,客戶(hù)端如果繼續復用這個(gè) TCP 連接發(fā)送 HTTP 請求報文的話(huà),服務(wù)端會(huì )因為在四次揮手期間不接收報文而發(fā)送 RST 報文給客戶(hù)端,客戶(hù)端收到 RST 報文就會(huì )提示異常 (即 NoHttpResponseException)我們再用流程圖仔細梳理一下上述這種產(chǎn)生 NoHttpResponseException 的原因,這樣能看得更明白一些費了這么大的功夫,我們終于知道了產(chǎn)生 NoHttpResponseException 的原因,那該怎么解決呢,有兩種策略
- 重試,收到異常后,重試一兩次,由于重試后客戶(hù)端會(huì )用有效的連接去請求,所以可以避免這種情況,不過(guò)一次要注意重試次數,避免引起雪崩!
- 設置一個(gè)定時(shí)線(xiàn)程,定時(shí)清理上述的閑置連接,可以將這個(gè)定時(shí)時(shí)間設置為 keep alive timeout 時(shí)間的一半以保證超時(shí)前回收。
evictExpiredConnections 就是用的上述第二種策略,來(lái)看下官方用法使用說(shuō)明
Makes this instance of HttpClient proactively evict idle connections from the
connection pool using a background thread.
調用這個(gè)方法只會(huì )產(chǎn)生一個(gè)定時(shí)線(xiàn)程,那為啥應用中線(xiàn)程會(huì )一直增加呢,因為我們對每一個(gè)請求都創(chuàng )建了一個(gè) HttpClient! 這樣由于創(chuàng )建每一個(gè) HttpClient 實(shí)例j時(shí)都會(huì )調用 evictExpiredConnections ,導致有多少請求就會(huì )創(chuàng )建多少個(gè)定時(shí)線(xiàn)程!還有一個(gè)問(wèn)題,為啥線(xiàn)上四臺機器幾乎同一時(shí)間點(diǎn)全掛呢?因為由于負載均衡,這四臺機器的權重是一樣的,硬件配置也一樣,收到的請求其實(shí)也可以認為是差不多的,這樣這四臺機器由于創(chuàng )建 HttpClient 而生成的后臺線(xiàn)程也在同一時(shí)間達到最高點(diǎn),然后同時(shí) OOM。所以針對以上提到的問(wèn)題,我們首先把 HttpClient 改成了單例,這樣保證服務(wù)啟動(dòng)后只會(huì )有一個(gè)定時(shí)清理線(xiàn)程,另外我們也讓運維針對應用的線(xiàn)程數做了監控,如果超過(guò)某個(gè)閾值直接告警,這樣能在應用 OOM 前及時(shí)發(fā)現處理。畫(huà)外音:再次強調,監控相當重要,能把問(wèn)題扼殺在搖籃里!本文通過(guò)線(xiàn)上四臺機器同時(shí) OOM 的現象,來(lái)詳細剖析定位了產(chǎn)生問(wèn)題的原因,可以看到我們在應用某個(gè)庫時(shí)首先要對這個(gè)庫要有充分的了解(上述 HttpClient 的創(chuàng )建不用單例顯然是個(gè)問(wèn)題),其次必要的網(wǎng)絡(luò )知識還是需要的,所以要成為一個(gè)合格的程序員,不光對語(yǔ)言本身有所了解,還要對網(wǎng)絡(luò ),數據庫等也要有所涉獵,這些對排查問(wèn)題以及性能調優(yōu)等會(huì )有非常大的幫助,再次,完善的監控非常重要,通過(guò)觸發(fā)某個(gè)閾值提前告警,可以將問(wèn)題扼殺在搖籃里!