談談 TCP 的 TIME_WAIT

由來


最近有同事在用 ab 進行服務壓測,到 QPS 瓶頸後懷疑是起壓機的問題,來跟我借測試機,因而我就趁機分析了一波起壓機可能成爲壓測瓶頸的可能,除了網絡 I/O、機器性能外,還考慮到了網絡協議的問題。git

固然本文的主角並非壓測,後來分析證實同事果真仍是想多了,瓶頸是在服務端。github

分析起壓機瓶頸的過程當中,對於 TCP TIME_WAIT 狀態的一個猜測引發了個人興趣。因爲以前排查問題時,簡單地接觸過這個狀態,但並未深刻了解,因而決定抽時間分析一下,拆解一下個人猜測。服務器

轉載隨意,文章會持續修訂,請註明來源地址:https://zhenbianshu.github.io 。網絡

TCP 的狀態轉換


咱們都知道 TCP 的三次握手,四次揮手,說來簡單,但在不穩定的物理網絡中,每個動做都有可能失敗,爲了保證數據被有效傳輸,TCP 的具體實現中也加入了不少對這些異常情況的處理。併發

狀態分析

先用一張圖來回想一下 TCP 的狀態轉換。curl

一眼看上去,這麼多種狀態,各個方向的連線,讓人感受有點懵。但細細分析下來,仍是有理可循的。socket

首先,整個圖能夠被劃分爲三個部分,即上半部分建連過程,左下部分主動關閉鏈接過程和右下部分被動關閉鏈接過程。tcp

再來看各個部分:建連過程就是咱們熟悉的三次握手,只是這張圖上多了一個服務端會存在的 LISTEN 狀態;而主動關閉鏈接和被動關閉鏈接,都是四次揮手的過程。高併發

查看鏈接狀態

在 Linux 上,咱們經常使用 netstat 來查看網絡鏈接的狀態。固然咱們還可使用更快捷高效的 ss (Socket Statistics) 來替代 netstat。工具

這兩個工具都會列出此時機器上的 socket 鏈接的狀態,經過簡單的統計就能夠分析出此時服務器的網絡狀態。

TIME_WAIT


定義

咱們從上面的圖中能夠看出來,當 TCP 鏈接主動關閉時,都會通過 TIME_WAIT 狀態。並且咱們在機器上 curl 一個 url 建立一個 TCP 鏈接後,使用 ss 等工具能夠在必定時長內持續觀察到這個連續處於 TIME_WAIT 狀態。

因此TIME_WAIT 是這麼一種狀態:TCP 四次握手結束後,鏈接雙方都再也不交換消息,但主動關閉的一方保持這個鏈接在一段時間內不可用。

那麼,保持這麼一個狀態有什麼用呢?

緣由

上文中提到過,對於複雜的網絡狀態,TCP 的實現提出了多種應對措施,TIME_WAIT 狀態的提出就是爲了應對其中一種異常情況。

爲了理解 TIME_WAIT 狀態的必要性,咱們先來假設沒有這麼一種狀態會致使的問題。暫以 A、B 來代指 TCP 鏈接的兩端,A 爲主動關閉的一端。

  • 四次揮手中,A 發 FIN, B 響應 ACK,B 再發 FIN,A 響應 ACK 實現鏈接的關閉。而若是 A 響應的 ACK 包丟失,B 會覺得 A 沒有收到本身的關閉請求,而後會重試向 A 再發 FIN 包。

    若是沒有 TIME_WAIT 狀態,A 再也不保存這個鏈接的信息,收到一個不存在的鏈接的包,A 會響應 RST 包,致使 B 端異常響應。

    此時, TIME_WAIT 是爲了保證全雙工的 TCP 鏈接正常終止。

  • 咱們還知道,TCP 下的 IP 層協議是沒法保證包傳輸的前後順序的。若是雙方揮手以後,一個網絡四元組(src/dst ip/port)被回收,而此時網絡中還有一個遲到的數據包沒有被 B 接收,A 應用程序又馬上使用了一樣的四元組再建立了一個新的鏈接後,這個遲到的數據包纔到達 B,那麼這個數據包就會讓 B 覺得是 A 剛發過來的。

    此時, TIME_WAIT 的存在是爲了保證網絡中迷失的數據包正常過時。

