一条会自己调节的队
前二十一章里,利用率是一个外生的、你只能被动接受的数。这一章的系统不一样:它每秒钟都在主动试探自己的天花板在哪,撞到就退回来。理解它,等于拿到了一把可以套用在任何拥塞问题上的钥匙。
一条 1000 包/秒 的链路,空载往返时延 20 毫秒。发送方用标准的 TCP 拥塞控制(慢慢加速,丢包就砍半)。
现在把路由器的缓冲区从 10 个包加到 1000 个包。
吞吐和延迟会怎么变?
一个会自己找墙的系统
TCP 的拥塞控制回答的是一个这本书一直回避的问题:发送方并不知道链路有多快,也不知道路上有多少人在抢。它该发多快?
答案叫 AIMD(Additive Increase, Multiplicative Decrease,加性增、乘性减):
# 每收到一批确认(一个往返),窗口 +1 w = w + 1 ← 线性地试探:还能再快一点吗? # 一旦丢包(说明队满了,撞到墙了),窗口砍半 w = w / 2 ← 指数地后退:退得比进得快
画出来是一条锯齿:缓慢爬升,突然掉落,再爬升。
这个不对称不是随便定的,它是唯一能同时满足效率和公平的组合。
1989 年 Chiu 和 Jain 证明了这件事。把两个共享同一条链路的发送方画在一张图上(横轴一个的速率,纵轴另一个的速率):
# 两个发送方的速率组合,在这个平面上移动
B的速率
│╲ ╲ = 总容量线(超过就丢包)
│ ╲ ╱ ╱ = 公平线(两人相等)
│ ╲ ╱
│ ╲ ╱
│ ╳
│ ╱ ╲
│╱ ╲
└──────────── A的速率
# 加性增:两人同时 +1 → 沿 45° 方向移动 → **朝公平线靠近**
# 乘性减:两人同时 ×½ → 沿指向原点的射线移动 → **保持当前比例**
#
# ★ 反复迭代,会收敛到"公平线与容量线的交点" —— 又高效又公平。
#
# 换成"乘性增、加性减"呢?会发散,不公平。
# 换成"加性增、加性减"呢?不收敛,来回震荡。
这是一个非常漂亮的结果:一个纯粹分布式的、没有中央协调的算法,靠一条不对称的规则,收敛到了全局公平。互联网能长这么大,这条规则功不可没。
缓冲区膨胀
现在来看这一章真正的主题,因为它是这本书所有主题的汇合点。
AIMD 靠丢包来知道自己撞墙了。而丢包只在缓冲区满的时候发生。
所以:缓冲区越大,发送方要越久才能发现自己超速了——而在发现之前,那个巨大的缓冲区已经被填满,每个包都得排在里面等。
| 缓冲区 | 吞吐 | 利用率 | 平均 RTT | 最坏 RTT |
|---|---|---|---|---|
| 10 包 | 958/秒 | 95.8% | 24.0 ms | 30 ms |
| 50 包 | 984/秒 | 98.4% | 54.9 ms | 70 ms |
| 200 包 | 994/秒 | 99.4% | 171.6 ms | 220 ms |
| 1000 包 | 999/秒 | 99.9% | 783.3 ms | 1020 ms |
从 10 个包加到 1000 个包:
- 吞吐涨了 4.2%(958 → 999);
- 平均延迟涨了 32.6 倍(24.0 ms → 783.3 ms);
- 而空载时的往返时延只有 20 毫秒——也就是说,763 毫秒全是在缓冲区里排队。
这个现象叫缓冲区膨胀(bufferbloat),由 Jim Gettys 在 2010 年前后命名。它的成因非常朴素:内存变便宜了,设备厂商就多装缓冲,因为「缓冲多总不是坏事」。
而它的后果是:你在下载一个大文件的时候,同一条网络上打游戏会卡到没法玩、视频会议会延迟一秒——尽管带宽明明还有富余。那一秒不是带宽不足,是你的包在路由器里排队。
「大缓冲区几乎不增加吞吐,它只是把拒绝换成了等到超时。」
在这里,被换掉的「拒绝」是丢包。而丢包在 TCP 里根本不是失败——它是控制信号。它是发送方唯一能收到的「你太快了」的反馈。
把缓冲区加大,等于把这个信号延迟了。而一个反馈回路里,延迟增加会让系统的震荡加剧、响应变慢——这是控制论的基本结论,和排队论在这里握手了。
所以现代的解法不是「调整缓冲区大小」(那个值和链路速率有关,没法静态设),而是主动队列管理(AQM):在队列满之前就开始丢包或打标记。
- RED(Random Early Detection):队列超过阈值就随机丢,队越长丢得越狠。
- CoDel:不看队列长度,看包在队列里待了多久。超过 5 毫秒就开始丢。这直接对应第 21 章那条「按时间限队」的建议。
- ECN:不丢包,而是给包打一个标记,让接收方告诉发送方「路上有点堵」。用信息代替损失。
BBR:换一个传感器
2016 年 Google 发布了 BBR,它做了一件更根本的事:不再用丢包当信号。
BBR 的观察是:丢包是一个滞后的、间接的拥塞信号。它只在缓冲区已经被填满之后才出现——而那时候延迟已经被拉高了。
BBR 改为直接估计两个物理量:
- 瓶颈带宽:最近一段时间观察到的最大交付速率;
- 最小往返时延:最近一段时间观察到的最小 RTT(也就是队列为空时的 RTT)。
然后让发送速率恰好等于瓶颈带宽,让「在途数据量」恰好等于 带宽 × 最小RTT——也就是刚好填满管道,但不填缓冲区。
用这本书的语言说:BBR 试图把工作点稳定在第 9 章那条曲线「起飞之前」的位置,而不是让它撞到墙再退回来。
到这一章为止,这本书讲的都是被动的排队:给定 λ、μ、c²,算出会排多长。
而拥塞控制是主动的:它有一个传感器(丢包或 RTT)、一个控制器(AIMD 或 BBR)、一个执行器(发送窗口)。这是一个标准的反馈回路,而反馈回路有它自己的一套理论。
# 一个反馈回路的三个致命参数 ① 增益:反馈有多强(AIMD 的"砍半"是很强的增益) ② 延迟:从执行到看见结果要多久(这里是一个 RTT + 排队时间) ③ 噪声:传感器有多准(丢包可能是无线信道错误,不是拥塞) # ★ 缓冲区膨胀 = 参数 ② 变大 → 回路震荡加剧、稳定性变差 # ★ BBR = 换掉传感器,让 ① 和 ③ 都变好 # ★ 而"用队列长度当传感器"本身就有一个根本缺陷: # 队列长度是**积分**信号(它累积了过去的所有失衡), # 积分环节天然带来相位滞后。CoDel 用"排队时长"而不是"队列长度", # 某种意义上是在做微分补偿。
如果你觉得这一节的词汇忽然变了,那是对的:这里是排队论和控制论的交界。而这本书学完之后,控制论是最值得接着读的方向之一(第 24 章会给入口)。
「背压」现在被用得很泛,几乎等同于「限流」。但它有一个更精确的含义,而且和这一章直接相关:
背压是一条从下游流回上游的信息通道,让上游知道该慢下来。
关键词是信息,不是阻止。区别在于:
- 限流:下游拒绝超出的请求。上游可能根本不知道发生了什么,继续发。
- 背压:下游告诉上游「我处理不过来」,上游主动减速。
TCP 的接收窗口是背压(接收方明确告诉发送方还能收多少),丢包是背压(隐式的),ECN 是背压(显式的),Reactive Streams 的 request(n) 是背压。
而「队列满了返回 503」不是背压,是限流——除非客户端真的会因此减速。如果客户端收到 503 就立刻重试,那你不但没有背压,还制造了正反馈。
你家路由器。如果你在下载大文件时打游戏会卡,那多半就是缓冲区膨胀。解法不是换更快的宽带(吞吐不是瓶颈),是在路由器上开启 SQM / CAKE / fq_codel——很多开源固件都支持。开启之后延迟能从几百毫秒掉到几十毫秒,而带宽只损失百分之几。这是这本书里你今晚就能验证的一个结论。
为什么视频会议要「限制自己的码率」。实时通信协议(WebRTC 那一套)用的是延迟敏感的拥塞控制,而不是 TCP 那种丢包驱动的——因为对视频来说,晚到的包等于丢包。它们宁可主动降画质,也不愿意让延迟涨上去。这是「主动留余量」在应用层的直接体现。
Kubernetes 的 HPA 就是一个 AIMD。观察指标(CPU)、和目标比较、增减副本。而它有一样和 TCP 一样的毛病:反馈延迟很大(拉起一个 Pod 要几十秒到几分钟)。所以 HPA 的冷却时间、缩容策略全都是在防震荡——和这一章是同一套问题。
库存管理里的牛鞭效应。供应链上,终端需求的小波动会被逐级放大成上游的大波动。原因也是反馈延迟:每一级都要等一段时间才知道下游的真实需求,于是过度反应。这是同一个数学,只是执行器是采购单。
这大概是整个网络工程史上代价最大的一个错误直觉,因为它被硬编码进了几十亿台设备。而它之所以能存活这么久,是因为吞吐是可见的、延迟是不可见的——厂商测吞吐会好看,没人测空载延迟和满载延迟的差。
而这个直觉的一般形式,正是这本书从头到尾在打的那个:
「把队开大一点,就不会有人被拒绝」——这句话在网络上叫缓冲区膨胀,在服务端叫无界线程池队列,在管理上叫「所有需求都接下来再说」。三者是同一个错误。
它们的共同解药也一样:把队限住,让压力尽早、诚实地暴露给上游。
B吞吐提高 4.2%,延迟涨 32.6 倍。吞吐从 958/秒 到 999/秒,平均 RTT 从 24.0 毫秒到 783.3 毫秒。
而空载 RTT 只有 20 毫秒——763 毫秒是纯粹的排队。你为了那 4.2% 的吞吐,付出了 38 倍的排队延迟。
A 「吞吐明显提高」——吞吐确实提高了,只是幅度小得可怜(4.2%),因为 10 个包的缓冲已经足够让 AIMD 跑到 95.8% 的利用率了。缓冲区的边际收益衰减得极快,而边际成本是线性的。 C 「都基本不变」——吞吐确实基本不变(这一半对了),但延迟变化巨大。 D 「吞吐下降」——不会。AIMD 在大缓冲区下反而能把链路填得更满(99.9%),因为它有更大的空间去「超速」而不被惩罚。这正是问题所在:它跑到了 99.9%,而第 9 章告诉你那个位置的等待时间是什么样。这一章的一句话
拥塞控制是把「有多满」变成一个可以被主动调节的量;而加大缓冲区等于把它的传感器蒙住。
下一章把这本书的公式带出软件。同一条曲线在高速公路上、在医院里、在看板上、在定价里长什么样?你会看到一张和第 9 章那面墙形状一致、但更狠的图:高速公路过了临界密度,通过量不但不再增长,还会真的下降——同样是 3000 辆/小时,可以是密度 43.5 跑 69 km/h,也可以是密度 96.5 爬 31 km/h。