從一個線上伺服器警告談談backlog
緣起
雙十一如期而至,此時的我因為在處理客戶的一個問題已經陷入了忙碌。突然,不斷接到駐場實施發來的反饋,都是相同的反饋——"客戶端操作緩慢"。
我現在負責的伺服器是一臺介面伺服器,所有的賣家都要通過這臺伺服器連線到自己的資料庫上,不得小覷。於是我立馬放下手頭的話,打開了我事先安裝好的伺服器監控軟體(netdata),便看到了下面的警告:
其實,作為一個伺服器端新手,我並不知道什麼意思。但是客戶反饋慢啊!屁股一想也知道是因為伺服器資源不夠了嘛!於是,我立馬把一小部分客戶的配置切換到了另外一臺準備好的伺服器上,危機化解了,我擦了一把汗。
所以,什麼是tcp accept queue?
通過搜尋這個警告,找到了一個叫backlog的關鍵字,網上很多辦法是增大這個值,因為當時雙十一已經過去了,客戶已經沒有那麼多單子發貨了。所以,也沒有驗證這個方法是否對不對。但backlog的迷還一直留在我的腦海裡。
http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html
直到有一天,我遇到了上面的一篇文章,才對backlog有較為具體的認識。我簡單的根據文中的意思介紹下backlog引數。
要搞清楚這個問題,我們必須要先了解下tcp建立連結的三次握手,從圖中可以看出,第一次握手是客戶端給伺服器傳送syn,此時伺服器進入SYN_RECV狀態(也叫未完成連結,對應圖中的SYN_RCVD),那麼既然是一個伺服器,所以接受這樣的連結就不是一個,是很多,所以會有一個佇列去記錄這樣的未完成連結,暫且稱為syn佇列。
第一次握手完成後,伺服器會給客戶端傳送 SYN+ACK,客戶端收到後,這個連結在客戶端方面已經是ESTABLISHED狀態了。客戶端接著會給伺服器傳送ACK,當伺服器收到這個ACK的時候,伺服器上的該連結由SYN_RECV 狀態到ESTABLISHED狀態,此時它進入了一個新佇列,這裡叫它accept佇列。顧明思議就是供accept來消耗的請求。
兩個佇列的長度限制由什麼引數決定?
經過上面分析,我們知道伺服器端處理的請求連結是有兩個佇列來控制,syn佇列(對應圖中的syns queue)是等待客戶端回覆ack的連結,叫做未完成連結或者半連線,而完成三次握手後,該連結進入accept佇列,那麼這兩個佇列的長度是多少呢?
由上圖可以看出,syn佇列就是由 /proc/sys/net/ipv4/tcp_max_syn_backlog引數來控制。處於這個佇列上的連結需要等待的全雙工的通訊時間,所以如果這個引數開的太小,可能對於一些併發性比較高的http伺服器來說,容易成為瓶頸。因為這個syns佇列滿了以後,新的請求就因為佇列滿的緣故而被忽略。
而accept函式是有兩個引數共同決定的,分別是系統函式listen函式的backlog引數以及核心引數somaxconn,這兩者會取最小值,如果應用伺服器能夠很快的從accept佇列中取出任務並執行,這個佇列並不會成為負擔,但如果每個請求都要耗費很長時間處理的話,那麼伺服器處理執行緒將會被打滿,這個時候accept佇列就會被佔滿。
accept佇列被佔滿的時候,會引入一個新的問題,那就是此時如果未完成連結接收到客戶端的ack完成三次握手,需要轉入accept佇列,此時該怎麼辦?此時,如果
tcp_abort_on_overflow引數為0的話,此時伺服器什麼也不做,忽略掉這次申請轉到accept佇列的請求。那麼這個請求就仍然留在了syns佇列,伺服器隔斷時間會再次傳送syn+ack給客戶端,也就是說會重複走第二部握手。所以,這裡accept佇列也應該設定大一些,這樣可以省去伺服器重複發請求的消耗。
關於syn佇列長度的限制,大部分文獻說的是由 tcp_max_syn_backlog 引數控制,但也有部分部落格有更詳細的說明,認為syn佇列不僅僅受控制與該引數,帶著這個疑問,我也看了些原始碼:
以上linux原始碼中,max_qlen_log中代表syn佇列的最大長度,sysctl_max_syn_backlog 即是tcp_max_syn_backlog引數的值。第一行的nr_table_entries 是計算這個最大值的因子,它是作為一個引數傳入進來的,它是傳入的backlog引數,上面已經介紹過backlog引數是核心引數somaxconn和listen函式中傳入的backlog的最小值,而通過第一行原始碼我們可以看出來,這裡syn佇列的最大值是取backlog 和 tcp_max_syn_backlog的最小值,因此得到以下結論。
syn queue length = min(somaxconn,backlog,tcp_max_syn_backlog)
accept queue length = min(somaxconn,backlog)
實驗驗證
口說無憑,下面進行一個實驗,來按照上面的理論分析下。
準備
OS 雲機 1核1G的入門機
伺服器是nginx + php-fpm
伺服器監控軟體netdata
ab壓測工具
step 1 : 引數設定
經過上面對兩個佇列的長度限制分析知道somaxconn引數對兩個佇列有影響,於是設定somaxconn的值為8,其他backlog引數均為1024。
net.ipv4.tcp_max_syn_backlog = 1024
net.core.somaxconn = 8
net.ipv4.tcp_syncookies = 0
為了避免其他因素的影響我們的測試php檔案中只有一句
ehco hello world;
step2: 開始壓測
ab 壓測 工具併發512個請求,請求總數5120個,請求傳送完,發現netdata已經收到了報錯資訊如下:
step3:得到結果
此時,可以看到兩個佇列都已經開始drop連結了。
step4:將somaxconn引數為1024,再次相同條件壓測發現已經沒有警告提示了
結論
通過合理的引數設定,是可以提高伺服器報警的門檻,將引數調整到伺服器能夠承受的最大值,確實可以在一定程度上提高伺服器的容量。一方面,佇列增大後,可以暫時容納這些佇列,供應用程式消耗。如果佇列設定過小,那麼新來的連結就會被忽略,客戶端還會進行重試,另一方面如果accept很快被充滿的話,syn佇列會被阻塞,而且syn佇列上的連結仍要去消耗資源對客戶端重新發送syn+ack,導致一個惡性迴圈。
但這不意味著儘量調整引數就可以萬事大吉了,在一些突發訪問的網路中,調大引數可以緩解網路情況,但此時客戶端訪問伺服器肯定不會很快,因為伺服器已經陷入了繁忙狀態。如果伺服器一直處於報警狀態,即連結不是突發性的,而是長時間處於一個比較高的數量,那麼就需要進行優化了。優化分為兩方面,第一方面應用程式的優化,即讓每個連結的處理速度加快,這樣執行緒可以快速的釋放,accept消耗加快,伺服器處理速度加快。另外一方面,就是擴容伺服器,通過負載均衡水平擴充套件計算資源,讓連結分配到不同機器上快速消耗。