由以上兩個緣由,TIME_WAIT 狀態的存在是很是有意義的。

時長的肯定

由緣由來推實現,TIME_WAIT 狀態的保持時長也就能夠理解了。肯定 TIME_WAIT 的時長主要考慮上文的第二種狀況,保證關閉鏈接後這個鏈接在網絡中的全部數據包都過時。

說到過時時間,不得不提另外一個概念: 最大分段壽命(MSL, Maximum Segment Lifetime),它表示一個 TCP 分段能夠存在於互聯網系統中的最大時間,由 TCP 的實現,超出這個壽命的分片都會被丟棄。

TIME_WAIT 狀態由主動關閉的 A 來保持,那麼咱們來考慮對於 A 來講,可能接到上一個鏈接的數據包的最大時長:A 剛發出的數據包,能保持 MSL 時長的壽命,它到了 B 端後,B 端因爲關閉鏈接了,會響應 RST 包,這個 RST 包最長也會在 MSL 時長後到達 A,那麼 A 端只要保持 TIME_WAIT 到達 2MS 就能保證網絡中這個鏈接的包都會消失。

MSL 的時長被 RFC 定義爲 2分鐘,但在不一樣的 unix 實現上,這個值不併肯定,咱們經常使用的 centOS 上,它被定義爲 30s,咱們能夠經過 /proc/sys/net/ipv4/tcp_fin_timeout 這個文件查看和修改這個值。

ab 的」奇怪」表現


猜測

由上文,咱們知道因爲 TIME_WAIT 的存在,每一個鏈接被主動關閉後,這個鏈接就要保留 2MSL(60s) 時長,一個網絡四元組也要被凍結 60s。而咱們機器默承認被分配的端口號約有 30000 個(可經過 /proc/sys/net/ipv4/ip_local_port_range文件查看)。

那麼若是咱們使用 curl 對服務器請求時,做爲客戶端,都要使用本機的一個端口號,全部的端口號分配到 60s 內,每秒就要控制在 500 QPS,再多了,系統就沒法再分配端口號了。

但是在使用 ab 進行壓測時時,以每秒 4000 的 QPS 運行幾分鐘,起壓機照樣正常工做,使用 ss 查看鏈接詳情時,發現一個 TIME_WAIT 狀態的鏈接都沒有。

分析

一開始我覺得是 ab 使用了鏈接複用等技術,仔細查看了 ss 的輸出發現本地端口號一直在變,究竟是怎麼回事呢?

因而,我在一臺測試機啓動了一個簡單的服務,端口號 8090,而後在另外一臺機器上起壓,並同時用 tcpdump 抓包。

結果發現,第一個 FIN 包都是由服務器發送的,即 ab 不會主動關閉鏈接。

登上服務器一看,果真,有大量的 TIME_WAIT 狀態的鏈接。

可是因爲服務器監聽的端口會複用,這些 TIME_WAIT 狀態的鏈接並不會對服務器形成太大影響,只是會佔用一些系統資源。

小結


固然,高併發狀況下,太多的 TIME_WAIT 也會給服務器形成很大的壓力,畢竟維護這麼多 socket 也是要消耗資源的,關於如何解決 TIME_WAIT 過多的問題,能夠看 tcp短鏈接TIME_WAIT問題解決方法大全(1)——高屋建瓴

多瞭解原理遇到問題才能更快地找到根源解決,網絡相關的知識還要繼續鞏固啊。

關於本文有什麼疑問能夠在下面留言交流,若是您以爲本文對您有幫助,歡迎關注個人 微博 或 GitHub 。您也能夠在個人 博客REPO 右上角點擊 Watch 並選擇 Releases only 項來 訂閱 個人博客,有新文章發佈會第一時間通知您。

相關文章
相關標籤/搜索