TCP RTT 测量困境与技术分析
一、核心问题
1. 问题本质
TCP 传输算法(包括拥塞控制算法、传输加速算法等)非常依赖 RTT(Round Trip Time,往返时间)进行决策,但 RTT 的测量准确性却与网络质量正向相关。
2. 核心矛盾
- 网络质量好时,不过度依赖 RTT 的精确测量
- 网络质量不好,需要精确 RTT 测量时,RTT 却测不准
这种矛盾源于 TCP 协议本身的设计缺陷,包括 Delayed ACK 机制和重传歧义问题。
二、RTT 测量的技术挑战
1. Delayed ACK 影响
A. 机制说明
Delayed ACK 是 TCP 接收端的一种优化机制,接收端收到数据后不立即发送 ACK,而是等待一段时间(通常 200ms)或等到收到第二个数据段时再发送 ACK,目的是减少 ACK 数量,提高网络效率。
B. 测量偏差
接收端收到 2 个段后回复一个 ACK,该 ACK 包含对两个段的确认。在这种情况下:
- 可用第一个段计算 srtt(用于 RTO 计算的 RTT)
- 可用第二个段计算 ca_rtt(用于拥塞控制的 RTT)
原因在于:
- 第一个段包含了主机 Delayed 时间
- 第二个段直接触发了 ACK,没有主机时间影响
C. 测量示意图
sequenceDiagram
participant S as 发送端
participant R as 接收端
S->>R: 数据段1 (时间t1)
Note over R: Delayed ACK 等待
S->>R: 数据段2 (时间t2)
R->>S: ACK (确认段1和段2)
Note over S: srtt ≈ ACK时间 - t1<br/>包含 Delayed 时间<br/>ca_rtt ≈ ACK时间 - t2<br/>更接近纯网络时延2. 重传歧义问题
A. 问题描述
当网络质量不好、频繁丢包时,如果发送端无法区分 ACK 或 SACK 是针对原始数据还是重传数据,时间信息将被丢弃,不再用于 RTT 计算。
B. 困境加剧
- 丢包重传时,没有 Delayed ACK 影响(接收端会立即确认)
- 但遇到重传歧义,无法确定 ACK 对应原始发送还是重传
- 恰恰在频繁丢包时,RTT 对拥塞控制最重要,但却测不到
C. 重传歧义示意图
graph LR
A[发送数据包] --> B{是否丢失?}
B -->|未丢失| C[收到 ACK]
B -->|丢失| D[重传数据包]
C --> E[正常计算 RTT]
D --> F{能否区分原始/重传?}
F -->|能区分| G[可计算 RTT]
F -->|不能区分| H[丢弃时间信息]三、Linux 的解决方案
1. 双 RTT 机制
Linux 内核实现了两类 RTT,从源码注释可见设计意图:
struct tcp_sacktag_state {
...
/* Timestamps for earliest and latest never-retransmitted segment
* that was SACKed. RTO needs the earliest RTT to stay conservative,
* but congestion control should still get an accurate delay signal.
*/
u64 first_sackt;
u64 last_sackt;
}A. srtt(Smoothed RTT)
- 用途:计算 RTO(重传超时时间)
- 特点:偏保守,避免激进重传
- 策略:使用最早的时间戳
B. ca_rtt(Congestion Control RTT)
- 用途:拥塞控制算法
- 特点:侧重精度,度量网络时延
- 策略:使用最新的时间戳,最小化主机影响
2. Timestamp 选项兜底
TCP Timestamp 选项可用于解决重传歧义:
- 每个数据包携带发送时间戳(TSval)
- ACK 携带回显时间戳(TSecr)
- 即使发生重传,通过时间戳仍可计算 RTT
3. 多种花活儿方案
为在丢包重传场景下获取 RTT,Linux 实现了多种技巧:
A. DSACK 方案
包含 old seg 来重传,用 DSACK 明确区分,计算 RTT
B. Probe 方案
用新数据 probe,明确对该数据的 SACK,计算 RTT
C. 不连续数据 Probe
用不连续数据 probe(新的或旧的),明确对该数据的 SACK/ACK,计算 RTT
D. 简单示例
若将 una ~ una + MSS 作为一个数据包传输并被标记为丢失:
- 重传 una ~ una + fk(0 < fk < MSS)
- 记录当前时间戳
- 保留 una + fk ~ una + MSS 为空洞
- 等待接收端回复 ACK
- 若 ACK 等于 una + fk 且等于重传数据包的 end_seq
- 则 now_us - skb.ts_us 为精确的 RTT
四、新协议的设计思路
1. QUIC 的解决方案
A. 核心思想
QUIC 将包序号(packet id)和流序号(offset)区分开:
- 包序号:用于传输控制
- 流序号:只用于数据语义,与重传关联
B. 机制优势
- 若丢包重传,包序号变化而流序号不变
- RTT 随时可以获得,不受重传歧义影响
- 避开了 TCP 的拧巴设计
C. QUIC RTT 测量流程
graph LR
A[发送数据包] --> B{是否需要重传?}
B -->|否| C[收到 ACK]
B -->|是| D[使用新包序号重传]
C --> E[直接计算 RTT]
D --> F[流序号保持不变]
F --> G[收到 ACK]
G --> H[根据包序号识别<br/>仍可计算 RTT]2. 延迟协商机制
A. 设计建议
为新协议增加 1bit 或 nbit 的 type 字段,明确数据包的目的:
- type = 1:RTT 测量位,receiver 收到后应立即应答
- type = 0:普通数据包,按配置策略自决(如 Delay 一段时间等待反向捎带)
B. 设计理念
这并非对 TCP 的兼容,而是对 TCP 少有优良品质的继承。Delayed ACK 并非原罪,相反它很好,问题的根源在于 TCP 没有任何机制协商 Delayed ACK or not,加上即可。
C. 协议僵化问题
若修改现有 TCP,利用报头里的 unused bits:
- 数据中心场景:可以使用,因为环境可控
- 广域网场景:由于协议僵化问题,这种把戏不适用
五、技术启示
1. 统计先行
传输优化,统计先行,统计未动,数据先行。所有的一切都依赖采集数据的准确性:
- 有了数据,统计就有了依托
- 支撑证据才更可信
- 基于准确性高的数据进行的统计分析反而不再需要准确性
2. 协议设计原则
从 TCP RTT 测量的困境可得出以下设计原则:
- 避免模糊不清的语义(如重传歧义)
- 提供明确的协商机制(如 Delayed ACK 的开关)
- 区分不同用途的标识符(如 QUIC 的包序号和流序号)
- 保持时间信息的透明性
3. 实现权衡
Linux 内核的实现展示了工程上的权衡:
- 双 RTT 机制:平衡保守性和精确性
- Timestamp 选项:为关键场景兜底
- 多种花活儿:在协议限制下尽量优化