1. 程式人生 > 其它 >Cannot assign requested address 導致 502

Cannot assign requested address 導致 502

1. 先說問題

某天晚上9點左右正是業務高峰期,我們有臺Nginx觸發了響應狀態碼異常的告警,大量502。登入機器檢視error log 輸出的都是

Cannot assign requested address     # 不能分配請求地址

2. 解決方案

[root@node-1 ~]# cat /etc/sysctl.conf
...
net.ipv4.ip_local_port_range=1024 65000     # 調大埠範圍
net.ipv4.tcp_timestamps=1                   # 開啟tcp連結資料包的時間戳
net.ipv4.tcp_tw_recycle=0                   # 關閉tcp time_wait 狀態的快速回收
net.ipv4.tcp_tw_reuse=1                     # 開啟tcp time_wait 狀態的複用
net.ipv4.tcp_max_tw_buckets=8192            # 適量調整系統中可存在的最大time_wait狀態數量
...

使其生效
[root@node-1 ~]# sysctl -p

3. 為什麼這麼做?

首先先分析下錯誤資訊: "Cannot assign requested address", 不能分配請求地址。 在網際網路中唯一標識一個會話的根本是什麼?答案是: 五元組

  • 五元組: 源ip:源port + 傳輸層協議 + 目的ip:目的port

知道了五元組的概念,還需要知道每個連線都唯一對應著一個五元組, 所以 "Cannot assign requested address" 意味著當前系統無法為新的連線構造/分配一個五元組, 其實這種錯誤一般出現在client端,因為在一個請求中目的ip和目的埠是固定的(因為server端啟動就監聽了一個ip+port),協議也不會少(一般是TCP/UDP), 因此這個問題僅會發生在client端(這裡指CS架構且C/S分開部署&無其他干擾的情況下)。

繼續分析client端,在同一個client中 源ip是固定的, 那麼唯一可變的就是源port, 那麼我們基本上就可以定位到此問題出現的原因是因為client端埠不足導致的。

3.1 Client的埠都去哪裡了?

此時你通過監控系統或者登入機器檢視此時的tcp連線狀態,會發現一些異常, time_wait 狀態很多

# 我這裡並沒有復現, 只是展示說明一下,實際發生時time_wait狀態會很多
[root@node-1 ~]# netstat -n|awk '/^tcp/{++S[$NF]} END {for(a in S) print a,S[a]}'

ESTABLISHED 35
SYN_SENT 1
TIME_WAIT 2100
  • 所以可以發現client的埠都被time_wait狀態佔用了

3.2 如何複用time_wait狀態的連線

即然讀到了這裡那麼我預設你是知曉tcp的11種狀態機的轉換機制的,那麼應該也會知道time_wait狀態會維持2MSL(最大報文生存時間)之後才會轉換到close狀態釋放該五元組。

# 在linux系統中2MSL是固定的60s
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
				  * state, about 60 seconds	*/

如果不做什麼改動的話死等60s對整個系統來說是不可接受的,因此我們需要做上述第2點的核心引數的改動,去快速複用time_wait狀態來建立新的連線,具體的引數解釋如下:

  • net.ipv4.ip_local_port_range=1024 65000

通過上面對五元組的介紹這個引數改動就很顯然易見了, 在4個量都不變的情況下, 可用的埠數量越多能夠建立的連線就越多

  • net.ipv4.tcp_timestamps=1
  • net.ipv4.tcp_tw_reuse=1

開啟time_wait複用的前提是需要開啟tcp連線的時間戳,因為tw_reuse需要依賴時間戳判斷一個time_wait是否可被複用

  • net.ipv4.tcp_tw_recycle=0

快速回收time_wait狀態, 該引數建議關閉(置為0), 因為在NAT的網路環境下開啟這個引數會產生很嚴重的問題。簡單來說: 該引數開啟後系統會依據tcp的時間戳先後順序來回收time_wait,但是每個機器(執行環境)它內部的tcp時間戳是不一致的(tcp的時間戳要區別於我們認為的時間戳),所以僅依據時間戳來判斷該time_wait是否可回收,太魯莽了。

  • net.ipv4.tcp_max_tw_buckets=8192

該引數是讓系統來幫我們維護time_wait的狀態最大數量不超過指定值,當系統中存在超過設定值的time_wait狀態時,會主動回收比較老的連線。

len(net.ipv4.ip_local_port_range) - net.ipv4.tcp_max_tw_buckets > 0 那不就是意味著系統一直會有可用埠了。