下载速度很快,但一有其他传输,通话和游戏就开始卡顿?真正原因往往是缓冲区膨胀——过大缓冲区带来的额外延迟,而非带宽不足。本文讲清成因与修复方法。
一条连接可能测出很棒的下载数字,却在别人开始一次大上传、或者一次游戏更新开始下载的那一刻突然变得很难用——通话变得像机器人在说话、游戏疯狂拉扯回弹、网页卡住不动。这通常不是带宽问题,而是缓冲区膨胀(bufferbloat):路由器、调制解调器或运营商设备里过大的缓冲区把数据排队积压,而不是丢弃它们,由此带来的延迟只有在连接繁忙时才会显现。下面讲清它的成因,以及如何修复。
核心要点
- 缓冲区膨胀是过大缓冲区带来的额外延迟,而非带宽不足——你的套餐速度可能完全正常,连接却依然在负载下感觉卡顿。
- 连接空闲时它完全隐形。 快速 Ping 一下看起来很棒;延迟只有在上传或下载把连接跑满之后才会出现。
- 负载下的 Ping 可能比空闲 Ping 高出一个数量级——在 APNIC 发布的一次家庭宽带实测中,链路被跑满之后,Ping 从几毫秒一路升到将近 300 ms。
- 标准修复方法是主动队列管理(AQM),特别是 FQ-CoDel,多数现代路由器和基于 OpenWrt 的固件都能直接开启,无需换新硬件。
- 只跑一次测速发现不了它——Ping 是在数据开始传输之前测的。要先把链路跑满,再测一次,然后比较两个数值。
什么是缓冲区膨胀?
路由器和调制解调器都会保留一小段队列——缓冲区——用来在数据到达速度超过出口链路承载能力的瞬间暂存数据。这是正常且必要的。问题出在缓冲区被配置得远比实际需要的大:过大的缓冲区不是短暂地抹平一次突发流量,而是让数据包排队等上几百毫秒甚至更久才被发出,因为设备宁愿把数据包排队,也不愿丢弃它。正如 APNIC 网络博客所说,人们直觉上会以为性能不佳是带宽不够,而真正的元凶往往正是这种堆在过深缓冲区里的额外排队延迟。
这个术语由工程师 Jim Gettys 在 2010 年前后提出,他追溯发现,家庭网络莫名其妙的卡顿源自消费级路由器和调制解调器——它们的缓冲区是为吞吐量跑分而设计的,不是为响应速度设计的。更大的缓冲区确实能在单独测试中跑出略高的峰值吞吐数字,但代价是任何排在一次大传输后面的数据包都得等它排到——而这个等待,就是缓冲区膨胀。
空闲 Ping vs. 负载 Ping
缓冲区膨胀之所以能躲过日常测试,是因为它只在负载下才出现。在网络上没有其他活动时 Ping 你的路由器,会看到一个干净的低数值——这是空闲 Ping,测的是一个空队列。同时开始一次大上传或下载,再重新测一次,你会得到负载 Ping:一个数据包排在所有已排队数据后面、真正需要等待的延迟。
两者之间的差距才是问题所在,而且这个差距一点都不小。在上面那篇 APNIC 文章里,作者一边同时跑上传和下载,一边记录到远端服务器的 Ping:测试开始后的前 5 秒,延迟一直只有几毫秒;传输一开始,Ping 就升到了将近 300 ms——一个数据包来回一趟就要花掉三分之一秒,足以让视频通话彻底没法用。带宽自始至终没有变化,只是每个数据包如今都排进了一个终于被用起来的队列。
这正是为什么业界的测量工作已经转向追踪负载下的延迟,而不只是空闲 Ping:在一条存在缓冲区膨胀的连接上,空闲延迟和原始吞吐量看起来都没问题,但两者都无法预测这条连接真正用起来会是什么感觉。
成因
缓冲区膨胀不是单一的 Bug——而是默认配置得很宽裕的缓冲区,叠加大多数传输的行为方式:
- 默认队列过大。 家用路由器、有线调制解调器、DSL 调制解调器以及光纤 ONT,往往被配置成在吞吐量跑分中永不丢包,而这恰恰与低延迟所需要的相反。
- 基于丢包的拥塞控制。 许多传输(大文件下载、云同步、大文件上传)会持续加快发送速度,直到真正发生丢包才会放慢。缓冲区一深,这个丢包信号要等到队列几乎排满才会出现——于是缓冲区在发送方减速之前几乎已经完全填满。
- 哪个方向被跑满,哪个方向就膨胀。 一次大上传会让上行队列膨胀(你的出站流量在等待);一次大下载会让运营商或调制解调器一侧的队列膨胀。任何一个都足以让连接上的其他一切感觉变慢。
- 运营商侧设备同样可能携带同样过大的缓冲区,而不只是你自己的路由器——这也是为什么换掉家用路由器后,缓冲区膨胀有时依然存在。
解决方法:主动队列管理与 FQ-CoDel
修复方法不是换更大的套餐——而是管理队列,而不仅仅是让它变大。**主动队列管理(AQM)**会在队列变长之前主动决定何时丢弃或标记数据包,而不是传统的「队尾丢弃」做法——只在缓冲区完全占满后才丢包。
现代标准是 FQ-CoDel(Fair Queuing with Controlled Delay,公平排队 + 受控延迟),在 RFC 8290 中被标准化为一种实验性算法。它同时做两件事。
CoDel 关注的不是队列里堆了多少个包,而是每个包在队列里实际停留了多久。一旦这个停留时间在一个观察窗口(默认 100 毫秒)内持续高于一个很小的目标值(默认 5 毫秒),它就开始丢包,以此告诉发送方降速,从而把队列压得很浅,不让它越堆越深。公平排队则用哈希把流量分散到大量按连接划分(per-flow)的独立队列中(默认 1024 条),再轮流调度,这样一个大传输就无法把排在它后面的一切都饿死。正是这个组合,让一次单独的大文件下载不再能毁掉同一条连接上的视频通话。
要用上这个方案,你不一定需要新硬件。多数基于 OpenWrt 的路由器,以及越来越多来自运营商和路由器厂商的原厂固件,都以 智能队列管理(Smart Queue Management,SQM) 或类似的「缓冲区膨胀修复」开关提供这项功能——通常会同时给出 fq_codel 和 cake 两个选项,后者是更新一代的改良方案,把流量整形和队列管理打包成一个调好的整体。
真正容易设错的是速率那一项。要把配置的上下行速度设得比你实测的实际带宽略低一些:这是刻意把瓶颈制造在你自己的路由器上,让队列堆在这台你能控制其队列策略的设备里,而不是堆进你管不着的上游运营商设备。这一点在下行方向尤其关键,因为下行的控制天生是间接的——数据包一旦已经到达你的路由器,就不可能再「退回去」,唯一的手段就是靠丢包或标记,让远端发送方主动降速。要有心理准备:这会牺牲一部分峰值吞吐(在上面那次 APNIC 实测中大约 10%),同时也要确认你的路由器有足够的 CPU 余量按线速做整形——AQM 并不是免费的。
如何在家测试缓冲区膨胀
只跑一次测速是发现不了它的,因为测速工具是在开始传数据之前测 Ping 的——而那正是队列空着的时刻。诀窍在于:先把链路弄忙,再去测。跑两次就够了:
- 先拿到空闲基准。 在网络上没有其他活动时,跑一次 BrowserInsight 的网络测速,记下它给出的延迟和抖动,以及你的上下行速度。
- 把链路跑满,并且一直保持。 开始一个一分钟后还没结束的持续传输——比如一次大文件上传或云备份、一个正在下载的游戏更新,或者干脆让同一网络下的另一台设备去跑测速。用上传通常最容易暴露问题,因为上行带宽一般是两者中更小的那个。
- 趁这个传输还在跑,再测一次速。 由于测速工具是在每次运行的开头读取延迟,所以你按下开始之前,链路就必须已经处于负载状态。此时得到的延迟和抖动,就是你的负载数值。
- 比较两者。 如果负载延迟是空闲值的好几倍——从几十毫秒变成几百毫秒——那就是缓冲区膨胀,而且抖动通常也会跟着一起涨。一条队列管理良好的连接,即使在满负载下,两次读数也会保持接近。
关于空闲数值本身的含义,可参阅带宽、延迟与抖动详解;如果你的读数在多次测试之间看起来不太一致,为什么你的网速测试结果总在变讲清了其他常见原因,以免把正常波动误认成缓冲区膨胀。
常见问题
换更大的套餐能修复缓冲区膨胀吗?
不能。缓冲区膨胀是排队/延迟问题,不是容量问题——更大的套餐甚至可能让它更严重,因为一台按旧的、较低速度配置的路由器,会在它自己的整形机制生效之前,让更大的一波数据先排起队来。真正有用的是修好队列管理(AQM/FQ-CoDel),而不是套餐大小。
缓冲区膨胀是运营商的问题,还是我路由器的问题?
两者都有可能,甚至两者兼有。消费级路由器普遍出厂就带着过大的默认缓冲区,但运营商提供的调制解调器或 ONT——以及更上游的设备——也可能携带同样的问题。先通过自己的路由器测试负载 Ping,能帮你判断修复应该在本地进行(开启 SQM),还是需要联系运营商。
为什么我自己没在上传,通话和游戏也会卡?
共享路径上任何被跑满的队列都会影响穿过它的每一条流量,而不只是把缓冲区填满的那一次传输。如果家里其他人开始一次大下载,或者你自己的设备在后台启动了一次备份,队列会以同样的方式被填满,而你的通话或游戏——它们对延迟远比对带宽敏感——就会首当其冲。
不用买新硬件也能修复缓冲区膨胀吗?
通常可以。许多现有路由器,尤其是运行 OpenWrt 或类似第三方固件的,已经支持智能队列管理 / FQ-CoDel,只需要开启一个设置项。只有在原厂固件不提供这个功能时,换新(或刷机)硬件才是现实的解决方案。
结语
一个漂亮的下载数字和一条感觉卡顿的连接并不矛盾——它们正是缓冲区膨胀的典型表现:过大的缓冲区在连接一忙起来时把数据包排队积压,而不是及时送达。空闲 Ping 会把它完全隐藏起来;只有负载 Ping 测试才能揭示它。修复方法不是加更多带宽,而是主动队列管理——尤其是 FQ-CoDel——而多数路由器如今都能开启它。在自己的连接上测一测空闲 Ping 和负载 Ping,如果差距很大,需要修的是队列,而不是升级套餐。
延伸阅读:


