1. 程式人生 > >又見CLOSE_WAIT

又見CLOSE_WAIT

電話 fib 很快 註冊 primary 我們 沒有 得出 的確

原文: http://mp.weixin.qq.com/s?__biz=MzI4MjA4ODU0Ng==&mid=402163560&idx=1&sn=5269044286ce1d142cca1b5fed3efab1&3rd=MzA3MDU4NTYzMw==&scene=6#rd

寫的太好了,忍不住就轉了。 2016-01-19 Anthon Liu FangTalk

抱歉遲了一天發文,本來想昨天出貨的。結果又一次被證明我的RPWT,關鍵時刻生產環境掛了...

原來準備寫“分布式系統裏的PAC哲學”的,不過這次處理的生產環境的問題還是很有代表性,同時又想起林濤(對!就是那個有百萬女朋友的林濤!!!)提過的從一個實際案例出發去寫文章的想法,因此就決定從頭來寫一下這次在線支持的來龍去脈。 (文章有點長,但希望整個思路能對您能有啟發)

環境簡述

要說清楚問題,先要簡單說下生產環境的網絡拓撲 (畢竟是個網絡問題對吧)

技術分享

看,挺簡單的對吧,一個OpenResty做SLB承受客戶端請求,後向代理到幾臺應用服務器。由於業務要求,必須要同步調用第三方運營商的接口並返回結果到客戶端。

怎麽“掛”了

深夜接到某妹子電話本該是激動人心的事,但是奈何怎麽都高興不起來,因為來電是來告訴我環境掛了。趕緊問清楚,回答說是一開始響應很慢,後來就徹底拿不到數據了。

好吧,自己摸出手機試下,果然.... 此時夜裏11點。

第一反應 (請註意,這裏開始是我的排錯思路)

從開始打開電腦到電腦點亮的時間裏我已經想好了一個初步的排錯檢查思路, 既然是拿不到數據,那有哪些可能呢?

  • 是不是特例還是所有情況下的數據都獲取不到?

  • 是不是網絡斷了(比如某廠的光纜又斷了?)

  • 是不是服務停了 (Sig 11?OOM?或者core dump)

  • 是不是應用服務器都CPU 100%了?

  • 看看監控系統有沒有報警? (當然得有對吧)

  • 看看DB是不是被人刪了?(進過某旅遊網站的事件後,這總也是一種可能行對吧)

好,因為我們有雲監控,看了下

  • SLB的心跳還活著,排除網絡問題

  • 所有服務器的CPU/Memory/IO都還正常沒有峰值

  • 關鍵進程還在

  • DB也還健在(還好 還好)

開始檢查

既然初步排除上述的問題,那下一步基本就是SSH到服務器上去看情況了。 自然從網絡開始,這裏要想說給很多在做或者即將做在線生產環境支持的小夥伴說的第一句話: “先聽聽操作系統的聲音,讓操作系統來告訴你問題在哪”。 不論是windows和Linux都提供了一堆小工具小命令,在過度依賴安裝第三方工具前請先看看是否操作系統自帶的工具已經不夠支撐你了。

好,第一個檢查就是本機的網絡連接

netstat -anop tcp

結果

技術分享

.......此處省略100多行.....

我擦,close_wait又讓我撞見了. 看了幾臺應用服務器都是上百個close_wait. (加起來有近千個close_wait, 發財了)。

網上有太多文章描述這個東西了所以我不會展開去解釋,就濃縮成以下幾點,大家參考這圖理解

技術分享

  • close_wait 是TCP關閉連接過程中的一個正常狀態

  • close_wait只會發生在關閉鏈接的那一端(各位親,請不要把圖裏的client/server和項目裏的客戶端服務器端混淆)

  • close_wait除非你殺進程,close_wait是不會自動消失的。當然不消失意味著占著資源呢,這裏就是占著FD。

看到這裏基本拿不到數據的原因之一找到了,大量的close_wait. 我之前項目也見過有的開發見到這種情況的直覺反應就是重啟大法,其實也不能算這個做法有錯,畢竟這個時候服務當了,客戶瘋了,夜已深了,你想休息了。 但,這樣真的對嗎?

“停” 先別急著重啟

如果你這時候重啟了,的確立竿見影解決了當前問題,但你卻失去真正解決問題的機會。這就是我想說的第二句話: “保留一下現場,不是所有問題的根源都能從日誌裏找得到的”。 close_wait 絕對就是這類問題,如果你是一位有過類似經歷的開發或者DevOps,你到現在應該有了下面2個疑問:

  1. 為啥一臺機器區區幾百個close_wait就導致不可繼續訪問?不合理啊,一臺機器不是號稱最大可以開到65535個端口嗎?

  2. 為啥明明有多個服務器承載,卻幾乎同時出了close_wait? 又為什麽同時不能再服務?那要SLB還有啥用呢

好,這也是我當時的問題,讓我們繼續往下分析:

  • 先理順出現close_wait的鏈接流向

前面說過close_wait是關閉連接過程中的正常狀態,但是正常情況下close_wait的狀態很快就會轉換所以很難被捕捉到。所以如果你能發現大批量的close_wait基本可以確定是出問題了。那第一個要確定的自然是連接的流向,判斷依據很簡單(還是netstat -anop tcp)

