被拋棄的tcp_recycle_小米雲技術-CSDN博客_sysctl: cannot stat /proc/sys/net/ipv4/tcp_tw_recy https://blog.csdn.net/pengzhouzhou/article/details/85229437
1、背景
最近准備搭建一個新的kubernetes集群,將內核從3.18更新到了4.14版本,並執行一些常規的優化操作。在執行sysctl -p操作時突然報錯如下:
sysctl: cannot stat /proc/sys/net/ipv4/tcp_tw_recycle: No such file or directory
2、問題原因
Linux 從4.12內核版本開始移除了 tcp_tw_recycle 配置。
參考:[1]tcp:remove tcp_tw_recycle 4396e460
移除sysctl.conf中關於net.ipv4.tcp_tw_recycle的配置內容,再次嘗試sysctl -p就不再提示報錯了。
3、深入解析
tcp_tw_recycle通常會和tcp_tw_reuse參數一起使用,用於解決服務器TIME_WAIT狀態連接過多的問題。
3.1、TIME_WAIT狀態出現原因與查看
讓我們回顧一下四次揮手的流程:
TIME_WAIT永遠是出現在主動發送斷開連接請求的一方(下文中我們稱之為客戶),划重點:這一點面試的時候經常會被問到。
客戶在收到服務器端發送的FIN(表示"我們也要斷開連接了")后發送ACK報文,並且進入TIME_WAIT狀態,等待2MSL(MaximumSegmentLifetime 最大報文生存時間)。對於Linux,字段為TCP_TIMEWAIT_LEN硬編碼為30秒,對於windows為2分鍾(可自行調整)。
為什么客戶端不直接進入CLOSED狀態,而是要在TIME_WAIT等待那么久呢,基於如下考慮:
1.確保遠程端處於關閉狀態。也就是說需要確保客戶端發出的最后一個ACK報文能夠到達服務器。由於網絡不可靠,有可能最后一個ACK報文丟失,如果服務器沒有收到客戶端的ACK,則會重新發送FIN報文,客戶端就可以在2MSL時間段內收到這個這個重發的報文,並且重發ACK報文。但如果客戶端跳過TIME_WAIT階段進入了CLOSED,服務端始終無法得到響應,就會處於LAST-ACK狀態,此時假如客戶端發起了一個新連接,則會以失敗告終。
異常流程如下:
2.防止上一次連接中的包,迷路后重新出現,影響新連接(經過2MSL,上一次連接中所有的重復包都會消失),這一點和為啥要執行三次握手而不是兩次的原因是一樣的。
異常流程如下:
查看方式有兩種:
(1)ss -tan state time-wait|wc -l
(2)netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
3.2、TIME_WAIT的危害
對於一個處理大量連接的處理器TIME_WAIT是有危害的,表現如下:
1.占用連接資源
TIME_WAIT占用的1分鍾時間內,相同四元組(源地址,源端口,目標地址,目標端口)的連接無法創建,通常一個ip可以開啟的端口為net.ipv4.ip_local_port_range指定的32768-61000,如果TIME_WAIT狀態過多,會導致無法創建新連接。
2.占用內存資源
這個占用資源並不是很多,可以不用擔心。
3.3、TIME_WAIT的解決
可以考慮如下方式:
1.修改為長連接,代價較大,長連接對服務器性能有影響。
2.增加可用端口范圍(修改net.ipv4.ip_local_port_range); 增加服務端口,比如采用80,81等多個端口提供服務; 增加客戶端ip(適用於負載均衡,比如nginx,采用多個ip連接后端服務器); 增加服務端ip; 這些方式治標不治本,只能緩解問題。
3.將net.ipv4.tcp_max_tw_buckets設置為很小的值(默認是18000). 當TIME_WAIT連接數量達到給定的值時,所有的TIME_WAIT連接會被立刻清除,並打印警告信息。但這種粗暴的清理掉所有的連接,意味着有些連接並沒有成功等待2MSL,就會造成通訊異常。
4.修改TCP_TIMEWAIT_LEN值,減少等待時間,但這個需要修改內核並重新編譯。
5.打開tcp_tw_recycle和tcp_timestamps選項。
6.打開tcp_tw_reuse和tcp_timestamps選項。
3.4、net.ipv4.tcp_tw_{reuse,recycle}
需要明確兩個點:
解決方式已經給出,那我們需要了解一下net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle有啥區別
1.兩個選項都需要打開對TCP時間戳的支持,即net.ipv4.tcp_timestamps=1(默認即為1)。
RFC 1323中實現了TCP拓展規范,以便保證網絡繁忙的情況下的高可用。並定義了一個新的TCP選項-兩個四字節的timestamp字段,第一個是TCP發送方的當前時鍾時間戳,第二個是從遠程主機接收到的最新時間戳。
2.兩個選項默認都是關閉狀態,即等於0。
3.4.1 - net.ipv4.tcp_tw_reuse:更安全的設置
將處於TIME_WAIT狀態的socket用於新的TCP連接,影響連出的連接。
[2]kernel sysctl 官方指南中是這么寫的:
Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.
It should not be changed without advice/request of technical experts.
協議安全主要指的是兩點:
1.只適用於客戶端(連接發起方)
net/ipv4/inet_hashtables.c
-
static int __inet_check_established(struct inet_timewait_death_row *death_row,
-
struct sock *sk, __u16 lport,
-
struct inet_timewait_sock **twp)
-
{
-
/* ……省略…… */
-
sk_nulls_for_each(sk2, node, &head->chain) {
-
if (sk2->sk_hash != hash)
-
continue;
-
-
if (likely(INET_MATCH(sk2, net, acookie,
-
saddr, daddr, ports, dif))) {
-
if (sk2->sk_state == TCP_TIME_WAIT) {
-
tw = inet_twsk(sk2);
-
if (twsk_unique(sk, sk2, twp))
-
break;
-
}
-
goto not_unique;
-
}
-
}
-
/* ……省略…… */
-
}
2.TIME_WAIT創建時間超過1秒才可以被復用
net/ipv4/tcp_ipv4.c
-
int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
-
{
-
/* ……省略…… */
-
if (tcptw->tw_ts_recent_stamp &&
-
(!twp || (sock_net(sk)->ipv4.sysctl_tcp_tw_reuse &&
-
get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {
-
/* ……省略…… */
-
return 1;
-
}
-
return 0;
-
}
滿足以上兩個條件才會被認為是"safe from protocol viewpoint"的狀況。啟用net.ipv4.tcp_tw_reuse后,如果新的時間戳比之前存儲的時間戳更大,那么Linux將會從TIME-WAIT狀態的存活連接中選取一個,重新分配給新的連接出去的的TCP連接,這種情況下,TIME-WAIT的連接相當於只需要1秒就可以被復用了。
重新回顧為什么要引入TIME-WAIT:
第一個作用就是避免新連接接收到重復的數據包,由於使用了時間戳,重復的數據包會因為時間戳過期被丟棄。
第二個作用是確保遠端不是處於LAST-ACK狀態,如果ACK包丟失,遠端沒有成功獲取到最后一個ACK包,則會重發FIN包。直到:
1.放棄(連接斷開)
2.收到ACK包
3.收到RST包
如果FIN包被及時接收到,並且本地端仍然是TIME-WAIT狀態,那ACK包會被發送,此時就是正常的四次揮手流程。
如果TIME-WAIT的條目已經被新連接所復用,則新連接的SYN包會被忽略掉,並且會收到FIN包的重傳,本地會回復一個RST包(因為此時本地連接為SYN-SENT狀態),這會讓遠程端跳出LAST-ACK狀態,最初的SYN包也會在1秒后重新發送,然后完成連接的建立,整個過程不會中斷,只是有輕微的延遲。流程如下:
需要注意,連接被復用后,TWrecycled計數器會增加(/proc/net/netstat中TWrecycled值)
3.4.2 - net.ipv4.tcp_tw_recycle:更激進的設置
啟用TIME_WAIT 狀態的sockets的快速回收,影響所有連入和連出的連接
[3]kernel sysctl 官方指南 是這么寫的
Enable fast recycling TIME-WAIT sockets. Default value is 0. It should not be changed without advice/request of technical experts.
這次表述的更加模糊,繼續翻看源碼:
net/ipv4/tcp_input.c
-
int tcp_conn_request(struct request_sock_ops *rsk_ops,
-
const struct tcp_request_sock_ops *af_ops,
-
struct sock *sk, struct sk_buff *skb)
-
{
-
/* ……省略…… */
-
if (!want_cookie && !isn) {
-
/* ……省略…… */
-
if (net->ipv4.tcp_death_row.sysctl_tw_recycle) {
-
bool strict;
-
-
dst = af_ops->route_req(sk, &fl, req, &strict);
-
-
if (dst && strict &&
-
!tcp_peer_is_proven(req, dst, true,
-
tmp_opt.saw_tstamp)) {
-
NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
-
goto drop_and_release;
-
}
-
}
-
/* ……省略…… */
-
isn = af_ops->init_seq(skb, &tcp_rsk(req)->ts_off);
-
}
-
/* ……省略…… */
-
-
drop_and_release:
-
dst_release(dst);
-
drop_and_free:
-
reqsk_free(req);
-
drop:
-
tcp_listendrop(sk);
-
return 0;
-
}
簡單來說就是,Linux會丟棄所有來自遠端的timestramp時間戳小於上次記錄的時間戳(由同一個遠端發送的)的任何數據包。也就是說要使用該選項,則必須保證數據包的時間戳是單調遞增的。
問題在於,此處的時間戳並不是我們通常意義上面的絕對時間,而是一個相對時間。很多情況下,我們是沒法保證時間戳單調遞增的,比如使用了nat,lvs等情況。
而這也是很多優化文章中並沒有提及的一點,大部分文章都是簡單的推薦將net.ipv4.tcp_tw_recycle設置為1,卻忽略了該選項的局限性,最終造成嚴重的后果(比如我們之前就遇到過部署在nat后端的業務網站有的用戶訪問沒有問題,但有的用戶就是打不開網頁)。
3.5、被拋棄的tcp_tw_recycle
如果說之前內核中tcp_tw_recycle僅僅不適用於nat和lvs環境,那么從4.10內核開始,官方修改了時間戳的生成機制。
參考:[4] tcp: randomize tcp timestamp offsets for each connection 95a22ca
在這種情況下,無論任何時候,tcp_tw_recycle都不應該開啟。故被拋棄也是理所應當的了。
4、總結
-
tcp_tw_recycle 選項在4.10內核之前還只是不適用於NAT/LB的情況(其他情況下,我們也非常不推薦開啟該選項),但4.10內核后徹底沒有了用武之地,並且在4.12內核中被移除.
-
tcp_tw_reuse 選項仍然可用。在服務器上面,啟用該選項對於連入的TCP連接來說不起作用,但是對於客戶端(比如服務器上面某個服務以客戶端形式運行,比如nginx反向代理)等是一個可以考慮的方案。
-
修改TCP_TIMEWAIT_LEN是非常不建議的行為。
5、參考鏈接
[1]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4396e46187ca5070219b81773c4e65088dac50cc
[2]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/networking/ip-sysctl.txt?h=v4.11#n648
[3]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/networking/ip-sysctl.txt?h=v4.11#n643
[4]https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95a22caee396cef0bb2ca8fafdd82966a49367bb
[5]Coping with the TCP TIME-WAIT state on busy Linux servers:https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux
[6]net.ipv4.tcp_tw_recycle は廃止されました ― その危険性を理解する:https://qiita.com/tmshn/items/b49f1b51bfc472968b30
[7]tcp_tw_reuse、tcp_tw_recycle 使用場景及注意事項:https://www.cnblogs.com/lulu/p/4149312.html
本文首發於公眾號“小米運維”,點擊查看原文。
net.ipv4.tcp_max_tw_buckets
TIME-WAIT 穩定至指定值
(base) root@sit:~# ss -atp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 0.0.0.0:https 0.0.0.0:* users:(("nginx",pid=5808,fd=9),("nginx",pid=5807,fd=9),("nginx",pid=1485,fd=9))
LISTEN 0 1024 0.0.0.0:ldap 0.0.0.0:* users:(("slapd",pid=1265,fd=8))
LISTEN 0 128 0.0.0.0:6000 0.0.0.0:* users:(("python3",pid=3926,fd=5))
LISTEN 0 511 0.0.0.0:http 0.0.0.0:* users:(("nginx",pid=5808,fd=10),("nginx",pid=5807,fd=10),("nginx",pid=1485,fd=10))
LISTEN 0 50 0.0.0.0:8080 0.0.0.0:* users:(("java",pid=1331,fd=147))
LISTEN 0 128 127.0.0.53%lo:domain 0.0.0.0:* users:(("systemd-resolve",pid=423,fd=13))
LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:* users:(("sshd",pid=1018,fd=3))
ESTAB 0 0 192.168.0.133:https 121.35.102.243:33515 users:(("nginx",pid=5807,fd=19))
TIME-WAIT 0 0 127.0.0.1:31126 127.0.0.1:25001
ESTAB 0 0 192.168.0.133:36390 100.100.27.15:3128 users:(("exe",pid=771,fd=5))
ESTAB 0 0 127.0.0.1:44230 127.0.0.1:25000 users:(("nginx",pid=5807,fd=21))
ESTAB 0 0 192.168.0.133:https 121.35.102.243:33348 users:(("nginx",pid=5807,fd=4))
TIME-WAIT 0 0 172.17.0.1:34092 172.17.0.3:5000
ESTAB 0 0 172.17.0.1:55194 172.17.0.2:5000 users:(("docker-proxy",pid=30859,fd=25))
ESTAB 0 0 192.168.0.133:https 121.35.102.243:33503 users:(("nginx",pid=5807,fd=8))
TIME-WAIT 0 0 172.17.0.1:56484 172.17.0.2:5000
TIME-WAIT 0 0 172.17.0.1:54188 172.17.0.4:5000
ESTAB 0 36 192.168.0.133:ssh 121.35.102.243:33769 users:(("sshd",pid=30622,fd=3))
ESTAB 0 0 192.168.0.133:https 121.35.102.243:30059 users:(("nginx",pid=5807,fd=22))
ESTAB 0 0 127.0.0.1:43168 127.0.0.1:25000 users:(("nginx",pid=5807,fd=5))
TIME-WAIT 0 0 172.17.0.1:34110 172.17.0.3:5000
ESTAB 0 0 172.17.0.1:54132 172.17.0.2:5000 users:(("docker-proxy",pid=30859,fd=13))
ESTAB 0 0 127.0.0.1:43162 127.0.0.1:25000 users:(("nginx",pid=5807,fd=18))
ESTAB 0 0 172.17.0.1:54144 172.17.0.2:5000 users:(("docker-proxy",pid=30859,fd=19))
ESTAB 0 0 192.168.0.133:https 121.35.102.243:31215 users:(("nginx",pid=5807,fd=17))
ESTAB 0 0 192.168.0.133:https 121.35.102.243:30061 users:(("nginx",pid=5807,fd=24))
TIME-WAIT 0 0 172.17.0.1:33966 172.17.0.3:5000
ESTAB 0 0 192.168.0.133:46810 100.100.30.26:http users:(("AliYunDun",pid=860,fd=17))
TIME-WAIT 0 0 172.17.0.1:55992 172.17.0.2:5000
ESTAB 0 0 172.17.0.1:54126 172.17.0.2:5000 users:(("docker-proxy",pid=30859,fd=5))
TIME-WAIT 0 0 172.17.0.1:34056 172.17.0.3:5000
ESTAB 0 0 192.168.0.133:15770 104.16.22.35:https users:(("node",pid=25216,fd=18))
ESTAB 0 0 127.0.0.1:43180 127.0.0.1:25000 users:(("nginx",pid=5807,fd=20))
LISTEN 0 1024 [::]:ldap [::]:* users:(("slapd",pid=1265,fd=9))
LISTEN 0 65535 *:4999 *:* users:(("docker-proxy",pid=2243,fd=4))
LISTEN 0 65535 *:25000 *:* users:(("docker-proxy",pid=30859,fd=4))
LISTEN 0 65535 *:25001 *:* users:(("docker-proxy",pid=31336,fd=4))
LISTEN 0 65535 *:25002 *:* users:(("docker-proxy",pid=31557,fd=4))
LISTEN 0 80 *:3306 *:* users:(("mysqld",pid=856,fd=29))
LISTEN 0 128 [::]:6000 [::]:* users:(("python3",pid=3926,fd=6))
ESTAB 0 0 [::ffff:127.0.0.1]:25000 [::ffff:127.0.0.1]:43168 users:(("docker-proxy",pid=30859,fd=12))
ESTAB 0 0 [::ffff:127.0.0.1]:25000 [::ffff:127.0.0.1]:44230 users:(("docker-proxy",pid=30859,fd=24))
ESTAB 0 0 [::ffff:127.0.0.1]:25000 [::ffff:127.0.0.1]:43162 users:(("docker-proxy",pid=30859,fd=3))
ESTAB 0 0 [::ffff:127.0.0.1]:25000 [::ffff:127.0.0.1]:43180 users:(("docker-proxy",pid=30859,fd=18))
(base) root@sit:~#
(base) root@sit:~#
(base) root@sit:~# sysctl -p
vm.swappiness = 0
net.ipv4.neigh.default.gc_stale_time = 120
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_synack_retries = 2
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
kernel.sysrq = 1
net.core.somaxconn = 65535
net.ipv4.tcp_fin_timeout = 2
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_max_tw_buckets = 8