偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

從一次線上問(wèn)題說(shuō)起,詳解 TCP 半連接隊(duì)列、全連接隊(duì)列

網(wǎng)絡(luò) 通信技術(shù)
某次大促值班 ing,對(duì)系統(tǒng)穩(wěn)定性有著充分信心、心態(tài)穩(wěn)如老狗的筆者突然收到上游反饋有萬(wàn)分幾的概率請(qǐng)求我們 endpoint 會(huì)出現(xiàn) Connection timeout 。

[[431611]]

本文轉(zhuǎn)載自微信公眾號(hào)「云巔論劍」,作者黃剛。轉(zhuǎn)載本文請(qǐng)聯(lián)系云巔論劍公眾號(hào)。

前言

某次大促值班 ing,對(duì)系統(tǒng)穩(wěn)定性有著充分信心、心態(tài)穩(wěn)如老狗的筆者突然收到上游反饋有萬(wàn)分幾的概率請(qǐng)求我們 endpoint 會(huì)出現(xiàn) Connection timeout 。此時(shí)系統(tǒng)側(cè)的 apiserver 集群水位在 40%,離極限水位還有著很大的距離,當(dāng)時(shí)通過(guò)緊急擴(kuò)容 apiserver 集群后錯(cuò)誤率降為了 0。事后進(jìn)行了詳細(xì)的問(wèn)題排查,定位分析到問(wèn)題根因出現(xiàn)在系統(tǒng)連接隊(duì)列被打滿導(dǎo)致,之前筆者對(duì) TCP 半連接隊(duì)列、全連接隊(duì)列不太了解,只依稀記得 《TCP/IP 詳解》中好像有好像提到過(guò)這兩個(gè)名詞。

目前網(wǎng)上相關(guān)資料都比較零散,并且有些是過(guò)時(shí)或錯(cuò)誤的結(jié)論,筆者在調(diào)查問(wèn)題時(shí)踩了很多坑。痛定思痛,筆者查閱了大量資料并做了眾多實(shí)驗(yàn)進(jìn)行驗(yàn)證,梳理了這篇 TCP 半連接隊(duì)列、全連接詳解,當(dāng)你細(xì)心閱讀完這篇文章后相信你可以對(duì) TCP 半連接隊(duì)列、全連接隊(duì)列有更充分的認(rèn)識(shí)。

本篇文章將結(jié)合理論知識(shí)、內(nèi)核代碼、操作實(shí)驗(yàn)為你呈現(xiàn)如下內(nèi)容:

  • 半連接隊(duì)列、全連接隊(duì)列介紹
  • 常用命令介紹
  • 全連接隊(duì)列實(shí)戰(zhàn) —— 最大長(zhǎng)度控制、全連接隊(duì)列溢出實(shí)驗(yàn)、實(shí)驗(yàn)結(jié)果分析...
  • 半連接隊(duì)列實(shí)戰(zhàn) —— 最大長(zhǎng)度控制、半連接隊(duì)列溢出實(shí)驗(yàn)、實(shí)驗(yàn)結(jié)果分析...
  • ...

半連接隊(duì)列、全連接隊(duì)列

在 TCP 三次握手的過(guò)程中,Linux 內(nèi)核會(huì)維護(hù)兩個(gè)隊(duì)列,分別是:

  • 半連接隊(duì)列 (SYN Queue)
  • 全連接隊(duì)列 (Accept Queue)

正常的 TCP 三次握手過(guò)程:

1、Client 端向 Server 端發(fā)送 SYN 發(fā)起握手,Client 端進(jìn)入 SYN_SENT 狀態(tài)

2、Server 端收到 Client 端的 SYN 請(qǐng)求后,Server 端進(jìn)入 SYN_RECV 狀態(tài),此時(shí)內(nèi)核會(huì)將連接存儲(chǔ)到半連接隊(duì)列(SYN Queue),并向 Client 端回復(fù) SYN+ACK

3、Client 端收到 Server 端的 SYN+ACK 后,Client 端回復(fù) ACK 并進(jìn)入 ESTABLISHED 狀態(tài)

4、Server 端收到 Client 端的 ACK 后,內(nèi)核將連接從半連接隊(duì)列(SYN Queue)中取出,添加到全連接隊(duì)列(Accept Queue),Server 端進(jìn)入 ESTABLISHED 狀態(tài)

5、Server 端應(yīng)用進(jìn)程調(diào)用 accept 函數(shù)時(shí),將連接從全連接隊(duì)列(Accept Queue)中取出

半連接隊(duì)列和全連接隊(duì)列都有長(zhǎng)度大小限制,超過(guò)限制時(shí)內(nèi)核會(huì)將連接 Drop 丟棄或者返回 RST 包。

相關(guān)指標(biāo)查看

ss 命令