技術分享
命令返回裏有一欄Foreign Address,這個就是代表對方的IP地址,這個時候再結合上面那張TCP的握手圖,我們就知道是哪臺機器和你連接著但是卻主動關閉了連接。

  • 根據項目數據請求流向還原可能的場景

知道了是哪臺IP,那接下來就可以根據項目實際情況還原連接的場景。在我這裏所有的close_wait都發生在和SLB的連接上。因此說明,是SLB主動關閉了連接但是多臺應用服務器都沒有響應ack 導致了close_wait. 只是這樣夠嗎? 明顯不夠。繼續

SLB 作為負載均衡,基本沒有業務邏輯,那它會主動關閉連接的場景有哪些?

  • 進程退出(正常或者非正常)

  • TCP 連接超時

這2個情況很好判斷而且大多數情況下是第二種(我遇見的也是),如果你還記得我文章一開始的環境結構圖,我想基本可以得出以下結論是:

由於調用第三方的請求API太慢而導致SLB這邊請求超時引起的SLB關閉了連接.

那解決方案也很容易就有了:

  • 加大SLB到應用服務器的連接超時時間

  • 在調用第三方的時候采用異步請求

完了嗎? (我怎麽那麽啰嗦。。。)

“再等等” 還有問題沒被回答

我們還有兩個問題沒回答:

  1. 為啥一臺機器區區幾百個close_wait就導致不可繼續訪問?不合理啊,一臺機器不是號稱最大可以開到65535個端口嗎?

  2. 為啥明明有多個服務器承載,卻幾乎同時出了close_wait? 又為什麽同時不能再服務?那要SLB還有啥用呢

是啊,解釋了為什麽出close_wait, 但並不能解釋這2個問題。好吧,既然找到了第一層原因,就先重啟服務讓服務可以用吧。剩下的我們可以用個簡單的原型代碼模擬一下。此時我的目光回到了我們用的Tornado上面來,“當你有問題解釋不了的時候,你還沒有發現正真的問題”

Tornado是一個高性能異步非阻塞的HTTP 服務器(還不明白這個啥意思的可以看 “從韓梅梅和林濤的故事中,學習一下I/O模型 ” 這篇文章,生動!!!),其核心類就是IOLoop,默認都是用HttpServer單進程單線程的方式啟動 (Tornado的process.fork_processes 也支持多進程方式,但官方並不建議)。我們還是通過圖來大概說下

IOLoop幹了啥:

技術分享

  • 維護每個listen socket註冊的fd;

  • 當listen socket可讀時回調_handle_events處理客戶端請求,這裏面的實現就是基於epoll 模型

好,現在我們知道:

  1. Tornado是單進程啟動的服務,所以IOLoop也就一個實例在監聽並輪詢

  2. IOLoop在監聽端口,當對應的fd ready時會回調註冊的handler去處理請求,這裏的handler就是我們寫業務邏輯的RequestHandler

  3. 如果我們啟用了Tornado的 @tornado.gen.coroutine,那理論上一個請求很慢不會影響其他的請求,那一定是代碼什麽地方錯了。

進而查看實現代碼,才真相大白,雖然我們用了 @tornado.gen.coroutine 和yield,但是在向第三方請求時用的是urllib2 庫。這是一個徹頭徹尾的同步庫,人家就不支持AIO(Tornado 有自己AsyncHTTPClient支持AIO).

由此讓我們來總結下原因:

  1. Tornado是單進程啟動的服務,所以IOLoop也就一個實例在監聽並輪詢

  2. Tornado在bind每個socket的時候有默認的鏈接隊列(也叫backlog)為128個

  3. 由於代碼錯誤,我們使用了同步庫urllib2 做第三方請求,導致訪問第三方的時候當前RequestHandler是同步的(yield不起作用),因此當IOLoop回調這個RequestHandler時會等待它返回

  4. 第三方接口真的不快!

最後來回答這兩個問題:

1. 為啥一臺機器區區幾百個close_wait就導致不可繼續訪問?不合理啊,一臺機器不是號稱最大可以開到65535個端口嗎?

[回答]: 由於原因#4和#3所以導致整個IOLoop慢了,進而因為#2 導致很多請求堆積,也就是說很多請求在被真正處理前已經在backlog裏等了一會了。導致了SLB這端的鏈接批量的超時,同時又由於close_wait狀態不會自動消失,導致最終無法再這個端口上創建新的鏈接引起了停止服務

2. 為啥明明有多個服務器承載,卻幾乎同時出了close_wait? 又為什麽同時不能再服務?那要SLB還有啥用呢

[回答]: 有了上一個答案,結合SLB的特性,這個也就很好解釋。這就是所謂的洪水蔓延,當SLB發現下面的一個節點不可用時會吧請求routing到其他可用節點上,導致了其他節點壓力增大。也猶豫相同原因,加速了其他節點出現close_wait.

寫在最後

花了2個小時寫完這文章,意圖並不是在重復去解釋什麽是close_wait. 只是希望藉由一次生產環境的實戰給需要做支持的小夥伴們一個解決問題思路的參照。一次支持也許是快速且短暫的,解決的快不代表你真的就理解了裏面的原因,有時候新的知識恰恰就藏在一次次的環境問題當中。

又見CLOSE_WAIT