被拋棄的tcp_recycle

本文從一次巧合發現高版本Linux再也不支持tcp_tw_recycle,深刻研究了鏈接狀態TIME_WAIT的原理,進而分析了tcp_tw族內核參數和如何應用它們對Linux的鏈接進行調優。
上篇文章回顧: 任播、自治系統與全球負載均衡


一、背景

最近準備搭建一個新的kubernetes集羣,將內核從3.18更新到了4.14版本,並執行一些常規的優化操做。在執行sysctl -p操做時忽然報錯以下:html

sysctl: cannot stat /proc/sys/net/ipv4/tcp_tw_recycle: No such file or directory複製代碼


二、問題緣由

Linux 從4.12內核版本開始移除了 tcp_tw_recycle 配置。node

參考:[1]tcp:remove tcp_tw_recycle 4396e460linux

移除sysctl.conf中關於net.ipv4.tcp_tw_recycle的配置內容,再次嘗試sysctl -p就再也不提示報錯了。nginx


三、深刻解析

tcp_tw_recycle一般會和tcp_tw_reuse參數一塊兒使用,用於解決服務器TIME_WAIT狀態鏈接過多的問題。git


3.一、TIME_WAIT狀態出現緣由與查看

讓咱們回顧一下四次揮手的流程:面試

TIME_WAIT永遠是出如今主動發送斷開鏈接請求的一方(下文中咱們稱之爲客戶),劃重點:這一點面試的時候常常會被問到。
windows

客戶在收到服務器端發送的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狀態,此時假如客戶端發起了一個新鏈接,則會以失敗了結。bash

異常流程以下:

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.二、TIME_WAIT的危害

對於一個處理大量鏈接的處理器TIME_WAIT是有危害的,表現以下:

1.佔用鏈接資源

TIME_WAIT佔用的1分鐘時間內,相同四元組(源地址,源端口,目標地址,目標端口)的鏈接沒法建立,一般一個ip能夠開啓的端口爲net.ipv4.ip_local_port_range指定的32768-61000,若是TIME_WAIT狀態過多,會致使沒法建立新鏈接。

2.佔用內存資源

這個佔用資源並非不少,能夠不用擔憂。


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.四、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.五、被拋棄的tcp_tw_recycle

若是說以前內核中tcp_tw_recycle僅僅不適用於nat和lvs環境,那麼從4.10內核開始,官方修改了時間戳的生成機制。

參考:[4]tcp: randomize tcp timestamp offsets for each connection 95a22ca

在這種狀況下,不管任什麼時候候,tcp_tw_recycle都不該該開啓。故被拋棄也是理所應當的了。


四、總結

  • tcp_tw_recycle 選項在4.10內核以前還只是不適用於NAT/LB的狀況(其餘狀況下,咱們也很是不推薦開啓該選項),但4.10內核後完全沒有了用武之地,而且在4.12內核中被移除.

  • tcp_tw_reuse 選項仍然可用。在服務器上面,啓用該選項對於連入的TCP鏈接來講不起做用,可是對於客戶端(好比服務器上面某個服務以客戶端形式運行,好比nginx反向代理)等是一個能夠考慮的方案。

  • 修改TCP_TIMEWAIT_LEN是很是不建議的行爲。


五、參考連接

[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


本文首發於公衆號「小米運維」,點擊查看原文

相關文章
相關標籤/搜索