一次HTTP connect-timeout的排查(下)

回顧-上期的遺留問題

  • 疑惑1:按道理全連接隊列滿了,但是客戶端的連接請求是已經(jīng)接收到SYN+ACK了,所以對于客戶端來說該連接已經(jīng)建立了,為啥會報connect timeout ? 應(yīng)該是read timeout或者connect reset 。
  • 疑惑2:全連接隊列滿了,但客戶端的請求認為連接ESTABLISH狀態(tài),可以繼續(xù)發(fā)送數(shù)據(jù)請求。這時候服務(wù)端如何處理?

TCP三次握手

  • client發(fā)送syn給server端。
  • server接收到client的syn,這個時候則會將相關(guān)信息放到半鏈接隊列(syn queue),并且發(fā)送syn+ack發(fā)送給client。
  • client接受到server的syn+ack之后,發(fā)送一個ack給server告訴server我接收到了。這個時候server就會將相關(guān)信息放到全連接隊列(accept queue)中。


    image.png

回顧-TCP三次握手-半連接隊列和全連接隊列

  1. 當 client 通過 connect 向 server 發(fā)出 SYN 包時,client 會維護一個 socket 等待隊列,而 server 會維護一個 SYN 隊列
  2. 此時進入半鏈接的狀態(tài),如果 socket 等待隊列滿了,server 則會丟棄,而 client 也會由此返回 connection time out;只要是 client 沒有收到 SYN+ACK,3s 之后,client 會再次發(fā)送,如果依然沒有收到,9s 之后會繼續(xù)發(fā)送
  3. 半連接 syn 隊列的長度為 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) 決定
  4. 當 server 收到 client 的 SYN 包后,會返回 SYN, ACK 的包加以確認,client 的 TCP 協(xié)議棧會喚醒 socket 等待隊列,發(fā)出 connect 調(diào)用
  5. client 返回 ACK 的包后,server 會進入一個新的叫 accept 的隊列,該隊列的長度為 min(backlog, somaxconn),默認情況下,somaxconn 的值為 128,表示最多有 129 的 ESTAB 的連接等待 accept(),而 backlog 的值則由 int listen(int sockfd, int backlog) 中的第二個參數(shù)指定,listen 里面的 backlog 的含義請看這里。
  6. 當accept 隊列滿了之后,即使 client 繼續(xù)向 server 發(fā)送 ACK 的包,也會不被相應(yīng),此時,server 通過 /proc/sys/net/ipv4/tcp_abort_on_overflow 來決定如何返回,0 表示直接丟丟棄該 ACK,1 表示發(fā)送 RST 通知 client;相應(yīng)的,client 則會分別返回 read timeout 或者 connection reset by peer。
    (在自己的測試驗證過程中,實際情況還會產(chǎn)生:服務(wù)器會隨機的忽略收到的 SYN,建立起來的連接數(shù)可以無限的增加,只不過客戶端會遇到延時以及超時的情況。)

驗證疑惑1:全連接隊列滿了情況怎么樣?

  1. 啟動一個TCP服務(wù)端Socket,只對端口做listen監(jiān)聽,不進行accept操作。(port = 9999, backlog = 2)
  2. 分別調(diào)用4次該端口(采用nc命令: nc ip port的方式)
  3. 服務(wù)器的TCP參數(shù) cat /proc/sys/net/ipv4/tcp_abort_on_overflow = 0
    PS: 注意以下的代碼,servserSocket.accpet()是注釋掉的,代表不會從全連接隊列中獲取請求信息。


    image.png
第1次Socket連接:
13:55:31.379052 IP 10.127.4.74.56720 > 10.16.30.142.9999: Flags [S], seq 1290846985, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:31.379065 IP 10.16.30.142.9999 > 10.127.4.74.56720: Flags [S.], seq 3765322754, ack 1290846986, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:31.406038 IP 10.127.4.74.56720 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
第2次Socket連接:
13:55:33.188479 IP 10.127.4.74.56728 > 10.16.30.142.9999: Flags [S], seq 3220053736, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:33.188494 IP 10.16.30.142.9999 > 10.127.4.74.56728: Flags [S.], seq 3458261601, ack 3220053737, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:33.208279 IP 10.127.4.74.56728 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
第3次Socket連接:
13:55:34.974518 IP 10.127.4.74.56735 > 10.16.30.142.9999: Flags [S], seq 1314208155, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:34.974539 IP 10.16.30.142.9999 > 10.127.4.74.56735: Flags [S.], seq 3817678685, ack 1314208156, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:34.999211 IP 10.127.4.74.56735 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
第4次Socket連接
backlog配置的2,全連接隊列個數(shù)為2+1=3,
第4次開始溢出,/proc/sys/net/ipv4/tcp_abort_on_overflow 配置的為0,則系統(tǒng)會默認重試5次SYN+ACK(重試次數(shù)見: /proc/sys/net/ipv4/tcp_synack_retries)
13:55:36.990148 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [S], seq 713717214, win 8192, options [mss 1360,nop,wscale 8,nop,nop,sackOK], length 0
13:55:36.990170 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:37.011510 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, length 0
13:55:38.389731 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:38.401817 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:55:40.589773 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:40.607068 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:55:44.789766 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:44.804919 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:55:52.789749 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:55:52.804957 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0
13:56:08.789786 IP 10.16.30.142.9999 > 10.127.4.74.56739: Flags [S.], seq 2481376603, ack 713717215, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
13:56:08.847814 IP 10.127.4.74.56739 > 10.16.30.142.9999: Flags [.], ack 1, win 69, options [nop,nop,sack 1 {0:1}], length 0

