TCP 保证按序交付,这个保证有代价:一个包丢了,后面已经到达的数据也只能在接收缓冲区里干等。这篇用 netlab 造丢包,逐条测消息从发出到被应用读到的延迟,看队头阻塞到底拖慢了多少、一次丢包会连累几条消息、拆成多条连接又能缓解多少。
UDP 不重传,延迟全在 50~53 ms,代价是丢 3% 的消息;TCP 一条不丢,但 28.8% 的消息延迟超过 80 ms,p99 229.8 ms。
配置 收到 丢失 p50(ms) p90(ms) p99(ms) 最大(ms) 延迟 > 80 ms 的消息
UDP(无重传) 1452 48 50.3 50.8 51.4 52.7 0(0.0%)
TCP 1 条连接 1500 0 50.5 141.0 229.8 280.3 432(28.8%)
TCP 4 条连接轮流发 1500 0 50.4 71.0 191.3 350.4 131(8.7%)
TCP 16 条连接轮流发 1500 0 50.3 50.9 311.1 469.8 82(5.5%)
理论:单程 50 ms;TCP 丢一个包后要等快速重传(约 1~2 个 RTT 以上)或 RTO(lo0 下限 ≈ 230 ms)才能补上,所有排在它后面的消息都被迫等待连接拆得越多,被拖住的消息比例越低(28.8% → 8.7% → 5.5%),16 条连接的尾部延迟却更高。
序号 发送时刻 应用收到时刻 延迟
68 680 731 51 ms
69 690 740 50 ms
70 700 972 272 ms
71 710 972 262 ms
72 720 972 252 ms
73 730 972 242 ms
74 740 972 232 ms
75 750 972 222 ms
76 760 972 212 ms
77 770 972 202 ms
78 780 972 192 ms
79 791 972 181 ms
80 801 972 171 ms
81 811 972 161 ms
从序号 70 起这一段里延迟 > 80 ms 的消息有 20 条,它们被应用收到的时刻集中在 972~972 ms 的 0 ms 之内——被丢的那个包补上之后,一口气释放序号 70 丢了,后面 19 条早已到达的消息一直在内核缓冲区里等,972 ms 补上之后同时交给应用。
3 次共 38 次队头阻塞事件;每次拖住的消息数:中位 9,最大 34;每次事件里的最大延迟:中位 161 ms,最大 280 ms
38 次事件,中位每次拖住 9 条消息,最大 34 条;事件里的最大延迟中位 161 ms。