1. 程式人生 > >記一次壓測問題定位:connection reset by peer,TCP三次握手後服務端傳送RST

記一次壓測問題定位:connection reset by peer,TCP三次握手後服務端傳送RST

問題描述

    這兩天用Go做一個比較簡單的task:後端有HTTPServer和TCPServer。客戶端通過http接入到HTTPServer,HTTPServer通過RPC將請求傳送到TCPServer,所有的業務邏輯都由TCPServer處理。

    壓測:自己的mac電腦(CPU:Intel i7, 4核,2.7GHz。記憶體:16G),硬體夠用。客戶端用Go編寫,1個goruntine啟動一個HTTPClient往HTTPServer傳送http請求。每個HTTPClient限定為一個HTTP長連結。

    問題:壓到400個HTTPClient,出現一些錯誤提示“read: connection reset by peer”。

問題定位以及原因

“connection reset by peer”的含義是往對端寫資料的時候,對端提示已經關閉了連線。一般往一個已經被關閉的socket寫會提示這個錯誤。但是通過log分析,服務端沒有應用層面的close,客戶端也沒有應用層面的write。抓包發現客戶端建立TCP完成3次握手後,服務端立刻就回了RST。如下圖:


這個抓包很好的反應了壓測中的現象:錯誤提示connection reset by peer,但是應用層並沒有任何的讀寫,TCP三次握手後服務端直接通過RST關閉了連線。RST的情況見的多,這種情況著實沒有遇到過。最後N次baidu google,終於找到答案。

TCP三次握手後服務端直接RST的真相

核心中處理TCP連線時維護著兩個佇列:SYN佇列和ACCEPT佇列,如上圖所示,服務端在建立連線過程中核心的處理過程如下:

  • (1)客戶端使用connect呼叫向服務端發起TCP連線,核心將此連線資訊放入SYN佇列,返回SYN-ACK
  • (2)服務端核心收到客戶端的ACK後,將此連線從SYN佇列中取出,放入ACCEPT佇列
  • (3)服務端使用accept呼叫將連線從ACCEPT佇列中取出

上述抓包說明,3次握手已經完成。但是應用層accept並沒有返回,說明問題出在ACCEPT佇列中。那麼什麼情況下,核心準確的說應該是TCP協議棧會在三次握手完成後發RST呢?原因就是ACCEPT佇列滿了,上述(2)中,服務端核心收到客戶端的ACK後將連線放入ACCEPT佇列失敗,就有可能回RST拒絕連線。

進一步來看Linux協議棧的一些邏輯:SYN佇列和ACCEPT佇列的長度是有限制的,SYN佇列長度由核心引數tcp_max_syn_backlog決定,ACCEPT佇列長度可以在呼叫listen(backlog)通過backlog,但總最大值受到核心引數somaxconn(/proc/sys/net/core/somaxconn)限制。若SYN佇列滿了,新的SYN包會被直接丟棄。若ACCEPT佇列滿了,建立成功的連線不會從SYN佇列中移除,同時也不會拒絕新的連線,這會加劇SYN佇列的增長,最終會導致SYN佇列的溢位。當ACCEPT佇列溢位之後,只要開啟tcp_abort_on_flow核心引數(預設為0,關閉),建立連線後直接回RST,拒絕連線(可以通過/proc/net/netstat中ListenOverflows和ListenDrops檢視拒絕的數目)

所以真相找到了:就是ACCEPT佇列溢位了導致TCP三次握手後服務端傳送RST

回到我的壓測環境,mac電腦的核心是unix,所以以上的一些引數別說調整了,有些找都找不到在哪。但是somaxconn這個倒是找到了:


而我壓測的時候起400個goruntine,同時跟服務端建立HTTP連線,可能導致了服務端的ACCEPT佇列溢位。這裡之所以用可能,是因為並沒有找到證據,只是理論上分析。但是驗證這個問題簡單:修改一下核心引數somaxconn。但是不想在mac電腦上搞了,於是將建立HTTP連線的速度放慢,20ms一個。果然,錯誤消失了,400個、800個、2000個client,都OK。

總結

  1. 理論還是很重要的。特別是遇到網路層面的疑難雜症,一定要結合理論,根據現象去推導。
  2. 沒有頭緒的時候,就抓包吧。
  3. 不要在mac上搞壓測這種事,測試儘量跟線上保持一樣的環境

參考