RTO 不能拍脑袋:太短会误重传,太长会白等,所以要用「平滑 RTT」估。 公式是「SRTT 加四倍偏差」,而且每次只改八分之一,避免被单个异常带走。
6.4.2 讲了「怎么发现丢包」,这一节讲「等多久没确认才算丢」。 4.4.1 的往返时间概念在这里第一次被精确化,虽然那节讲的是路由选路。
一、为什么不能拍脑袋
RTO 定得太短,会把「只是慢」误判成「丢了」:于是重传一个其实已经到的包,白白浪费带宽, 而且重传之后又更拥塞,形成恶性循环。 定得太长,丢包要等很久才补:用户看到的是「卡一下」。 所以 RTO 必须「跟着网络状况变」,而「网络状况」唯一的观测手段就是「实测的往返时间」。
二、RFC 6298 的公式
做法分两步:先平滑 RTT,再按偏差放大。
| 量 | 怎么更新 | 作用 |
|---|---|---|
| SRTT | 旧值乘 0.875 加新样本乘 0.125 | 平滑后的往返时间 |
| RTTVAR | 旧值乘 0.75 加偏差乘 0.25 | 波动有多大 |
| RTO | SRTT 加四倍 RTTVAR | 实际用的超时 |
第二行那个「偏差」是「新样本和 SRTT 差多少」的绝对值:它衡量「网络稳不稳」。 稳的网络偏差小,RTO 就接近往返时间; 抖的网络偏差大,RTO 就留出更多余量。 这比「取最近几次的平均」高明得多:平均会把「偶发的一次慢」摊薄,看不出「波动变大」; 而偏差专门盯「波动」,所以能提前把 RTO 抬起来。
三、Karn 算法与退避
有个坑:重传之后测到的 RTT 是不准的——因为你不知道 ack 是回给「原来那个」还是「重传那个」。 Karn 算法的做法很干脆:重传过的段,不做 RTT 采样。 另一个机制是「指数退避」:重传一次不成功,下次的 RTO 翻倍,再不成再翻倍,直到上限。 这样做的好处是「网络真断了的时候不会疯狂重传」, 坏处是「恢复得慢」——所以「断网几秒再恢复」之后,连接往往要过一会儿才顺畅。 RFC 6298 还规定了上下限:下限 1 秒、上限 60 秒。 下限是为了防误重传,上限是为了别死等。 注意这个 1 秒下限在实际高速网络里偏保守,所以有些系统会用更激进的实现, 这也是「不同系统的 TCP 行为不完全一样」的原因之一。
四、动手 Demo:重发超时是怎么算出来的
给五个 RTT 样本,看 SRTT、偏差和 RTO 怎么变。 第四个样本故意给了 300,看它怎么消化。
rtts = [100.0, 110.0, 90.0, 300.0, 105.0]
srtt, rttvar = rtts[0], rtts[0] / 2
for r in rtts[1:]:
rttvar = 0.75 * rttvar + 0.25 * abs(srtt - r)
srtt = 0.875 * srtt + 0.125 * r
print('样本 %5.0f SRTT %6.1f 偏差 %5.1f RTO %6.1f'
% (r, srtt, rttvar, srtt + 4 * rttvar))
实跑输出:
--------------------------------------------------------------------
重发超时是怎么算出来的
--------------------------------------------------------------------
**RTO 不能拍脑袋**:**太短会误重传、太长会白等。**
**所以 TCP 用「平滑 RTT 加四倍偏差」来估。**
第几个样本 实测 RTT SRTT 偏差 算出的 RTO
----------------------------------------------------------
1 100 100.0 50.0 300.0
2 110 101.2 40.0 261.2
3 90 99.8 32.8 231.1
4 300 124.9 74.6 423.5
5 105 122.4 61.0 366.2
**第一行是「初始化」**:**第一个样本直接当基准**,**偏差取它的一半**。
**从第二行起才是平滑**:**每个新样本只改八分之一**,**所以「一个突发的慢包」不会把 RTO 带跑。**
**第四行那个 300 很关键**:**RTO 明显涨了,但没有涨到 300**。
**这就是「平滑」的作用**:**它把「偶发的一次慢」当成了「波动变大」,而不是「常态变慢」。**
**RFC 6298 还规定 RTO 下限 1 秒、上限 60 秒**,**下限是为了防误重传,上限是为了别死等。**
**表里是没做截断的原始值,实际用之前还要按这个规矩夹一下。**
第四个样本 300 进来,RTO 从 231 涨到 423:涨了,但没有涨到 300 以上太多, 这就是「平滑」的作用——它把「偶发的一次慢」当成「波动变大」,而不是「常态变慢」。 第五个样本 105 进来,RTO 又降回 366:因为偏差开始收敛了。 所以这个算法是「自适应」的,网络好它就紧,网络差它就松,不需要人工调参。 表里是没做截断的原始值:实际用之前还要按「下限 1 秒、上限 60 秒」夹一下。 所以真实系统的 RTO 很少小于 1 秒——这也意味着「丢一个包至少要等 1 秒」, 这就是「为什么快速重传那么重要」。
30 秒实验
- 算一下:SRTT 是 100、偏差是 10,RTO 是多少?
- 想一下:为什么重传过的段不做 RTT 采样?
- 想一下:指数退避在网络恢复之后会带来什么影响?
常见误区
- 以为「RTO 是个固定值」。 是算出来的,每个连接、每个时刻都可能不同。
- 以为「取最近几次平均就行」。 还要看偏差,否则看不出「波动变大」。
- 以为「重传之后测的 RTT 也能用」。 不能用,因为分不清 ack 回给谁(Karn 算法)。
自测三问(点开对答案)
1. RTO 的公式是什么? —— **SRTT 加四倍 RTTVAR**,其中 SRTT 是平滑往返时间,RTTVAR 是偏差。 2. 为什么每次只改八分之一? —— **避免被单个异常样本带跑**,让估计值稳定地跟随趋势。 3. Karn 算法解决什么问题? —— **重传之后 RTT 采样不准**的问题:重传过的段直接不采样。下一节讲连接的建立和关闭。
- ☐ (本节完,下一节 6.4.4 连接管理)