1、流控基础:什么是流量控制?为什么需要流控?流控与拥塞控制的区别
各位好,我是老张。今天咱们正式开讲《流控调试实战》的第一章。说实话,我做了十几年网络优化,最常被问到的问题就是:「为什么我带宽加了一倍,视频还是卡?」「为什么延迟忽高忽低?」
这些问题,十有八九都跟流量控制有关。咱们今天就把这个基础打牢。
1.1 什么是流量控制?
流量控制,英文叫 Flow Control。说白了,就是控制发送方别把接收方撑死。
你想想看,网络通信就像两个人用管子倒水。发送方使劲倒,接收方来不及接,水就洒了一地。在网络上,这个「洒水」就是丢包。丢包了就得重传,重传就导致延迟和卡顿。
所以流量控制的核心逻辑就一句话:发送速度 = 接收能力。
核心定义:流量控制是端到端的、基于接收方剩余缓存空间的发送速率调节机制。它确保发送方不会发送超过接收方处理能力的数据量。
我记得刚入行时,带我的老工程师跟我说过一句话,我一直记到现在:「流控不是让车跑多快,而是让车在下一个路口停得住。」
1.2 为什么需要流控?
这个问题其实可以反过来问:没有流控会怎样?
我直接说结论:没有流控,网络会陷入「疯狂重传」的死循环。
咱们看一个真实场景。我在某视频平台做过一次压测,关闭了客户端的流控机制。结果呢?
- 发送端以 100 Mbps 的速率猛发数据
- 接收端缓存只有 64 KB,每秒只能处理 10 Mbps
- 结果:丢包率飙升到 35%,有效吞吐量反而掉到 6 Mbps
- 用户端表现:画面卡成 PPT,缓冲转圈转个不停
这就是典型的「好心办坏事」——发送方太积极,反而把网络搞崩了。
所以流控的必要性可以总结为三点:
- 防止接收方缓存溢出 —— 避免丢包和重传
- 保证公平性 —— 多个连接共享带宽时,谁也别把谁饿死
- 提升用户体验 —— 减少卡顿和延迟抖动
我的经验:在实际调优中,我习惯先看接收窗口(rwnd)的变化曲线。如果 rwnd 频繁降到 0,说明接收端处理不过来,这时候加带宽没用,得优化接收端处理逻辑。
1.3 流控与拥塞控制的区别
这是新手最容易搞混的地方。我当年也犯过这个错——面试时被问到「流控和拥塞控制有什么区别」,我愣了半天没答上来。
咱们用一张图把这事说清楚:
从图上可以看得很清楚:
- 流量控制:关注的是接收端能不能接得住。发送端根据接收端通告的窗口大小(rwnd)来调整发送速率。
- 拥塞控制:关注的是中间网络能不能扛得住。发送端根据网络丢包或延迟信号来调整发送速率。
我再用一个比喻帮你理解:
- 流量控制 = 你倒水时看着杯子的容量,别溢出来
- 拥塞控制 = 你倒水时看着管道的粗细,别堵住
两者缺一不可。光有流控没有拥塞控制,网络一堵你就疯狂丢包;光有拥塞控制没有流控,接收端缓存一满你也得丢包。
避坑指南:我曾经遇到过一个案例,某团队把接收窗口设得特别大(2MB),以为这样能提升吞吐量。结果网络一拥塞,大量数据积压在中间路由器,延迟从 20ms 飙升到 800ms。这就是典型的「只调流控、不管拥塞」的教训。
1.4 流控的核心参数
搞清楚了概念,咱们看看实际调优时盯哪些参数。我列一个常用表格:
| 参数 | 全称 | 作用 | 调优方向 |
|---|---|---|---|
| rwnd | 接收窗口 | 接收方剩余缓存大小 | 根据带宽延迟积(BDP)设置 |
| cwnd | 拥塞窗口 | 发送方估计的网络容量 | 根据丢包/延迟动态调整 |
| ssthresh | 慢启动阈值 | 慢启动到拥塞避免的切换点 | 根据历史丢包记录设置 |
| RTT | 往返时间 | 数据包从发到收的耗时 | 越小越好,但受物理距离限制 |
| RTO | 重传超时 | 等待 ACK 的最长时间 | 基于 RTT 动态计算 |
这里我多说一句关于 rwnd 的。很多人以为 rwnd 越大越好,其实不一定。我见过一个极端案例:某直播平台把 rwnd 设成 4MB,结果用户手机内存不够,频繁 OOM 崩溃。所以 rwnd 的设置要综合考虑接收端的内存和处理能力。
1.5 流控的两种实现方式
在实际工程中,流控主要有两种实现方式:
1.5.1 基于窗口的流控(TCP 经典方式)
这是 TCP 协议默认的方式。接收方在 ACK 包里携带当前剩余窗口大小(rwnd),发送方根据这个值决定还能发多少数据。
// 伪代码示意:发送方逻辑
while (有数据要发送) {
if (已发送未确认数据量 < 接收方通告的 rwnd) {
发送数据;
} else {
等待 ACK;
}
}
这种方式的好处是简单可靠,坏处是 rwnd 更新有延迟——等你收到 ACK 时,接收方的缓存可能已经变了。
1.5.2 基于速率的流控(现代协议常用)
像 QUIC、SRT 这些新协议,更倾向于基于速率控制。发送方直接计算一个目标发送速率,然后按这个速率匀速发送。
// 基于速率的流控示意
target_rate = 接收方反馈的可用带宽;
while (有数据要发送) {
send_at_rate(target_rate);
// 定期根据反馈调整 target_rate
}
我个人更偏好基于速率的方式,尤其是在视频流场景下。为什么呢?因为视频编码本身就是恒定码率或可变码率,用速率控制能跟编码器配合得更好。
我的建议:如果你在做实时音视频或直播,优先考虑基于速率的流控方案。TCP 的窗口流控在弱网环境下表现不够理想,而基于速率的方案可以更精细地控制发送节奏。
1.6 本章小结
好了,咱们把第一章的核心内容捋一遍:
- 流量控制是端到端的、基于接收方能力的发送速率调节
- 没有流控会导致缓存溢出 → 丢包 → 重传 → 卡顿的恶性循环
- 流控 vs 拥塞控制:流控看接收端,拥塞控制看网络
- 核心参数包括 rwnd、cwnd、ssthresh、RTT、RTO
- 实现方式有基于窗口和基于速率两种,场景不同选择不同