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