天天看點

服務之間調用連接配接不上運維層面問題分析

筆者: 張首富
時間: 20201118 晚           

問題描述

1,20201118日上午清華反應 callout 服務調用 robot 服務失敗;網絡-趙晨排查從網絡層面排查并沒有重傳,丢包現象;

2,20201118日下午李剛反應 auth 等多個 java 服務連不上資料庫,連不上資料庫的 java 服務都是部署在同一台機器上;

過程分析

上面兩個問題其實是有相同點的,相同點就是服務之間的調用不能正常通信(好像是廢話),相同的地方就是都去連接配接服務端固定的一個端口;

通過監控平台分析發下,在發生問題的時間點上,出問題的兩台用戶端機器上的

TCP_timeout

都過多,cpu 記憶體磁盤等名額都趨于正常。是以從 這個問題開始着手分析;監控資料如下

服務之間調用連接配接不上運維層面問題分析
服務之間調用連接配接不上運維層面問題分析

然後懷疑是 TCP timeout 連接配接數過多産生的問題,針對這方面進行排查

排查過程

檢視系統預設 tcp 相關名額

# 是否允許将TIME-WAIT sockets重新用于新的TCP連接配接,預設是否
[root@idc-111 ~]# cat /proc/sys/net/ipv4/tcp_tw_reuse
0
# 是否開啟TCP連接配接中TIME-WAIT sockets的快速回收,預設是否
[root@idc-111 ~]# cat /proc/sys/net/ipv4/tcp_tw_recycle
0
# 系統預設的TIMEOUT時間
[root@idc-111 ~]# cat /proc/sys/net/ipv4/tcp_fin_timeout
60
# 系統可用的 tcp-udp 連接配接數
[root@idc-111 ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768    60999
# 系統預設可用 28321個
[root@idc-111 ~]# echo "" | awk 'BEGIN{print 60999-32768}'
28231           

通過檢視這個名額,和上面的監控圖來看,使我們用戶端可用的 tcp 端口不夠用了,是以導緻連接配接失敗;然後我們來看下這個端口範圍是否真的會影響連接配接

主要分析ip_local_port_range參數

網上關于

net.ipv4.ip_local_port_range

的值的效果衆說紛纭(下面所說的連接配接都假定使用的是相同的協定(都是 TCP 或 UDP)):

  • 大部分文章都說這個值決定了用戶端的一個 ip 可用的端口數量,即一個 ip 最多隻能建立 60K 多一點的連接配接(1025-65535),如果要突破這個限制需要用戶端機器綁定多個 ip。
  • 還有部分文章說的是這個值決定的是 socket 四元組中的本地端口數量,即一個 ip 對同一個目标 ip+port 最多可以建立 60K 多一點連接配接,隻要目标 ip 或端口不一樣就可以使用相同的本地端口,不一定需要多個用戶端 ip 就可以突破端口數量限制。

檢視幫助文檔介紹的模糊不清

ip_local_port_range - 2 INTEGERS
    Defines the local port range that is used by TCP and UDP to
    choose the local port. The first number is the first, the
    second the last local port number.
    If possible, it is better these numbers have different parity.
    (one even and one odd values)
    The default values are 32768 and 60999 respectively.           

實驗測試上述内容

實際兩台主機為

[root@idc-111 ~]# uname -a
Linux idc-111 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux           

1, 相同目标 ip 和相同目标端口下的端口數量限制

先設定 ip_local_port_range 的值為非常小的範圍:

$ echo "net.ipv4.ip_local_port_range = 61000  61001" >> /etc/sysctl.conf
$ sysctl -p
$ cat /proc/sys/net/ipv4/ip_local_port_range
61000       61001           

然後對相同 ip 和端口發送 tcp 請求。建立兩個連接配接,達到最大端口數量限制:

[root@220 ~]# nohup telnet 172.16.102.221 80 &
[1] 18941
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"
[root@220 ~]# nohup telnet 172.16.102.221 80 &
[1] 18940
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"

[root@220 ~]# ss -ant |grep 172.16.102.220:600
TIME-WAIT  0      0      172.16.102.220:60001              172.16.102.221:80
TIME-WAIT  0      0      172.16.102.220:60000              172.16.102.221:80           

然後再建立第三個連接配接,此時預期應該會失敗,因為超出的端口數量現在:

[root@220 ~]# telnet 172.16.102.221 80
Trying 172.16.102.221...
telnet: connect to address 172.16.102.221: Cannot assign requested address           

确實建立第三個連接配接的時候失敗了。然後等了一會超過了 timeout 逾時時間,回收之後又可以繼續連接配接了

[root@220 ~]# ss -ant |grep 172.16.102.220
ESTAB      0      0      172.16.102.220:22                 192.168.5.62:53827
[root@220 ~]# telnet 172.16.102.221 80
Trying 172.16.102.221...
Connected to 172.16.102.221.
Escape character is '^]'.
^CConnection closed by foreign host.           

2, 相同目标 ip 不同目标端口

下面看看相同目标 ip 不同目标端口是否可以突破這個端口限制:

[root@220 ~]# nohup telnet 172.16.102.221 80 &
[1] 18941
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"
[root@220 ~]# nohup telnet 172.16.102.221 80 &
[1] 18940
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"
[root@220 ~]# nohup telnet 172.16.102.221 443 &
[1] 18942
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"
[root@220 ~]# nohup telnet 172.16.102.221 443 &
[1] 18943
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"


[root@220 ~]# ss -ant |grep 172.16.102.220:600
TIME-WAIT  0      0      172.16.102.220:60001              172.16.102.221:80
TIME-WAIT  0      0      172.16.102.220:60000              172.16.102.221:80
TIME-WAIT  0      0      172.16.102.220:60001              172.16.102.221:443
TIME-WAIT  0      0      172.16.102.220:60000              172.16.102.221:443           

可以看到相同目标 ip 不同目标端口下,每個目标端口都有一個獨立的端口限制,即,相同源 ip 的源端口是可以相同的。

按照推測這兩個目标端口應該隻能建立四個連接配接,下面試試看:

[root@220 ~]# telnet 172.16.102.221 80
Trying 172.16.102.221...
telnet: connect to address 172.16.102.221: Cannot assign requested address

[root@220 ~]# telnet 172.16.102.221 443
Trying 172.16.102.221...
telnet: connect to address 172.16.102.221: Cannot assign requested address           

确實是不能再建立連接配接了,因為每個目标端口都達到了 ip_local_port_range 的限制。

3,多個目标 ip 相同目标端口

下面看一下多個目标 ip 相同目标端口下的情況:

[root@220 ~]# nohup telnet 172.16.102.221 80 &
[1] 18941
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"
[root@220 ~]# nohup telnet 172.16.102.221 80 &
[1] 18940
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"

[root@220 ~]# nohup telnet 172.16.102.222 80 &
[1] 18941
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"
[root@220 ~]# nohup telnet 172.16.102.222 80 &
[1] 18940
[root@220 ~]# nohup: 忽略輸入并把輸出追加到"nohup.out"


[root@220 ~]# ss -ant |grep 172.16.102.220:600
TIME-WAIT  0      0      172.16.102.220:60001              172.16.102.221:80
TIME-WAIT  0      0      172.16.102.220:60000              172.16.102.221:80
TIME-WAIT  0      0      172.16.102.220:60001              172.16.102.222:80
TIME-WAIT  0      0      172.16.102.220:60000              172.16.102.222:80           

可以看到,每個目标 ip 都有獨立的 ip_local_port_range 限制。

4, 多個目标 ip 不同目标端口

下面看一下多個目标 ip 相同不同端口下的情況,按照前面的經驗兩個 ip 加兩個端口應該隻能建立 8 個連接配接,下面這個自行測試,

處理辦法

vim /etc/sysctl.conf
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30           

1,增加可用的端口範圍

2,開啟重用

3,開啟TCP連接配接中TIME-WAIT sockets的快速回收

4,縮短 timeout 的時間

總結

通過上面的實驗發現,昨天發生問題的時候我們屬于第一種情況,用戶端連接配接同一台機器的同一個端口。當 tcp timeout 過的時候造成新的連接配接連接配接不上,是以出現連接配接失敗的情況;還請排查為什麼會出現那麼多 timeout建議檢查分析代碼