通過(guò) ss 命令可以查看到全連接隊(duì)列的信息

  1. # -n 不解析服務(wù)名稱 
  2. # -t 只顯示 tcp sockets 
  3. # -l 顯示正在監(jiān)聽(tīng)(LISTEN)的 sockets 
  4.  
  5. $ ss -lnt 
  6. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  7. LISTEN     0      128       [::]:2380                  [::]:* 
  8. LISTEN     0      128       [::]:80                    [::]:* 
  9. LISTEN     0      128       [::]:8080                  [::]:* 
  10. LISTEN     0      128       [::]:8090                  [::]:* 
  11.  
  12. $ ss -nt 
  13. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  14. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:47452 
  15. ESTAB      0      536       [::ffff:33.9.95.134]:80                  [::ffff:33.43.108.144]:37656 
  16. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38130 
  17. ESTAB      0      536       [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38280 
  18. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [:: 

對(duì)于 LISTEN 狀態(tài)的 socket

  • Recv-Q:當(dāng)前全連接隊(duì)列的大小,即已完成三次握手等待應(yīng)用程序 accept() 的 TCP 鏈接
  • Send-Q:全連接隊(duì)列的最大長(zhǎng)度,即全連接隊(duì)列的大小

對(duì)于非 LISTEN 狀態(tài)的 socket

  • Recv-Q:已收到但未被應(yīng)用程序讀取的字節(jié)數(shù)
  • Send-Q:已發(fā)送但未收到確認(rèn)的字節(jié)數(shù)

相關(guān)內(nèi)核代碼:

  1. // https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_diag.c 
  2. static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 
  3.             void *_info) 
  4.   struct tcp_info *info = _info; 
  5.  
  6.   if (inet_sk_state_load(sk) == TCP_LISTEN) { // socket 狀態(tài)是 LISTEN 時(shí) 
  7.     r->idiag_rqueue = READ_ONCE(sk->sk_ack_backlog);  // 當(dāng)前全連接隊(duì)列大小 
  8.     r->idiag_wqueue = READ_ONCE(sk->sk_max_ack_backlog); // 全連接隊(duì)列最大長(zhǎng)度 
  9.   } else if (sk->sk_type == SOCK_STREAM) {    // socket 狀態(tài)不是 LISTEN 時(shí) 
  10.     const struct tcp_sock *tp = tcp_sk(sk); 
  11.  
  12.     r->idiag_rqueue = max_t(int, READ_ONCE(tp->rcv_nxt) - 
  13.                READ_ONCE(tp->copied_seq), 0);    // 已收到但未被應(yīng)用程序讀取的字節(jié)數(shù) 
  14.     r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una;   // 已發(fā)送但未收到確認(rèn)的字節(jié)數(shù) 
  15.   } 
  16.   if (info) 
  17.     tcp_get_info(sk, info); 

netstat 命令

通過(guò) netstat -s 命令可以查看 TCP 半連接隊(duì)列、全連接隊(duì)列的溢出情況

  1. $ netstat -s | grep -i "listen" 
  2.     189088 times the listen queue of a socket overflowed 
  3.     30140232 SYNs to LISTEN sockets dropped 

上面輸出的數(shù)值是累計(jì)值,分別表示有多少 TCP socket 鏈接因?yàn)槿B接隊(duì)列、半連接隊(duì)列滿了而被丟棄

  • 189088 times the listen queue of a socket overflowed 代表有 189088 次全連接隊(duì)列溢出
  • 30140232 SYNs to LISTEN sockets dropped 代表有 30140232 次半連接隊(duì)列溢出

在排查線上問(wèn)題時(shí),如果一段時(shí)間內(nèi)相關(guān)數(shù)值一直在上升,則表明半連接隊(duì)列、全連接隊(duì)列有溢出情況

實(shí)戰(zhàn) —— 全連接隊(duì)列

全連接隊(duì)列最大長(zhǎng)度控制

TCP 全連接隊(duì)列的最大長(zhǎng)度由 min(somaxconn, backlog) 控制,其中:

  • somaxconn 是 Linux 內(nèi)核參數(shù),由 /proc/sys/net/core/somaxconn 指定
  • backlog 是 TCP 協(xié)議中 listen 函數(shù)的參數(shù)之一,即 int listen(int sockfd, int backlog) 函數(shù)中的 backlog 大小。在 Golang 中,listen 的 backlog 參數(shù)使用的是 /proc/sys/net/core/somaxconn 文件中的值。

相關(guān)內(nèi)核代碼:

  1. // https://github.com/torvalds/linux/blob/master/net/socket.c 
  2.  
  3. /* 
  4.  *  Perform a listen. Basically, we allow the protocol to do anything 
  5.  *  necessary for a listen, and if that works, we mark the socket as 
  6.  *  ready for listening. 
  7.  */ 
  8. int __sys_listen(int fd, int backlog) 
  9.   struct socket *sock; 
  10.   int err, fput_needed; 
  11.   int somaxconn; 
  12.  
  13.   sock = sockfd_lookup_light(fd, &err, &fput_needed); 
  14.   if (sock) { 
  15.     somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;  // /proc/sys/net/core/somaxconn 
  16.     if ((unsigned int)backlog > somaxconn) 
  17.       backlog = somaxconn;   // TCP 全連接隊(duì)列最大長(zhǎng)度 min(somaxconn, backlog) 
  18.  
  19.     err = security_socket_listen(sock, backlog); 
  20.     if (!err) 
  21.       err = sock->ops->listen(sock, backlog); 
  22.  
  23.     fput_light(sock->file, fput_needed); 
  24.   } 
  25.   return err; 

