綜合演習 — 以封包的視角解讀 TCP 的行為
把模擬實務故障調查的 8 個案例,依照症狀 → 是哪項承諾的話題 → 該看的數字的順序切分。卡住了就回到對應章節複習。
收尾的 8 題 — 把症狀翻譯成「哪項承諾的話題」
最終章是沒有講解的綜合演習。對 8 個模擬實務故障調查的案例,橫跨全部 6 章的知識來作答。迷惘時,請回到本課程的口訣 — 現在快被打破的,是送達的承諾?順序?還是步調的配合?然後把症狀翻譯成數字:是 RST 還是沉默、重複 ACK 的號碼指向哪裡、取 min 的是哪扇窗。
| 卡住的話 | 回去的章節 |
|---|---|
| IP 的性質、TCP/UDP 的分工、四元組 | 第 1 章 |
| 交握、TIME_WAIT、RST 與 FIN | 第 2 章 |
| seq/ack 的計算、RTO、重複 ACK 與快速重傳 | 第 3 章 |
| 視窗通告、in-flight、視窗 ÷ RTT、零視窗 | 第 4 章 |
| cwnd、慢啟動、對遺失的 2 段反應 | 第 5 章 |
| 位元組串流、framing | 第 6 章 |
本課程做出來的切分地圖。依照症狀 → 封包的證據 → 哪項承諾的話題的順序往下走,就能從亂槍打鳥的重啟畢業。
案例 7-1 — 連不上、斷掉
第 1〜2 章的範圍。請翻譯成「是立即的拒絕,還是沉默」「是 FIN 還是 RST」。
Q1. 案例:無法連上新部署的 API 伺服器。從用戶端 A 立刻收到 Connection refused,從用戶端 B 則等了 30 秒後連線逾時。對各自狀況的解讀,正確的是哪一個?
立即的 refused 是 RST 回來的證據 — 已送達主機,卻沒有程式在那個連接埠等待(行程未啟動、連接埠號錯誤等)。沉默到底的逾時是沒有回應回來 — 路徑中斷、主機停機,或默默丟棄的防火牆。從 A 的存在可以縮小到「主機活著、問題在連接埠」「與 B 之間的路徑某處被阻斷」。有送達的拒絕,與沒送達的沉默 — 第 2 章切分的定式。
Q2. 案例:一台經由負載平衡器向後端建立大量短連線的代理伺服器,用 netstat 一看,TIME_WAIT 狀態的連線排了數萬條。對這個狀況的理解,正確的是哪一個?
TIME_WAIT 是先送出 FIN 的一方,為了最後一個 ACK 的保險與舊區段的排毒而留下的正常狀態(第 2 章)。每秒建立又丟棄數百條短命連線,光是數十秒的滯留就會累積成數萬條。危害在於連線資源(連接埠等)的消耗,根本對策是不要每次重新建線而是重複使用。不是「消除異常狀態」,而是「修正讓正常狀態大量發生的設計」,這才是正解。
Q3. 案例:一條連往資料庫的常駐連線,在深夜批次處理睽違數小時送出查詢的瞬間被 RST 切斷。從連線建立到閒置為止沒有任何異常。最可疑的原因是哪一個?
TCP 本身可以無限期維持閒置的連線(只要兩端的帳本還在就好 — 第 2 章)。但路徑上的防火牆或 NAT 持有以四元組為單位的狀態表,會把持續閒置的項目以逾時刪除。之後送達的區段就成了「所屬不明」,被丟棄或回以 RST。對策是用 TCP keep-alive 或應用層的定期存活確認,持續展示「還在使用中」。若是深夜的壅塞,應該會先看到遺失或重傳。
案例 7-2 — 很慢、堵住
第 3〜5 章的範圍。請翻譯成「重傳是哪一型」「取 min 的是哪扇窗」。
Q4. 案例:接到傳輸很慢的申告後查看封包擷取,發現大量排列著相同 ack 號碼的 ACK。接收端已連續收到位元組 8000 為止,只有 8001〜9000 的區段遺失,後續(9001 以後)持續送達。排列著的重複 ACK 的 ack 號碼是多少?
ack 號碼是「下一個想要的位元組」,而且是累積確認,所以在洞(8001〜9000)補上之前,後續送達再多封,ack 都停在 8001(第 3 章)。也就是說重複 ACK 的號碼直接指向遺失區段的開頭位元組。封包擷取裡的重複 ACK,可以讀成指出該重傳哪裡的箭頭。
Q5. 案例:用頻寬 1 Gbps(= 125 MB/秒)的專線向海外據點(RTT 50 毫秒)傳檔案,卻只有 4 MB/秒。調查後發現接收端的設定把視窗固定在 200 KB(200,000 位元組)。這個設定下的吞吐量上限是多少 MB/秒?(1 MB = 100 萬位元組)
上限 = 視窗 ÷ RTT = 200,000 位元組 ÷ 0.05 秒 = 4,000,000 位元組/秒 = 4 MB/秒。與觀測值完全一致,可以確定犯人就是視窗設定(第 4 章)。要用滿 125 MB/秒的頻寬,需要 BDP = 125 MB/秒 × 0.05 秒 = 6.25 MB 以上的視窗。「線路很粗卻很慢」的頭號嫌疑人,永遠是視窗 ÷ RTT。
Q6. 案例:往記錄收集伺服器的傳輸週期性地停住。封包擷取中排列著來自接收端的視窗 0 通告,與來自送信端反覆的小型探詢封包。接下來該調查的地方是哪裡?
零視窗 + 視窗探測的組合,是「接收緩衝區已滿,所有人都在等應用程式用 recv 讀走」這個狀態的教科書級封包擷取(第 4 章)。網路只是正確地傳遞了煞車,犯人是接收端應用程式的處理堵塞(磁碟寫入太慢、GC、鎖等)。若是遺失或壅塞,應該會看到重傳或重複 ACK,是和視窗 0 不同的風景。
案例 7-3 — 資料怪怪的
第 6 章的範圍。請找出「在 TCP 的承諾之外,假設了什麼」。
Q7. 案例:一個 parse 從 socket 收到的 JSON 的服務,只在正式環境每月數次發生 parse 錯誤。查看錯誤時的資料,發現 JSON 中途截斷的,與 2 個 JSON 相連的兩種都有。正確的處置是哪一個?
中途截斷(切割)與相連(合併)兩者都出現,是「1 recv = 1 訊息」前提的典型症狀(第 6 章)。TCP 完全守住了位元組序列與順序,網路與協定都沒有異常。重試只是重複錯誤的切法而已。邊界由應用程式建立 — 導入 framing 並修改為「攢著、切出、留到下次」的迴圈,是唯一的根治之道。
Q8. 案例:傳輸完全沉默數秒後,從同一區段的重傳重新開始,這個模式不斷重複。幾乎看不到重複 ACK。這段期間送信端的壅塞視窗(cwnd)發生的事情是哪一個?
幾乎沒有重複 ACK + 沉默後的重傳,不是快速重傳而是 RTO 滿期型的重傳(第 3 章)。此時 TCP 判斷為重症,把 cwnd 退回 1 MSS,從慢啟動重新開始(第 5 章)。恢復要花好幾個 RTT,體感就是「卡住數秒後又慢吞吞地起步」的反覆。與重複 ACK 型(減半後繼續)症狀與嚴重度都不同 — 分辨重傳的型,是效能問題解讀的分水嶺。
課程的結尾 — 用數字的心情去讀
辛苦了。這門課程需要死背的項目其實非常少。取而代之希望你帶走的,是在封包擷取與故障記錄的任何場面都通用的讀法。
- 一定送達(沒送到就會發現) — ack 是下一個想要的位元組。重複 ACK 是指向缺漏開頭的箭頭。重傳有輕症型(重複 ACK)與重症型(RTO)
- 按照順序 — 累積確認與接收緩衝區修正亂序。但切點在承諾之外 — 邊界由應用程式以 framing 建立
- 配合對方與網路 — 上限是 min(rwnd, cwnd) ÷ RTT。零視窗是對方的堵塞,cwnd 的斷崖是路徑的壅塞
如果原本停在「總之逾時了,先重啟再說」的調查,已經變成能指出哪項承諾、在哪個封包、如何快被打破的調查,本課程的目標就達成了。下次打開封包擷取時,seq、ack 與 win 的數列,應該就能像文章一樣讀懂了。