一、問題
今天有個小夥伴跑過來告訴我有個奇怪的問題需要協助下,問題确實也很奇怪。用戶端調用RT比較高并伴随着間歇性異常Connection reset出現,而服務端CPU 、線程棧等看起來貌似都很正常,而且服務端的RT很短。
這裡先說下結果:
因為TCP全連接配接隊列太小導緻的連接配接被丢棄,因為項目使用Spring Boot 内置的Tomcat,而預設accept-count是100,而這個參數在這裡就代表了全連接配接隊列大小。是以在請求波峰的時候全連接配接隊列被打滿導緻有連接配接丢棄。是以我們調整server.tomcat.accept-count這個參數解決了問題。
二、半連接配接隊列和全連接配接隊列
好了為了知其然知其是以然,從異常資訊來看可能是TCP連接配接出現了什麼問題,其中重點就是半連接配接隊列和全連接配接隊列。下面就來看看什麼是TCP 半連接配接隊列和全連接配接隊列,其為什麼會出現這種奇怪的現象。
1、TCP 三次握手流程和隊列
TCP三向交握時,Linux核心會維護兩個隊列:
- 半連接配接隊列,被稱為SYN隊列
- 全連接配接隊列,被稱為 accept隊列
老生常談,還要從大家都熟悉TCP三向交握說起,來看一張圖:

1、用戶端發送SYN包,并進入SYN_SENT狀态
2、服務端接收到資料包将相關資訊放入半連接配接隊列(SYN 隊列),并傳回SYC+ACK包給用戶端。
3、服務端接收用戶端ACK資料包,這時如果全連接配接隊列(accept 隊列)沒滿,就會從半連接配接隊列裡面将資料取出來放入全連接配接隊列,等待應用使用,當隊列已滿就會跟據tcp_abort_on_overflow配置執行政策。
這裡半連接配接隊列(SYN 隊列)和全連接配接隊列(accept 隊列)就是重點了。
2、全連接配接隊列檢視
當查詢問題的時候,我們就需要檢視全連接配接隊列的狀态。服務端我們可以使用 ss 指令進行檢視,ss 指令擷取資料又分為LISTEN 狀态,和非LISTEN 狀态。
LISTEN 狀态下資料:
# -l 顯示正在Listener 的socket
# -n 不解析服務名稱
# -t 隻顯示tcp
# Recv-Q 完成三次握手并等待服務端 accept() 的 TCP 全連接配接總數,
# Send-Q 全連接配接隊列大小
[root@server ~]# ss -lnt |grep 6080
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 100 :::6080 :::*
非LISTEN 狀态下資料:
# Recv-Q 已收到但未被應用程序讀取的位元組數
# Send-Q 已發送但未收到确認的位元組數
[root@server ~]# ss -nt |grep 6080
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 433 :::6080 :::*
3、全連接配接隊列溢出
當有大量請求進入,如果TCP全連接配接隊列過小的話就會出現全連接配接隊列溢出,當出現全連接配接隊列溢出現象的時候,後續的請求就會被丢棄,就會出現服務請求數量上不去的現象。
前面提到在TCP三向交握的最後一步,當全連接配接隊列已滿就會根據tcp_abort_on_overflow政策進行處理。Linux 可通過 /proc/sys/net/ipv4/tcp_abort_on_overflow 進行配置。
- 當tcp_abort_on_overflow=0,服務accept 隊列滿了,用戶端發來ack,服務端直接丢棄該ACK,此時服務端處于【syn_rcvd】的狀态,用戶端處于【established】的狀态。在該狀态下會有一個定時器重傳服務端 SYN/ACK 給用戶端(不超過 /proc/sys/net/ipv4/tcp_synack_retries 指定的次數,Linux下預設5)。超過後,伺服器不在重傳,後續也不會有任何動作。如果此時用戶端發送資料過來,服務端會傳回RST。(這也就是我們的異常原因了)
- 當tcp_abort_on_overflow=1,服務端accept隊列滿了,用戶端發來ack,服務端直接傳回RST通知client,表示廢掉這個握手過程和這個連接配接,client會報connection reset by peer。
1). 當全連接配接隊列溢出時,有哪些名額可以說明呢,我們又有哪些有效的查詢手段呢?
指令查詢,我們可以根據TCP 的握手特性來看:
[root@server ~] netstat -s | egrep "listen|LISTEN"
7102 times the listen queue of a socket overflowed 全連接配接隊列溢出的次數
7102 SYNs to LISTEN sockets ignored 表示半連接配接隊列溢出次數
710 2times表示全連接配接隊列溢出的次數,隔幾秒查詢一次,如果這個數字一直在遞增,說明全連接配接隊列出現了溢出的狀态
2). 配置全連接配接隊列和半連接配接隊列?
全連接配接隊列大小取決于backlog 和somaxconn 的最小值,也就是 min(backlog,somaxconn)
- somaxconn 是Linux核心參數,預設128,可通過/proc/sys/net/core/somaxconn進行配置
- backlog是 listen(int sockfd,int backlog)函數中的參數backlog,Tomcat 預設100,Nginx 預設511.
半連接配接隊列的長度可以通過 /proc/sys/net/ipv4/tcp_max_syn_backlog來設定.os層面,隻能設一個,由所有程式共享)
3). 檢視半連接配接狀态
半連接配接,也就是服務端處于SYN_RECV狀态的TCP連接配接,這種狀态的都在半連接配接隊列,是以可以使用如下指令進行計算:
#檢視半連接配接隊列
[root@server ~] netstat -natp | grep SYN_RECV | wc -l
233 #表示半連接配接狀态的TCP連接配接有233個
三、總結
通過以上的知識點可以定位到這次事件的原因了,因為Spring Boot tomcat 預設的配置導緻應用在啟動時全連接配接隊列隻有預設的100,在流量激增的情況下導緻全連接配接隊列打滿出現了第三次握手資料包被丢棄的現象。
是以總結下:
- 1、TCP三向交握時,Linux維護了全連接配接和半連接配接兩個隊列
- 2、在全連接配接隊列滿的時候丢棄政策根據tcp_abort_on_overflow的配置執行
- 3、全連接配接隊列大小會取Linux系統配置和應用配置中的最小值
- 4、Linux 中的backlog 就是我們所說的全連接配接隊列大小
- 5、應用部署時記得檢查全連接配接隊列是否正确配置
backlog配置
- Tomcat AbstractEndpoint預設參數是100,如果使用獨立Tomcat配置了 server.xml,其實 connector 中 acceptCount 最終是 backlog的值。而使用Spring Boot内置Tomcat記得配置server.tomcat.accept-count參數,否則預設值就是
- Nginx 配置 server{ listen 8080 default_server backlog=512}
- Redis 配置redis.conf檔案 tcp-backlog 511參數
專注于分享技術幹貨文章的地方,内容涵蓋java基礎、中間件、分布式、apm監控方案、異常問題定位等技術棧。多年基礎架構經驗,擅長基礎元件研發,分布式監控系統,熱愛技術,熱愛分享