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 那不就是意味著系統一直會有可用埠了。