慢启动是什么
TCP 刚建立连接时并不知道链路能承载多少数据,于是先按一个保守的初始拥塞窗口发一小批数据:每收到确认就把窗口翻倍,呈指数增长,直到出现丢包或达到 ssthresh 门槛,再转入拥塞避免阶段线性增长。现代实现(RFC 6928)把初始窗口提到 10 个 MSS 约 14.6KB,以缩短短连接的首屏耗时。
基本信息
| 初始拥塞窗口 | 10 MSS,约 14.6KB(历史值曾为 3-4) |
|---|---|
| 增长方式 | 每收到一个 ACK 窗口加一,宏观看是每 RTT 翻倍 |
| 退出条件 | cwnd 达到 ssthresh 或检测到丢包 |
| 规范 | RFC 6928、RFC 5681 §3.1 |
| 相关参数 | net.ipv4.tcp_slow_start_after_idle 控制空闲后是否重新慢启动 |
| 观测工具 | ss -ti 输出中的 cwnd 字段 |
典型使用场景
- 解释小文件页面的首字节与首屏延迟:短连接几乎全程处于慢启动
- 评估连接池与 keep-alive 的价值:复用连接可以跳过重复的慢启动过程
- 高延迟链路上诊断吞吐上不去:窗口增长需要足够多的 RTT 才能填满带宽
- 排查长连接空闲后首包变慢:tcp_slow_start_after_idle 会让窗口重新回到起点
- 估算文件传输的爬坡时间,为分片并发数提供依据
观察拥塞窗口增长
观察拥塞窗口增长
ss -ti '( dport = :443 )' | head -20 # 输出里的 cwnd:10 表示 10 个 MSS
ss -tin state established | grep -E 'cwnd|rtt'
sysctl net.ipv4.tcp_slow_start_after_idle # 1 表示空闲后重新慢启动
sysctl net.ipv4.tcp_init_cwnd_reduction # 丢包后如何收敛
nstat -az TcpExtTCPLostRetransmit | head -3阶段与行为
| 阶段 | 窗口变化 | 触发条件 |
|---|---|---|
| 慢启动 | 每 RTT 翻倍,指数增长 | 连接刚建立或超时重传后 |
| 拥塞避免 | 每 RTT 线性加一 | cwnd 达到 ssthresh 之后 |
| 快速重传/恢复 | 窗口减半后再缓慢增长 | 收到三个重复 ACK |
| 空闲后重启动 | 窗口回落重新增长 | tcp_slow_start_after_idle 生效且连接空闲较久 |
对页面加载这类大量短连接的场景,最重要的优化不是调窗口参数,而是复用连接:HTTP/2、连接池、keep-alive 都能让后续请求跳过慢启动,比调大初始窗口的收益更稳定。
tcp_slow_start_after_idle 默认为 1:长连接空闲一段时间后再发数据会重新从小窗口开始。交互型长连接(如数据库连接池)在延迟敏感场景可以考虑关闭,但要以链路质量可靠为前提。