題目與適用情境
一個 Linux API 用戶端沒有重複使用連線,每秒向同一個上游建立 5000 條短 TCP 連線,接著出現連線逾時與大量 TIMEWAIT。請解釋 TCP 如何建立與關閉連線、哪一端進入 TIMEWAIT、為什麼需要它,並提出診斷與修正順序。
主要回答採用以下假設:用戶端只有一個來源 IP,上游位址與連接埠固定;暫時連接埠範圍是 Linux 文件列出的預設值 32768–60999;沒有保留連接埠;封包擷取顯示用戶端先送出 FIN,而且 TIME_WAIT 約持續 60 秒。最後兩項是題目提供的現場證據,不是所有系統的固定行為。
這題適合後端、基礎架構、SRE、用戶端與通用軟體工程職缺。面試官要看的不只是背誦三向交握與關閉流程,而是候選人能否從 TCP 狀態、四元組容量與封包證據,區分正常回收、暫時連接埠耗盡、掉包與上游過載。
面試官考察重點
第一,能否說明三向交握同步的是雙方初始序號。SYN 會占用一個序號,接收端用確認號表示下一個預期序號;第三個封包確認發起端已收到對方的初始序號。只說出 SYN、SYN+ACK、ACK 的順序,還沒有回答為什麼需要三次。
第二,能否把全雙工關閉對應到狀態。兩個方向可以各自停止傳送,因此正常關閉常寫成 FIN → ACK → FIN → ACK。ACK 也可能和 FIN 合併,封包擷取不一定永遠看到四個獨立封包。完整回答會說清楚 FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、LAST-ACK 與 TIME-WAIT 各自在等什麼。
第三,能否判斷哪一端進入 TIMEWAIT。通常是主動關閉並送出最後 ACK 的一端;可能是用戶端,也可能是伺服器。若雙方同時主動關閉,兩端都可能進入 TIMEWAIT。不能只依用戶端或伺服器角色下結論。
第四,能否避免把大量 TIMEWAIT 直接判定為故障。短連線很多時,大量 TIMEWAIT 可以是協定正常運作的結果。只有它和有限四元組、連線建立速率、錯誤型態及封包證據一起出現時,才能推導連接埠耗盡或其他瓶頸。
第五,能否提出風險逐步增加的修正順序。優先減少新連線,其次檢查容量與網路路徑,最後才評估核心參數。直接縮短狀態、降低 bucket 上限或用 RST 避開正常關閉,可能把可靠性問題藏成偶發資料錯誤。
回答前需要釐清的問題
- 哪一端主動關閉? 封包中先送
FIN的一端通常走主動關閉路徑。若上游主動關閉,就不能用「用戶端總是主動關閉」解釋用戶端上的TIME_WAIT。 - 「連線逾時」對應什麼錯誤? 本機連接埠配置失敗常是
EADDRNOTAVAIL等立即錯誤;長時間停在SYN-SENT並重傳,比較像網路路徑、過濾規則、listen backlog 或上游回應問題。錯誤碼與耗時分布會改變排查方向。 - 連線真的不能重複使用嗎? HTTP keep-alive、連線池、HTTP/2 多工或長連線可以直接降低交握與關閉次數。只有協定或上游強制每個請求一條連線時,才需要進入容量擴充方案。
- 連接埠限制發生在哪一層? 主機有本機暫時連接埠範圍;NAT、代理或負載平衡器也有自己的公網位址與連接埠映射池。主機沒有耗盡,不代表出口設備沒有耗盡。
- 目標是否固定? TCP 連線由來源位址、來源連接埠、目標位址與目標連接埠識別。同一個本機連接埠可連往不同目標,因此集中連同一上游比總連線數更容易形成四元組壓力。
- 系統的實際設定為何? 連接埠範圍、保留連接埠、TCP timestamp、重複使用策略與核心版本都會影響容量。題目的預設值只適合第一輪估算,正式結論必須讀取現場設定。
30 秒回答架構
「三向交握讓雙方交換並確認各自的初始序號:用戶端送 SYN,伺服器用 SYN+ACK 確認用戶端序號並提供自己的序號,用戶端再回 ACK。正常關閉分成兩個方向,所以常見 FIN、ACK、FIN、ACK;先主動關閉且送出最終 ACK 的一端通常進入 TIME_WAIT,用來重新確認對方重傳的 FIN,並隔離上一條連線的延遲重複封包。
大量 TIMEWAIT 本身不代表資源洩漏。我會先看錯誤碼、SYN-SENT、封包與連接埠範圍。題目中單一來源 IP 連同一上游只有 28232 個預設暫時連接埠,而每秒 5000 條新連線、觀察到約 60 秒 TIMEWAIT,需求量約 30 萬個四元組,明顯高於供給。修正優先使用連線池或多工,其次確認 NAT 與連接埠容量,再謹慎擴大範圍或來源位址池;不會先強制刪除 TIMEWAIT 或隨意修改 tcptw_reuse。」
分步深入解析
第一步:用序號解釋三向交握
假設用戶端初始序號為 x,伺服器初始序號為 y:
| 封包 | 關鍵欄位 | 狀態變化 | 證明內容 | |---|---|---|---| | 用戶端 → 伺服器 | SYN, seq=x | 用戶端進入 SYN-SENT | 用戶端提出連線並提供自己的初始序號 | | 伺服器 → 用戶端 | SYN, ACK, seq=y, ack=x+1 | 伺服器進入 SYN-RECEIVED | 伺服器收到用戶端 SYN,並提供自己的初始序號 | | 用戶端 → 伺服器 | ACK, ack=y+1 | 雙方進入 ESTABLISHED | 用戶端收到伺服器 SYN,雙方完成序號同步 |
SYN 占用一個序號,因此確認號是 x+1 或 y+1。純 ACK 不占序號空間。三向交握的核心是雙方都送出自己的初始序號、收到對方的初始序號,並取得對方確認;同時降低舊的重複連線要求被誤認成新連線的風險。
兩次交換只能讓發起端看到伺服器的確認,伺服器還沒收到「用戶端確實收到伺服器初始序號」的確認。把原因簡化成「測試雙向網路都有通」不夠精確,因為 TCP 真正維護的是可確認、可重傳的序號空間與連線狀態。
第二步:從兩個獨立方向推導關閉狀態
TCP 是全雙工位元組流。一端關閉傳送方向,表示自己不再傳資料,但仍可能接收對方尚未送完的資料。因此正常關閉要分別終止兩個方向:
| 主動關閉端 | 被動關閉端 | 意義 | |---|---|---| | 送出 FIN,進入 FIN-WAIT-1 | 收到 FIN,回 ACK,進入 CLOSE-WAIT | 主動端停止傳送;被動端應用程式仍可送出剩餘資料 | | 收到 ACK,進入 FIN-WAIT-2 | 應用程式完成後送 FIN,進入 LAST-ACK | 主動端等待對方關閉另一個方向 | | 收到 FIN,回最終 ACK,進入 TIME-WAIT | 收到最終 ACK,進入 CLOSED | 兩個方向完成正常關閉 |
CLOSE-WAIT 長時間累積,通常代表應用程式收到對方 FIN 後沒有及時關閉自己的 socket;原因和 TIME_WAIT 不同。FIN-WAIT-2 長時間累積,則表示主動端的 FIN 已確認,但仍在等對方 FIN。面試時把所有關閉狀態統稱為「連線沒有釋放」,會失去診斷價值。
四段關閉交換是方便記憶的正常流程,不是固定封包數。被動端若已沒有資料,可把 ACK 和自己的 FIN 合在同一個封包;雙方同時關閉時還會進入 CLOSING,並可能兩端都進入 TIME-WAIT。
第三步:解釋 TIME_WAIT 的兩個職責
主動關閉端送出最終 ACK 後,不能立刻忘記連線,主要有兩個原因。
第一,最終 ACK 可能遺失。被動關閉端收不到 ACK 會重傳 FIN;仍在 TIME_WAIT 的主動端可以再次確認。若狀態立刻刪除,重傳 FIN 可能收到 RST,正常關閉的可靠性會被破壞。
第二,網路中可能還有舊連線的延遲或重複封包。TCP 連線以四元組識別;若相同四元組立刻給新連線使用,舊封包可能和新連線的序號空間衝突。RFC 規定主動關閉的連線維持 TIME-WAIT 達 2 × MSL,讓舊封包消失,也保留最終 ACK 重傳的時間。RFC 1337 進一步說明,過早終止這個狀態會重新暴露舊重複資料、失去同步與錯誤 ACK 等風險。
所以,TIMEWAIT 是可靠性保護,不等於記憶體洩漏。診斷時應先問「為什麼建立這麼多新連線、四元組供給是否足夠、哪一端主動關閉」,不能先問「如何把 TIMEWAIT 歸零」。
第四步:估算題目的暫時連接埠壓力
Linux 文件列出的預設 iplocalport_range 是 32768–60999。在題目假設沒有保留連接埠時,自動配置可用數量是:
60999 - 32768 + 1 = 28232對單一來源 IP、單一目標 IP 與目標連接埠,若舊四元組尚不能安全重複使用,最多約有 28232 個來源連接埠槽位。每秒 5000 條新連線,代表約 5.65 秒就會走過同等數量的連接埠:
28232 / 5000 ≈ 5.65 秒題目又提供約 60 秒的現場 TIME_WAIT 觀察值,粗略需求是:
5000 × 60 = 300000 條近期關閉的連線30 萬遠高於 28232,因此「單一來源 IP、單一上游、沒有連線重複使用」是明確容量風險。這個估算不代表系統必定在第 5.65 秒失敗:核心安全重複使用、並行已建立連線、保留連接埠、連線持續時間與 NAT 行為都會改變實際值。它的用途是證明數量級不相稱,帶出下一步取證。
同一來源連接埠可以連不同目標,因此不能直接拿全機新連線總數除以連接埠數。若出口經過 NAT,真正稀缺的也可能是 NAT 公網 IP 到目標的映射四元組,而不是應用主機的本機連接埠。
第五步:用狀態、錯誤碼與封包分離根因
先蒐集不改變系統行為的證據:
ss -s
ss -Htan state syn-sent | wc -l
ss -Htan state time-wait | wc -l
ss -Htan state close-wait | wc -l
cat /proc/sys/net/ipv4/ip_local_port_range
cat /proc/sys/net/ipv4/ip_local_reserved_ports
sysctl net.ipv4.tcp_tw_reuse再把現象分成幾條路徑:
| 證據 | 較可能方向 | 下一步 | |---|---|---| | connect() 很快回報本機位址不可用,且沒有送出 SYN | 暫時連接埠或本機綁定資源不足 | 依目標統計新連線,檢查連接埠範圍、保留連接埠、來源 IP 與 NAT | | 大量 SYN-SENT,封包顯示 SYN 重傳但沒有 SYN+ACK | 網路掉包、ACL、上游未回應或 listen backlog 壓力 | 在用戶端與上游同時擷取封包,找出封包消失位置 | | 大量 CLOSE-WAIT | 本機應用程式沒有完成被動關閉 | 檢查檔案描述元、請求取消路徑與例外處理 | | 大量 TIME_WAIT,但沒有錯誤且連接埠餘裕足夠 | 短連線的正常結果 | 持續觀察,不為了降低數字而調參數 | | 主機連接埠充足,NAT 後多個實例同時失敗 | 出口 NAT 映射池可能耗盡 | 查看 NAT 指標、公網來源位址數量與目標熱點 |
封包擷取要確認三件事:哪一端先送 FIN、失敗連線是否真的送出 SYN、重傳發生在哪一側。只看 ss 總數不能證明連接埠耗盡,只看應用程式的 timeout 字串也不能證明網路掉包。
第六步:依風險由低到高修正
首選是減少連線建立。啟用並正確設定連線池、HTTP keep-alive、HTTP/2 多工或適用的長連線,讓多個請求共用已建立連線。這會同時減少交握 RTT、CPU、暫時連接埠與 TIME_WAIT,直接處理根因。
第二,修正連線生命週期。不要在每個請求後主動關閉健康連線;為連線池設定合理的最大連線數、閒置逾時、最大生命週期與上游並行限制。若伺服器主動關閉過快,也要檢查 keep-alive、負載平衡閒置逾時與滾動部署行為。目標是降低無意義的 churn,不是把主動關閉責任機械式地移到另一端。
第三,連線重複使用仍不足時再擴大四元組供給。確認保留連接埠後,可評估擴大本機暫時連接埠範圍;對單一目標熱點,也可以增加來源 IP 或分散目標位址。若經過 NAT,必須同步擴大或分割 NAT 公網位址池,否則應用主機增加來源 IP 可能沒有作用。
最後才評估核心重複使用參數。Linux 文件明確說明 tcptwreuse 只在協定安全時重複使用 TIMEWAIT,而且不應在沒有技術專家建議時修改。tcpmaxtwbuckets 是防止簡單阻斷服務攻擊的保護上限,文件也明確警告不要人為降低;超過上限會立刻刪除 time-wait socket 並記錄警告。參數變更必須有核心版本、timestamp 行為、對端特徵、壓力測試與回復依據。
不要把 RST 當成通用最佳化。RST 是中止連線,會立即丟棄狀態;應用程式尚未確認的資料可能遺失。也不要把「讓伺服器先關」當成萬用方案,它只是把 TIME_WAIT 壓力移到另一端,還可能製造伺服器連接埠或記憶體壓力。
第七步:驗證修正是否真的有效
修正後要同時驗證容量、正確性與網路證據:
- 用相同流量重播,比較每秒新建 TCP 連線、連線重複使用率與請求吞吐。
- 觀察
SYN-SENT、TIME_WAIT、CLOSE-WAIT、檔案描述元與本機連接埠使用趨勢,不只看單一時間點。 - 對失敗樣本擷取封包,確認是否送出 SYN、是否收到 SYN+ACK、哪一端主動關閉。
- 檢查用戶端、NAT、負載平衡器與上游四層指標,避免只修主機而漏掉出口瓶頸。
- 執行長時間穩定性測試,確認沒有資料截斷、RST 增加、尾端延遲惡化或連線池陳舊連線。
成功標準是在相同業務吞吐下,新連線顯著下降、錯誤消失、連接埠餘裕穩定,而且請求正確性沒有退化。TIME_WAIT 數量降低是結果,不是唯一目標。
高品質示範回答
「我會先確認兩個現場事實:哪一端先送 FIN,以及所謂逾時是本機立即報錯,還是 SYN 送出後一直沒有回應。因為 TIME_WAIT 通常屬於主動關閉並送出最終 ACK 的一端,而連線逾時不一定由連接埠耗盡造成。
建立連線時,用戶端用 SYN 提供初始序號 x;伺服器用 SYN+ACK 確認 x+1,同時提供自己的序號 y;用戶端再確認 y+1。這樣雙方都交換並確認序號空間,也能辨識舊的重複連線要求。關閉時兩個傳送方向獨立結束,所以常見 FIN、ACK、FIN、ACK。先主動關閉的一端經過 FIN-WAIT-1 與 FIN-WAIT-2,收到對方 FIN 後送出最終 ACK,並進入 TIME_WAIT。它保留狀態,一方面能在最終 ACK 遺失時再次確認重傳 FIN,另一方面避免舊連線的延遲重複封包污染立即重複使用的同一四元組。
題目的容量已經值得懷疑。預設暫時連接埠從 32768 到 60999,共 28232 個。單一來源 IP 持續連同一目標,每秒建立 5000 條連線;現場又觀察到約 60 秒 TIME_WAIT,粗估會累積 30 萬條近期關閉連線,遠高於連接埠槽位。不過我不會只靠估算定案。我會看錯誤碼:如果 connect 立即回報本機位址不可用而且沒有 SYN,優先檢查連接埠和 NAT;如果大量 SYN-SENT 並持續重傳,優先檢查網路路徑、ACL、listen backlog 與上游。
修正先從連線池、keep-alive 或 HTTP/2 多工開始,降低每秒新連線。接著核對連線池閒置逾時、上游關閉策略與 NAT 連接埠池。若業務仍需要很高的連線建立率,再評估擴大暫時連接埠範圍、增加來源位址或分散目標。tcptwreuse 只有協定安全時才能使用,Linux 文件也要求謹慎;我不會先縮短 TIME_WAIT、降低 bucket 上限或用 RST 強制釋放狀態。最後用相同流量驗證新連線率、錯誤、各 TCP 狀態、封包與 NAT 指標,確保沒有用可靠性換容量。」
常見錯誤
- 把三向交握說成「互相打招呼」 → 沒有解釋序號與確認號,無法回答兩次為何不足 → 用雙方初始序號與第三次確認推導。
- 認定用戶端一定進入 TIME_WAIT → 狀態由主動關閉角色決定,不由用戶端標籤決定 → 從第一個 FIN 與最終 ACK 判斷。
- 把關閉流程當成固定四個封包 → ACK 可和 FIN 合併,雙方也可能同時關閉 → 描述兩個獨立方向與狀態轉換。
- 看到大量 TIME_WAIT 就判定記憶體洩漏 → 短連線自然產生這個狀態 → 結合新連線速率、連接埠範圍、錯誤碼與封包判斷容量。
- 把所有 connect timeout 都歸因於連接埠耗盡 → SYN 掉包、ACL、上游過載與 listen backlog 也會逾時 → 先區分本機立即失敗和 SYN 重傳。
- 先降低 tcpmaxtw_buckets → 超過上限會提前刪除狀態,而且 Linux 文件明確反對人為降低 → 先減少連線 churn,再擴充容量。
- 用 RST 取代正常關閉 → 立即刪除狀態可能丟棄尚未可靠送達的資料 → 保留正常 FIN 關閉,只在真正異常中止時使用 RST。
- 只擴大應用主機連接埠範圍 → 瓶頸可能在 NAT 的公網映射池 → 沿來源主機、出口設備與上游逐層核對四元組容量。
追問與應對
追問一:最後一個 ACK 遺失時,主動關閉端為什麼還能補救?
被動關閉端在 LAST-ACK 等自己的 FIN 被確認。收不到最終 ACK 時,它會重傳 FIN。主動關閉端仍保留 TIME_WAIT,辨識重傳 FIN 後再次送 ACK,並重新開始等待計時。若主動端已忘記連線,重傳 FIN 可能觸發 RST,正常關閉的可靠性就無法維持。
追問二:伺服器出現大量 CLOSEWAIT,和 TIMEWAIT 有什麼差別?
CLOSE-WAIT 表示對方已送 FIN,本機 TCP 也已通知應用程式,但應用程式尚未關閉自己的傳送方向;本機應用程式仍可能送出剩餘資料。它通常指向應用程式生命週期、例外路徑或檔案描述元管理問題。TIME_WAIT 則表示主動關閉端已完成報文交換,正在保護最終 ACK 與舊封包隔離。兩者都位於關閉階段,但根因、是否仍可傳送資料與修正方式不同。
追問三:為什麼同一個來源連接埠有時能同時用於多條連線?
連線身分由四元組共同決定。只要目標位址或目標連接埠不同,同一個本機連接埠可以屬於不同連線。因此暫時連接埠壓力必須依來源 IP 與目標熱點分析。大量請求分散到不同目標時,總連線數可高於單一連接埠範圍;集中到同一上游時,可用四元組會更快耗盡。
追問四:把用戶端擴成十個實例,一定能解決連接埠耗盡嗎?
不一定。每個實例若有獨立來源 IP,主機側四元組供給會增加;但所有實例若經過同一個只有少量公網 IP 的 NAT,壓力會在 NAT 匯合。還要檢查上游是否按來源 IP 限流,以及更多並行連線是否超過上游的 listen backlog、檔案描述元或負載平衡容量。
追問五:什麼時候可以考慮 tcptwreuse?
先證明連線重複使用、連線池設定、連接埠範圍與 NAT 容量仍不足,再核對實際核心版本、TCP timestamp 與對端行為。這個參數只允許在協定判定安全時重複使用,Linux 文件明確要求沒有專家建議不要修改。變更前要壓測重新連線與資料正確性,監控 RST、失敗率和延遲,並準備立即回復;它不能取代應用層連線重複使用。