驗證結(jié)果:抓包的TCP DUMP 顯示,在第4次的時候全連接隊列發(fā)生溢出:在收到客戶端的ack確認時候(時間13:55:37.011510),由于backlog已滿,所以服務(wù)端直接丟棄該ACK確認,再次重試響應(yīng)SYN+ACK(時間13:55:38.389731) 。反復(fù)5次以后,客戶端出現(xiàn)read-timeout。 這個就證實了開章首提的疑惑1確實如我們所想

驗證疑惑1:為啥會出現(xiàn)connect-timeout?

啟動一個http服務(wù),配置tomcat-acceptCount為1,并發(fā)10個線程請求進行,發(fā)現(xiàn)出現(xiàn)了一次connect timeout

#正常的三次握手
18:15:14.975879 IP 10.16.80.136.52633 > 10.16.30.142.19966: Flags [S], seq 17196608, win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
18:15:14.975907 IP 10.16.30.142.19966 > 10.16.80.136.52633: Flags [S.], seq 3788688748, ack 17196609, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
18:15:14.981048 IP 10.16.80.136.52633 > 10.16.30.142.19966: Flags [.], ack 1, win 256, length 0
#由于全隊列滿隨機忽略不響應(yīng)SYN+ACK:
18:15:14.981065 IP 10.16.80.136.52637 > 10.16.30.142.19966: Flags [S], seq 1960972950, win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
為啥會出現(xiàn)這種情況,查找了相關(guān)Linux資料,如下:

man listen中提到每次收到新SYN包,內(nèi)核往SYN隊列追加一個新連接(除非該隊列已滿)。但事實并非如此,net/ipv4/tcp_ipv4.c中tcp_v4_conn_request函數(shù)負責處理SYN包,請看以下代碼:

if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)     
    goto drop;     
  • sk_acceptq_is_full()函數(shù)很好理解,根據(jù)字面意思就可以看出,該函數(shù)是檢查連接隊列是否已滿
  • inet_csk_reqsk_queue_young()函數(shù)返回半連接隊列中未重傳過SYN+ACK段的連接請求塊數(shù)量。 如果連接隊列已滿并且半連接隊列中的連接請求塊中未重傳的數(shù)量大于1,則會跳轉(zhuǎn)到drop處,丟棄SYN包。如果半連接隊列中未重傳的請求塊數(shù)量大于1,則表示未來可能有2個完成的連接,這些新完成的連接要放到連接隊列中,但此時連接隊列已滿。如果在接收到三次握手中最后的ACK后連接隊列中沒有空閑的位置,會忽略接收到的ACK包,連接建立會推遲,所以此時最好丟掉部分新的連接請求,空出資源以完成正在進行的連接建立過程。還要注意,這個判斷并沒有考慮半連接隊列是否已滿的問題。從這里可以看出,即使開啟了SYN cookies機制并不意味著一定可以完成連接的建立。
  • 參考資料:https://blog.csdn.net/justlinux2010/article/details/12619761
    自己按照理解畫了一個簡圖,如下:
    image.png

結(jié)論:所以正是由于inet_csk_reqsk_queue_young()函數(shù)機制的存在,所以在極端情況下,全連接隊列滿了,也會引起客戶端出現(xiàn)connect-timeout的情況

驗證疑惑2:全連接隊列滿了,客戶端繼續(xù)發(fā)送數(shù)據(jù)請求。這時候服務(wù)端如何處理?

這個其實就是經(jīng)典的client fooling問題
由于全隊列滿的情況對于客戶端來說是透明的,因為請求方已經(jīng)收到了服務(wù)端的SYN+ACK,狀態(tài)已經(jīng)變?yōu)镋STABLISH,客戶端認為連接是建立的,所以繼續(xù)發(fā)消息,那么此種情況下(全隊列滿,服務(wù)端沒有對應(yīng)連接,客戶端認為連接建立成功),客戶端仍發(fā)消息會產(chǎn)生什情況?,試驗如下:


image.png

結(jié)論:可以發(fā)現(xiàn)客戶端發(fā)送的數(shù)據(jù)包如果沒有收到服務(wù)端的ack, 客戶端會自動重試幾次,如果仍沒收到ack,則客戶端發(fā)送RST指令斷開已服務(wù)端的連接。

小結(jié)

TCP握手的全連接隊列、半連接隊列溢出這種問題很容易被大家忽視,但是又很關(guān)鍵。 一旦溢出,從各項指標比如CPU, GC情況, 線程狀態(tài)看都很正常,但是壓力上不去,從服務(wù)端上看其他的請求的響應(yīng)時間又很快(見上篇的pinpoint監(jiān)控圖)。
希望本文能夠幫助大家理解TCP 全連接、半連接隊列的概念,以及出現(xiàn)問題如何排查。
如文章有理解不足之處,也請大伙幫忙指出,感謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容

  • 《大佛普拉斯》是由黃信堯執(zhí)導,葉如芬、鐘孟宏監(jiān)制的劇情片。由陳竹升、莊益增、戴立忍、納豆、張少懷等主演。該片于20...
    Alina娜閱讀 1,118評論 0 4
  • 金河的手上戴著一串精致的佛珠, 佛珠一共有15顆,小葉紫檀材質(zhì),每一顆佛珠的花紋都展現(xiàn)出別樣的樣貌,每次感覺緊張或...
    艷南天閱讀 339評論 0 0
  • 如果提前了解,你會擁有的人生,不知道是否還會有勇氣前來。 2018,即將過去,這一年很特別,這一年充滿變化與挑戰(zhàn),...
    妙曼風景閱讀 1,879評論 1 6