實(shí)驗(yàn)

服務(wù)端 server 代碼

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.   "net" 
  6.   "time" 
  7.  
  8. func main() { 
  9.   l, err := net.Listen("tcp"":8888"
  10.   if err != nil { 
  11.     log.Printf("failed to listen due to %v", err) 
  12.   } 
  13.   defer l.Close() 
  14.   log.Println("listen :8888 success"
  15.  
  16.   for { 
  17.     time.Sleep(time.Second * 100) 
  18.   } 

在測(cè)試環(huán)境查看 somaxconn 的值為 128

  1. $ cat /proc/sys/net/core/somaxconn 
  2. 128 

啟動(dòng)服務(wù)端,通過(guò) ss -lnt | grep :8888 確認(rèn)全連接隊(duì)列大小

  1. LISTEN     0      128       [::]:8888                  [::]:* 

全連接隊(duì)列最大長(zhǎng)度為 128

現(xiàn)在更新 somaxconn 值為 1024,再重新啟動(dòng)服務(wù)端。

1、更新 /etc/sysctl.conf 文件,該文件為內(nèi)核參數(shù)配置文件

a.新增一行 net.core.somaxconn=1024

2、執(zhí)行 sysctl -p 使配置生效

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 1024 

3、檢查 /proc/sys/net/core/somaxconn 文件,確認(rèn) somaxconn 為更新后的 1024

  1. $ cat /proc/sys/net/core/somaxconn 
  2. 1024 

重新啟動(dòng)服務(wù)端, 通過(guò) ss -lnt | grep :8888 確認(rèn)全連接隊(duì)列大小

  1. $ ss -lnt | grep 8888 
  2. LISTEN     0      1024      [::]:8888                  [::]:* 

可以看到,現(xiàn)在全鏈接隊(duì)列最大長(zhǎng)度為 1024,成功更新。

全連接隊(duì)列溢出

下面來(lái)驗(yàn)證下全連接隊(duì)列溢出會(huì)發(fā)生什么情況,可以通過(guò)讓服務(wù)端應(yīng)用只負(fù)責(zé) Listen 對(duì)應(yīng)端口而不執(zhí)行 accept() TCP 連接,使 TCP 全連接隊(duì)列溢出。

實(shí)驗(yàn)物料

服務(wù)端 server 代碼

  1. // server 端監(jiān)聽(tīng) 8888 tcp 端口 
  2.  
  3. package main 
  4.  
  5. import ( 
  6.   "log" 
  7.   "net" 
  8.   "time" 
  9.  
  10. func main() { 
  11.   l, err := net.Listen("tcp"":8888"
  12.   if err != nil { 
  13.     log.Printf("failed to listen due to %v", err) 
  14.   } 
  15.   defer l.Close() 
  16.   log.Println("listen :8888 success"
  17.  
  18.   for { 
  19.     time.Sleep(time.Second * 100) 
  20.   } 

客戶端 client 代碼

  1. // client 端并發(fā)請(qǐng)求 10 次 server 端,成功建立 tcp 連接后向 server 端發(fā)送數(shù)據(jù) 
  2. package main 
  3.  
  4. import ( 
  5.   "context" 
  6.   "log" 
  7.   "net" 
  8.   "os" 
  9.   "os/signal" 
  10.   "sync" 
  11.   "syscall" 
  12.   "time" 
  13.  
  14. var wg sync.WaitGroup 
  15.  
  16. func establishConn(ctx context.Context, i int) { 
  17.   defer wg.Done() 
  18.   conn, err := net.DialTimeout("tcp"":8888"time.Second*5) 
  19.   if err != nil { 
  20.     log.Printf("%d, dial error: %v", i, err) 
  21.     return 
  22.   } 
  23.   log.Printf("%d, dial success", i) 
  24.   _, err = conn.Write([]byte("hello world")) 
  25.   if err != nil { 
  26.     log.Printf("%d, send error: %v", i, err) 
  27.     return 
  28.   } 
  29.   select { 
  30.   case <-ctx.Done(): 
  31.     log.Printf("%d, dail close", i) 
  32.   } 
  33.  
  34. func main() { 
  35.   ctx, cancel := context.WithCancel(context.Background()) 
  36.   for i := 0; i < 10; i++ { 
  37.     wg.Add(1) 
  38.     go establishConn(ctx, i) 
  39.   } 
  40.  
  41.   go func() { 
  42.     sc := make(chan os.Signal, 1) 
  43.     signal.Notify(sc, syscall.SIGINT) 
  44.     select { 
  45.     case <-sc: 
  46.       cancel() 
  47.     } 
  48.   }() 
  49.  
  50.   wg.Wait() 
  51.   log.Printf("client exit"

為了方便實(shí)驗(yàn),將 somaxconn 全連接隊(duì)列最大長(zhǎng)度更新為 5:

1、更新 /etc/sysctl.conf 文件,將 net.core.somaxconn 更新為 5

2、執(zhí)行 sysctl -p 使配置生效

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 5 

實(shí)驗(yàn)結(jié)果

客戶端日志輸出

  1. 2021/10/11 17:24:48 8, dial success 
  2. 2021/10/11 17:24:48 3, dial success 
  3. 2021/10/11 17:24:48 4, dial success 
  4. 2021/10/11 17:24:48 6, dial success 
  5. 2021/10/11 17:24:48 5, dial success 
  6. 2021/10/11 17:24:48 2, dial success 
  7. 2021/10/11 17:24:48 1, dial success 
  8. 2021/10/11 17:24:48 0, dial success 
  9. 2021/10/11 17:24:48 7, dial success 
  10. 2021/10/11 17:24:53 9, dial error: dial tcp 33.9.192.157:8888: i/o timeout 

客戶端 socket 情況

  1. tcp        0      0 33.9.192.155:40372      33.9.192.157:8888       ESTABLISHED 
  2. tcp        0      0 33.9.192.155:40376      33.9.192.157:8888       ESTABLISHED 
  3. tcp        0      0 33.9.192.155:40370      33.9.192.157:8888       ESTABLISHED 
  4. tcp        0      0 33.9.192.155:40366      33.9.192.157:8888       ESTABLISHED 
  5. tcp        0      0 33.9.192.155:40374      33.9.192.157:8888       ESTABLISHED 
  6. tcp        0      0 33.9.192.155:40368      33.9.192.157:8888       ESTABLISHED 

服務(wù)端 socket 情況

  1. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40376      ESTABLISHED 
  2. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40370      ESTABLISHED 
  3. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40368      ESTABLISHED 
  4. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40372      ESTABLISHED 
  5. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40374      ESTABLISHED 
  6. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40366      ESTABLISHED 
  7.  
  8. tcp    LISTEN     6      5      [::]:8888               [::]:*                   users:(("main",pid=84244,fd=3)) 

抓包結(jié)果

對(duì)客戶端、服務(wù)端抓包后,發(fā)現(xiàn)出現(xiàn)了三種情況,分別是:

  • client 成功與 server 端建立 tcp socket 連接,發(fā)送數(shù)據(jù)成功
  • client 認(rèn)為成功與 server 端建立 tcp socket 連接,發(fā)送數(shù)據(jù)失敗,一直在 RETRY;server 端認(rèn)為 tcp 連接未建立,一直在發(fā)送 SYN+ACK
  • client 向 server 發(fā)送 SYN 未得到響應(yīng),一直在 RETRY

全連接隊(duì)列實(shí)驗(yàn)結(jié)果分析

上述實(shí)驗(yàn)結(jié)果出現(xiàn)了三種情況,我們分別對(duì)抓包內(nèi)容進(jìn)行分析

情況一:Client 成功與 Server 端建立 tcp socket 鏈接,發(fā)送數(shù)據(jù)成功

上圖可以看到如下請(qǐng)求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手
  • Server 端收到 Client 端 SYN 后,向 Client 端回復(fù) SYN+ACK,socket 連接存儲(chǔ)到半連接隊(duì)列(SYN Queue)
  • Client 端收到 Server 端 SYN+ACK 后,向 Server 端回復(fù) ACK,Client 端進(jìn)入 ESTABLISHED 狀態(tài)
  • Server 端收到 Client 端 ACK 后,進(jìn)入 ESTABLISHED 狀態(tài),socket 連接存儲(chǔ)到全連接隊(duì)列(Accept Queue)
  • Client 端向 Server 端發(fā)送數(shù)據(jù) [PSH, ACK],Server 端確認(rèn)接收到數(shù)據(jù) [ACK]

這種情況就是正常的請(qǐng)求,即全連接隊(duì)列、半連接隊(duì)列未滿,client 成功與 server 建立了 tcp 鏈接,并成功發(fā)送數(shù)據(jù)。

情況二:Client 認(rèn)為成功與 Server 端建立 tcp socket 連接,后續(xù)發(fā)送數(shù)據(jù)失敗,持續(xù) RETRY;Server 端認(rèn)為 TCP 連接未建立,一直在發(fā)送SYN+ACK

上圖可以看到如下請(qǐng)求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手
  • Server 端收到 Client 端 SYN 后,向 Client 端回復(fù) SYN+ACK,socket 連接存儲(chǔ)到半連接隊(duì)列(SYN Queue)
  • Client 端收到 Server 端 SYN+ACK 后,向 Server 端回復(fù) ACK,Client 端進(jìn)入 ESTABLISHED狀態(tài)(重要:此時(shí)僅僅是 Client 端認(rèn)為 tcp 連接建立成功)
  • 由于 Client 端認(rèn)為 TCP 連接已經(jīng)建立完成,所以向 Server 端發(fā)送數(shù)據(jù) [PSH,ACK],但是一直未收到 Server 端的確認(rèn) ACK,所以一直在 RETRY
  • Server 端一直在 RETRY 發(fā)送 SYN+ACK

為什么會(huì)出現(xiàn)上述情況?Server 端為什么一直在 RETRY 發(fā)送 SYN+ACK?Server 端不是已經(jīng)收到了 Client 端的 ACK 確認(rèn)了嗎?

上述情況是由于 Server 端 socket 連接進(jìn)入了半連接隊(duì)列,在收到 Client 端 ACK 后,本應(yīng)將 socket 連接存儲(chǔ)到全連接隊(duì)列,但是全連接隊(duì)列已滿,所以 Server 端 DROP 了該 ACK 請(qǐng)求。

之所以 Server 端一直在 RETRY 發(fā)送 SYN+ACK,是因?yàn)?DROP 了 client 端的 ACK 請(qǐng)求,所以 socket 連接仍舊在半連接隊(duì)列中,等待 Client 端回復(fù) ACK。

tcp_abort_on_overflow 參數(shù)控制

全連接隊(duì)列滿DROP 請(qǐng)求是默認(rèn)行為,可以通過(guò)設(shè)置 /proc/sys/net/ipv4/tcp_abort_on_overflow 使 Server 端在全連接隊(duì)列滿時(shí),向 Client 端發(fā)送 RST 報(bào)文。

tcp_abort_on_overflow 有兩種可選值:

  • 0:如果全連接隊(duì)列滿了,Server 端 DROP Client 端回復(fù)的 ACK
  • 1:如果全連接隊(duì)列滿了,Server 端向 Client 端發(fā)送 RST 報(bào)文,終止 TCP socket 鏈接 (TODO:后續(xù)有時(shí)間補(bǔ)充下該實(shí)驗(yàn))

為什么實(shí)驗(yàn)結(jié)果中當(dāng)前全連接隊(duì)列大小 > 全連接隊(duì)列最大長(zhǎng)度配置?

上述結(jié)果中可以看到 Listen 狀態(tài)的 socket 鏈接:

  • Recv-Q 當(dāng)前全連接隊(duì)列的大小是 6
  • Send-Q 全連接隊(duì)列最大長(zhǎng)度是 5
  1. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  2. LISTEN     6      5         [::]:8888                  [::]:* 

為什么全連接隊(duì)列大小 > 全連接隊(duì)列最大長(zhǎng)度配置呢?

經(jīng)過(guò)多次實(shí)驗(yàn)發(fā)現(xiàn),能夠進(jìn)入全連接隊(duì)列的 Socket 最大數(shù)量始終比配置的全連接隊(duì)列最大長(zhǎng)度 + 1。

結(jié)合其他文章以及內(nèi)核代碼,發(fā)現(xiàn)內(nèi)核在判斷全連接隊(duì)列是否滿的情況下,使用的是 > 而非 >= (具體是為什么沒(méi)有找到相關(guān)資源 : ) )。

相關(guān)內(nèi)核代碼:

  1. /* Note: If you think the test should be: 
  2.  *  return READ_ONCE(sk->sk_ack_backlog) >= READ_ONCE(sk->sk_max_ack_backlog); 
  3.  * Then please take a look at commit 64a146513f8f ("[NET]: Revert incorrect accept queue backlog changes."
  4.  */ 
  5. static inline bool sk_acceptq_is_full(const struct sock *sk) 
  6.   return READ_ONCE(sk->sk_ack_backlog) > READ_ONCE(sk->sk_max_ack_backlog); 

情況三:Client 向 Server 發(fā)送 SYN 未得到相應(yīng),一直在 RETRY

圖片上圖可以看到如下請(qǐng)求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手,未得到 Server 回應(yīng),一直在 RETRY

(這種情況涉及到半連接隊(duì)列,這里先給上述情況發(fā)生的原因結(jié)論,具體內(nèi)容將在下文半連接隊(duì)列中展開(kāi)。)

發(fā)生上述情況的原因由以下兩方面導(dǎo)致:

1、開(kāi)啟了 /proc/sys/net/ipv4/tcp_syncookies 功能

2、全連接隊(duì)列滿了

實(shí)戰(zhàn) —— 半連接隊(duì)列

半連接隊(duì)列最大長(zhǎng)度控制

翻閱了很多博文,查找關(guān)于半連接隊(duì)列最大長(zhǎng)度控制的相關(guān)內(nèi)容,大多含糊其辭或不準(zhǔn)確,經(jīng)過(guò)不懈努力,最終找到了比較確切的內(nèi)容(相關(guān)博文鏈接在附錄中)。

很多博文中說(shuō)半連接隊(duì)列最大長(zhǎng)度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 參數(shù)指定,實(shí)際上只有在 linux 內(nèi)核版本小于 2.6.20 時(shí),半連接隊(duì)列才等于 backlog 的大小。

這塊的源碼比較復(fù)雜,這里給一下大體的計(jì)算方式,詳細(xì)的內(nèi)容可以參考附錄中的相關(guān)博文。半連接隊(duì)列長(zhǎng)度的計(jì)算過(guò)程:

  1. backlog = min(somaxconn, backlog) 
  2. nr_table_entries = backlog 
  3. nr_table_entries = min(backlog, sysctl_max_syn_backlog) 
  4. nr_table_entries = max(nr_table_entries, 8) 
  5. // roundup_pow_of_two: 將參數(shù)向上取整到最小的 2^n,注意這里存在一個(gè) +1 
  6. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) 
  7. max_qlen_log = max(3, log2(nr_table_entries)) 
  8. max_queue_length = 2^max_qlen_log 

可以看到,半連接隊(duì)列的長(zhǎng)度由三個(gè)參數(shù)指定:

  • 調(diào)用 listen 時(shí),傳入的 backlog
  • /proc/sys/net/core/somaxconn 默認(rèn)值為 128
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 默認(rèn)值為 1024

我們假設(shè) listen 傳入的 backlog = 128 (Golang 中調(diào)用 listen 時(shí)傳遞的 backlog 參數(shù)使用的是 /proc/sys/net/core/somaxconn),其他配置采用默認(rèn)值,來(lái)計(jì)算下半連接隊(duì)列的最大長(zhǎng)度

  1. backlog = min(somaxconn, backlog) = min(128, 128) = 128 
  2. nr_table_entries = backlog = 128 
  3. nr_table_entries = min(backlog, sysctl_max_syn_backlog) = min(128, 1024) = 128 
  4. nr_table_entries = max(nr_table_entries, 8) = max(128, 8) = 128 
  5. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) = 256 
  6. max_qlen_log = max(3, log2(nr_table_entries)) = max(3, 8) = 8 
  7. max_queue_length = 2^max_qlen_log = 2^8 = 256 

可以得到半隊(duì)列大小是 256。

判斷是否 Drop SYN 請(qǐng)求

當(dāng) Client 端向 Server 端發(fā)送 SYN 報(bào)文后,Server 端會(huì)將該 socket 連接存儲(chǔ)到半連接隊(duì)列(SYN Queue),如果 Server 端判斷半連接隊(duì)列滿了則會(huì)將連接 Drop 丟棄。

那么 Server 端是如何判斷半連接隊(duì)列是否滿的呢?除了上面一小節(jié)提到的半連接隊(duì)列最大長(zhǎng)度控制外,還和 /proc/sys/net/ipv4/tcp_syncookies 參數(shù)有關(guān)。(tcp_syncookies 的作用是為了防止 SYN Flood 攻擊的,下文會(huì)給出相關(guān)鏈接介紹)

流程圖

判斷是否 Drop SYN 請(qǐng)求的流程圖:

上圖是整理了多份資料后,整理出來(lái)的判斷是否 Drop SYN 請(qǐng)求的流程圖。

注意:第一個(gè)判斷條件 「當(dāng)前半連接隊(duì)列是否已超過(guò)半連接隊(duì)列最大長(zhǎng)度」在不同內(nèi)核版本中的判斷不一樣,Linux4.19.91 內(nèi)核判斷的是當(dāng)前半連接隊(duì)列長(zhǎng)度是否 >= 全連接隊(duì)列最大長(zhǎng)度。

相關(guān)內(nèi)核代碼:

  1. static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk) 
  2.   return inet_csk_reqsk_queue_len(sk) >= sk->sk_max_ack_backlog; 

我們假設(shè)如下參數(shù),來(lái)計(jì)算下當(dāng) Client 端只發(fā)送 SYN 包,理論上 Server 端何時(shí)會(huì) Drop SYN 請(qǐng)求:

  • 調(diào)用 listen 時(shí)傳入的 backlog = 1024
  • /proc/sys/net/core/somaxconn 值為 1024
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 值為 128

當(dāng) /proc/sys/net/ipv4/tcp_syncookies 值為 0 時(shí)

  • 計(jì)算出的半連接隊(duì)列最大長(zhǎng)度為 256
  • 當(dāng)半連接隊(duì)列長(zhǎng)度增長(zhǎng)至 96 后,再新增 SYN 請(qǐng)求,就會(huì)觸發(fā) Drop SYN 請(qǐng)求

當(dāng) /proc/sys/net/ipv4/tcp_syncookies 值為 1 時(shí)

1.計(jì)算出的半連接隊(duì)列最大長(zhǎng)度為 256

2.由于開(kāi)啟了 tcp_syncookies

  • 當(dāng)全連接隊(duì)列未滿時(shí),永遠(yuǎn)不會(huì) Drop 請(qǐng)求 (注意:經(jīng)實(shí)驗(yàn)發(fā)現(xiàn)這個(gè)理論是錯(cuò)誤的,實(shí)驗(yàn)發(fā)現(xiàn)只要半連接隊(duì)列的大小 > 全連接隊(duì)列最大長(zhǎng)度就會(huì)觸發(fā) Drop SYN 請(qǐng)求)
  • 當(dāng)全連接隊(duì)列滿了后,即全連接隊(duì)列大小到 1024 后,就會(huì)觸發(fā) Drop SYN 請(qǐng)求

PS:/proc/sys/net/ipv4/tcp_syncookies 的取值還可以為 2,筆者沒(méi)有詳細(xì)實(shí)驗(yàn)。

回顧全連接隊(duì)列實(shí)驗(yàn)結(jié)果

在上文全連接隊(duì)列實(shí)驗(yàn)中,有一類實(shí)驗(yàn)結(jié)果是:client 向 Server 發(fā)送 SYN 未得到響應(yīng),一直在 RETRY。

發(fā)生上述情況的原因由以下兩方面導(dǎo)致:

1. 開(kāi)啟了 /proc/sys/net/ipv4/tcp_syncookies 功能

2. 全連接隊(duì)列滿了

半連接隊(duì)列溢出實(shí)驗(yàn)

上文我們已經(jīng)知道如何計(jì)算理論上半連接隊(duì)列何時(shí)會(huì)溢出,下面我們來(lái)具體實(shí)驗(yàn)下

(Golang 調(diào)用 listen 時(shí)傳入的 backlog 值為 somaxconn)

實(shí)驗(yàn)一:syncookies=0,somaxconn=1024,tcp_max_syn_backlog=128

理論上:

  • 計(jì)算出的半連接隊(duì)列最大長(zhǎng)度為 256
  • 當(dāng)半連接隊(duì)列長(zhǎng)度增長(zhǎng)至 96 后,后續(xù) SYN 請(qǐng)求就會(huì)觸發(fā) Drop

將相關(guān)參數(shù)的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 1024 
  3. net.ipv4.tcp_max_syn_backlog = 128 
  4. net.ipv4.tcp_syncookies = 0 

啟動(dòng)服務(wù)端 Server 監(jiān)聽(tīng) 8888 端口(代碼參考全連接隊(duì)列實(shí)驗(yàn)物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務(wù)端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個(gè)數(shù):

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 96 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 96 

實(shí)驗(yàn)結(jié)果符合預(yù)期,當(dāng)半連接隊(duì)列長(zhǎng)度增長(zhǎng)至 96 后,后續(xù) SYN 請(qǐng)求就會(huì)觸發(fā) Drop。

實(shí)驗(yàn)二:syncookies = 0,somaxconn=128,tcp_max_syn_backlog=512

理論上:

  • 計(jì)算出的半連接隊(duì)列最大長(zhǎng)度為 256,由于筆者實(shí)驗(yàn)機(jī)器上的內(nèi)核版本是 4.19.91,所以當(dāng)半連接隊(duì)列長(zhǎng)度 >= 全連接隊(duì)列最大長(zhǎng)度時(shí),內(nèi)核就認(rèn)為半連接隊(duì)列溢出了
  • 所以當(dāng)半連接隊(duì)列長(zhǎng)度增長(zhǎng)至 128 后,后續(xù) SYN 請(qǐng)求就會(huì)觸發(fā) DROP

將相關(guān)參數(shù)的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 128 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 0 

啟動(dòng)服務(wù)端 Server 監(jiān)聽(tīng) 8888 端口(代碼參考全連接隊(duì)列實(shí)驗(yàn)物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務(wù)端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個(gè)數(shù):

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 128 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 128 

實(shí)驗(yàn)結(jié)果符合預(yù)期,當(dāng)半連接隊(duì)列長(zhǎng)度增長(zhǎng)至 128 后,后續(xù) SYN 請(qǐng)求就會(huì)觸發(fā) Drop

實(shí)驗(yàn)三:syncookies = 1,somaxconn=128,tcp_max_syn_backlog=512

理論上:

  • 當(dāng)全連接隊(duì)列未滿,syncookies = 1,理論上 SYN 請(qǐng)求永遠(yuǎn)不會(huì)被 Drop

將相關(guān)參數(shù)的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 128 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 1 

啟動(dòng)服務(wù)端 Server 監(jiān)聽(tīng) 8888 端口(代碼參考全連接隊(duì)列實(shí)驗(yàn)物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務(wù)端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個(gè)數(shù):

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 128 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 128 

實(shí)驗(yàn)發(fā)現(xiàn)即使syncookies=1,當(dāng)半連接隊(duì)列長(zhǎng)度 > 全連接隊(duì)列最大長(zhǎng)度時(shí),就會(huì)觸發(fā) DROP SYN 請(qǐng)求!!!(TODO:有時(shí)間閱讀下相關(guān)內(nèi)核源碼,再分析下)

繼續(xù)做實(shí)驗(yàn),將 somaxconn 更新為 5

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 5 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 1 

發(fā)起 SYN Flood 攻擊后,查看服務(wù)端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個(gè)數(shù):

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3.  
  4. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  5. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 

確實(shí) 即使 syncookies=1,當(dāng)半連接隊(duì)列長(zhǎng)度 > 全連接最大長(zhǎng)度時(shí),就會(huì)觸發(fā) DROP SYN 請(qǐng)求。

實(shí)驗(yàn)四:syncookies = 1,somaxconn=256,tcp_max_syn_backlog=128

理論上:

  • 當(dāng)半連接隊(duì)列大小到 256 后,后觸發(fā) DROP SYN 請(qǐng)求

將相關(guān)參數(shù)的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 256 
  3. net.ipv4.tcp_max_syn_backlog = 128 
  4. net.ipv4.tcp_syncookies = 1 

啟動(dòng)服務(wù)端 Server 監(jiān)聽(tīng) 8888 端口(代碼參考全連接隊(duì)列實(shí)驗(yàn)物料)。

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務(wù)端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個(gè)數(shù):

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 256 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 256 

實(shí)驗(yàn)結(jié)果符合預(yù)期,當(dāng)半連接隊(duì)列長(zhǎng)度增長(zhǎng)至 256 后,后續(xù) SYN 請(qǐng)求就會(huì)觸發(fā) Drop。

回顧線上問(wèn)題

再回顧值班時(shí)遇到的 Connection timeout 問(wèn)題,當(dāng)時(shí)相關(guān)系統(tǒng)參數(shù)配置為:

  • net.core.somaxconn = 128
  • net.ipv4.tcp_max_syn_backlog = 512
  • net.ipv4.tcp_syncookies = 1
  • net.ipv4.tcp_abort_on_overflow = 0

所以出現(xiàn) Connection timeout 有兩種可能情況:

1、半連接隊(duì)列未滿,全連接隊(duì)列滿,Client 端向 Server 端發(fā)起 SYN 被 DROP (參考全連接隊(duì)列實(shí)驗(yàn)結(jié)果情況三分析、半連接隊(duì)列溢出實(shí)驗(yàn)情況三)

2、全連接隊(duì)列未滿,半連接隊(duì)列大小超過(guò)全鏈接隊(duì)列最大長(zhǎng)度(參考半連接隊(duì)列溢出實(shí)驗(yàn)情況三、半連接隊(duì)列溢出實(shí)驗(yàn)情況四)

問(wèn)題的最快修復(fù)方式是將 net.core.somaxconn 調(diào)大,以及 net.ipv4.tcp_abort_on_overflow 設(shè)置為 1,net.ipv4.tcp_abort_on_overflow 設(shè)置為 1 是為了讓 client fail fast。

總結(jié)

半連接隊(duì)列溢出、全連接隊(duì)列溢出這類問(wèn)題很容易被忽略,同時(shí)這類問(wèn)題又很致命。當(dāng)半連接隊(duì)列、全連接隊(duì)列溢出時(shí) Server 端,從監(jiān)控上來(lái)看系統(tǒng) cpu 水位、內(nèi)存水位、網(wǎng)絡(luò)連接數(shù)等一切正常,然而卻會(huì)持續(xù)影響 Client 端業(yè)務(wù)請(qǐng)求。對(duì)于高負(fù)載上游使用短連接的情況,出現(xiàn)這類問(wèn)題的可能性更大。

本文詳細(xì)梳理了 TCP 半連接隊(duì)列、全連接隊(duì)列的理論知識(shí),同時(shí)結(jié)合 Linux 相關(guān)內(nèi)核代碼以及詳細(xì)的動(dòng)手實(shí)驗(yàn),講解了 TCP 半連接隊(duì)列、全連接隊(duì)列的相關(guān)原理、溢出判斷、問(wèn)題分析等內(nèi)容,希望大家在閱讀后可以對(duì) TCP 半連接隊(duì)列、全連接隊(duì)列有更充分的認(rèn)識(shí)。

PS:可以去線上檢查下服務(wù)器的相關(guān)參數(shù)喲~

附錄

這里羅列下相關(guān)參考博文資料:

Linux 源碼

  • https://github.com/torvalds/linux

Linux 詭異的半連接隊(duì)列長(zhǎng)度

  • https://www.cnblogs.com/zengkefu/p/5606696.html

TCP 半連接隊(duì)列和全連接隊(duì)列滿了會(huì)發(fā)生什么

  • https://www.cnblogs.com/xiaolincoding/p/12995358.html

一次 HTTP connect-timeout 排查

  • https://www.jianshu.com/p/3b9c4216b822

Connection Reset 排查

  • https://cjting.me/2019/08/28/tcp-queue/

深入淺出 TCP 中的 SYN-Cookies

  • https://segmentfault.com/a/1190000019292140

 

責(zé)任編輯:武曉燕 來(lái)源: 云巔論劍
相關(guān)推薦

2019-09-16 09:29:01

TCP全連接隊(duì)列半連接隊(duì)列

2015-04-23 18:46:38

TCPTCP協(xié)議

2023-04-06 07:53:56

Redis連接問(wèn)題K8s

2018-07-05 14:25:01

TCP握手原理

2024-01-19 19:22:45

TCPTIME_WAIT

2020-10-14 14:31:37

LinuxTCP連接

2021-03-17 09:51:31

網(wǎng)絡(luò)編程TCP網(wǎng)絡(luò)協(xié)議

2019-11-17 22:11:11

TCPSYN隊(duì)列Accept隊(duì)列

2020-01-18 14:11:13

數(shù)據(jù)庫(kù)線程技術(shù)

2021-11-23 21:21:07

線上排查服務(wù)

2021-12-12 18:12:13

Hbase線上問(wèn)題

2020-11-16 07:19:17

線上函數(shù)性能

2020-02-17 10:10:43

TCP三次握手四次揮手

2010-07-07 10:45:22

TCP UDP協(xié)議

2020-08-24 07:34:39

網(wǎng)絡(luò)超時(shí)請(qǐng)求

2023-11-29 12:12:24

Oceanbase數(shù)據(jù)庫(kù)

2021-10-14 20:33:16

TCP連接關(guān)閉

2012-07-02 13:26:28

電線連接

2014-08-22 09:10:46

2020-10-21 08:17:11

隊(duì)列數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)