学习记录做到第四讲,我基本摸清了这门课的节奏:前面几讲是把网络边缘、接入网、物理媒介这些零件一个个摆出来,到这一讲开始往回收,先给你一张全球视角的"地图"——Internet 到底由哪些 ISP 拼起来,然后镜头猛地推到单个分组身上,问一句:它从 A 主机跑到 B 主机,这一路上时间到底花在哪儿了。Internet 与 ISP 的层级结构、分组延时的四个组成部分、分组丢失的成因,以及吞吐量由谁决定,这四个点基本上是后面所有章节的地基,TCP 拥塞控制、路由选择、CDN 调度,回头看全是这几件事的延伸。这篇笔记我按自己的理解重排了一遍,尽量把教材里那些公式和真实网络里能摸到的现象对上号,适合刚开这门课、被四个延时绕晕的同学,也适合工作里只能靠 ping 和 traceroute 瞎猜问题的朋友。
1. Internet 与 ISP 结构:先把"地图"画出来
1.1 一堆盒子和一堆线,组成了什么
教材对 Internet 的经典描述是"端系统 + 通信链路 + 分组交换机"三件套。端系统就是我们手上的电脑、手机、服务器;通信链路是光纤、双绞线、无线电波;分组交换机是路由器和链路层交换机。听起来像废话,但这个抽象极其关键——它把"网络"从一堆具体设备里抽离出来了,后面无论讨论延时还是吞吐量,你只需要盯着"分组在链路上传输、在交换机里排队"这两件事。
端系统要接入 Internet,不能直接拉根线到地球另一边,得找接入 ISP(接入服务提供商)。接入 ISP 可能是小区宽带、校园网、移动蜂窝网络。接入 ISP 往上连区域 ISP,区域 ISP 再往上连第一层 ISP(Tier-1)。Tier-1 在全球范围内互相连接,它们之间的链路是这张网最粗的骨架。中间还有一类内容提供商网络,把机房和缓存节点直接铺到各地,走的是自建链路,不完全依赖公共层级。
为什么非得搞成层级,而不是让所有 ISP 两两相连?算一笔账就明白了。假设有 n 个 ISP,全互联需要 n(n-1)/2 条链路,n 是 1000 的时候就是将近 50 万条链路,每条链路都要谈合同、铺光纤、维护 BGP 会话,成本爆炸。改成层级结构后,每个 ISP 只需要连上游和少量对等伙伴,链路数量降到 O(n) 量级,成本可控,代价是路径可能变长几跳。
注意:层级结构是商业关系的产物,不是技术强制。同一个城市的两家运营商之间可能直接对等互联,也可能要绕到上游再回来,这就是为什么你访问一个本地服务,traceroute 却显示跑了几百公里。
1.2 转接与对等:谁给谁付钱
ISP 之间的关系有两类,这在考试和实际工作里都很重要。**转接(transit)**是客户付钱给上游,上游承诺把客户的流量送到全球任何地方;**对等(peering)**是两家地位相近的 ISP 直接互连,交换各自网内的流量,双方都不付钱。对等的谈判依据是流量是否大致对等——如果一方只是把对等链路当成免费出海口,另一方肯定不干。
对等通常发生在互联网交换点(IXP),一个机房里几十上百家网络把设备接进同一台交换机,互相建 BGP 会话。IXP 的存在大幅降低了互联成本,也让本地流量不用绕远路。多宿(multi-homing)是指一个 ISP 或企业同时连多家上游,主链路断了自动切换,这是可用性的基本保障。
这些结构上的细节看着偏商业,实际上直接决定你的延时。一个内容提供商如果和主要运营商在本地 IXP 有对等,用户的请求可能只走三跳、几毫秒;如果没有,流量就得绕到上游,可能跨省甚至跨境兜一圈,延时从 5 毫秒变成 60 毫秒。这就是"网络结构决定性能"最朴素的一个例子。
1.3 电路交换与分组交换:为什么必须有排队
网络核心传送数据有两种方式。电路交换在通信开始前就预留一条端到端的链路资源,通话期间这条通道独占,别人插不进来,优点是延时稳定、无抖动,缺点是资源浪费——说话间隙那部分带宽也空着。分组交换不预留资源,每个分组到达路由器后按需转发,链路被所有人共享,这就是统计复用。
打个比方,电路交换像是包一辆大巴,无论坐没坐满都得付整车钱;分组交换像是拼车,每个人只占自己那一份,但高峰期可能要等位。等位就是排队,排队久了队列溢出就是丢包。分组交换用"可能拥塞"换来了"资源利用率高",这是整个 Internet 的核心取舍,也是后面所有延时和丢包分析的前提。
课上有个经典例题:一条 1 Mbps 的链路,用户平均需求 100 kbps,但只有 10% 的时间在活跃。电路交换最多支持 10 个用户,因为每人要预留 100 kbps。分组交换呢?支持 35 个用户时,同时活跃用户超过 10 个的概率(也就是超载概率)小到可以忽略。用二项分布能直接算出来:
from math import comb def overload_prob(n, p, capacity): """n 个用户,每人活跃概率 p,链路最多同时承载 capacity 个活跃用户""" return sum(comb(n, k) * p**k * (1-p)**(n-k) for k in range(capacity + 1, n + 1)) print(overload_prob(35, 0.1, 10)) # 约 0.0004 print(overload_prob(20, 0.1, 10)) # 约 0.0024实测下来这个数字确实小得惊人,35 个用户下超载概率不到万分之四。但同时也要看到另一面:一旦真的超载,所有分组的延时都会一起变差,没有任何优先级保障。这就是分组交换的"共享风险"。
2. 分组延时:四个来源拆开算
2.1 节点总延时公式与四个分量
一个分组从路由器 A 进入、从 A 出去,经历的总延时记为节点总延时:
d_nodal = d_proc + d_queue + d_trans + d_prop
四项分别是处理延时、排队延时、传输延时、传播延时。这个公式是整章的主线,后面所有的计算和排查都围着它转。
处理延时是路由器检查分组头部、查转发表、做校验的时间,通常在微秒级,现代硬件转发甚至压到几十纳秒,一般场景下可以忽略,但要是开了复杂的 ACL 或者深度包检测,这一项能涨到毫秒级。
排队延时是分组在输出队列里等待链路空闲的时间。注意它是在输出端口排队,不是输入端口。这个分量最狡猾,因为它不是固定值,取决于当时的拥塞状况,从 0 到"无穷大"(实际上到队列满为止)都有可能。
传输延时是把分组的所有比特推上链路所需的时间,公式d_trans = L / R,L 是分组长度(比特),R 是链路速率(bps)。传播延时是比特在物理介质里跑完这段距离的时间,公式d_prop = d / s,d 是距离,s 是介质中的信号传播速度,光纤里大约 2.0×10^8 m/s,铜缆稍慢,无线电波接近光速。
提示:教材里把这四个分量写成可加的形式,前提是链路不拥塞、处理能力足够。真实设备里四者是并行的,硬件流水线会重叠执行,公式算出来的是上界估计。
2.2 传输延时和传播延时:最容易混的一对
这两个名字太像了,我自己第一遍学的时候也搞混过。用一个收费站类比固定下来:一队车要通过收费站,传输延时是把整队车全部放行完毕的时间,取决于车队有多长(L)和放行速度有多快(R);传播延时是最后一辆车从收费站开到下一个收费站的时间,取决于距离和车速,跟车队长短完全无关。
数值上感受一下差距。千兆链路上一个 1500 字节的标准以太网帧,传输延时 = 12000 bit / 10^9 bps = 12 微秒。同一个分组跑 1000 公里的光纤,传播延时 = 10^6 m / (2×10^8 m/s) = 5 毫秒。差了 400 多倍。
def trans_delay(packet_bytes, link_bps): return packet_bytes * 8 / link_bps def prop_delay(distance_m, speed=2e8): return distance_m / speed print(f"1Gbps, 1500B 帧: {trans_delay(1500, 1e9)*1e6:.2f} 微秒") print(f"1000km 光纤: {prop_delay(1e6)*1e3:.2f} 毫秒") print(f"100Mbps, 1500B 帧: {trans_delay(1500, 1e8)*1e3:.2f} 毫秒")这个对比直接推出一个常被误解的结论:加带宽只能压缩传输延时,压不动传播延时。跨洲际的 RTT 主要由光纤长度决定,给你 10 Gbps 和给你 100 Mbps,ping 同一个海外地址延时基本一样,只是大文件的下载时间差很多。很多人抱怨"升级了千兆宽带怎么打游戏还是卡",问题往往出在传播延时和中间节点的排队延时上,跟带宽一点关系没有。
还有个小细节:低速链路上传输延时可能比传播延时还大。比如 56 kbps 的链路上发 1500 字节要 214 毫秒,比跨城传播延时都长。所以不能一律说"带宽不重要",得看你在哪个量级上。
2.3 排队延时与流量强度:为什么延时是突然爆炸的
排队延时的核心变量是流量强度,记作 La/R,其中 L 是分组长度、a 是分组到达速率(个/秒)、R 是链路速率。La/R 的物理含义是"到达的流量占链路能力的比例",也就是链路利用率。
- La/R ≈ 0:几乎不排队,排队延时接近 0
- La/R < 1:有排队,但队列能稳定下来,延时随利用率上升
- La/R > 1:到达速率超过服务速率,队列理论上无限增长,实际是队列满后丢包
- La/R → 1:延时不是线性上升,而是急剧飙升
用 M/M/1 队列模型能给出一个粗略的定量关系(这里假设泊松到达、指数服务时间,实际情况有偏差,但趋势一致):平均排队延时 ≈ 传输延时 × (La/R) / (1 - La/R)。
| 流量强度 La/R | 平均排队延时(相对传输延时) |
|---|---|
| 0.1 | 0.11 倍 |
| 0.5 | 1 倍 |
| 0.8 | 4 倍 |
| 0.9 | 9 倍 |
| 0.99 | 99 倍 |
这张表解释了一个非常实际的工程原则:链路利用率长期超过 80% 就开始难受,超过 90% 就是在赌运气。很多团队做容量规划时把 70% 当作警戒线,不是保守,是排队延时的非线性太吓人。你在监控上看到带宽利用率从 60% 涨到 85%,感受上延时可能翻了好几倍,问题就出在这里。
注意:La/R 算的是平均值。即使平均利用率只有 50%,遇到突发流量(比如整点抢购、微服务批量重试)短时间内也能把队列打满,这就是为什么平均指标好看的链路照样会卡。
2.4 端到端延时与实测手段
单个节点的延时加起来才是端到端延时。但还有个处理-存储转发的累加效应:一个分组经过 n 条链路、n-1 台路由器,每个节点都要先收完整个分组才能开始转发下一跳(存储转发),所以端到端延时约等于各节点延时之和。分组越大、跳数越多,这个代价越明显。
实际测量靠三件套:ping测 RTT,traceroute / mtr看逐跳延时,抓包看精确时间戳。
# 跑 100 个包,间隔 0.2 秒,看丢包和抖动 ping -c 100 -i 0.2 目标地址 # 逐跳探测,每跳 3 个探测包 traceroute -n 目标地址 # 持续统计每一跳的丢包和延时分布,比 traceroute 好用 mtr -n -c 100 目标地址这里有几个必须知道的坑。ping 出来的 RTT 是往返时间,单向延时通常按一半估算,但路由不对称时这个假设不成立。traceroute 的中间跳延时不准,因为路由器处理 TTL 超时的 ICMP 响应走的是控制平面,优先级低,很多设备直接限速到每秒几个包,于是中间几跳看着延时巨大,实际数据面转发很快。判断方法很简单:如果中间跳延时大但后续跳恢复正常,那基本是控制平面限速;如果从某一跳开始延时持续升高且不再回落,才可能是真问题。
我自己排查过一次"网络慢",traceroute 显示第 7 跳 200 毫秒、第 8 跳 30 毫秒,一开始怀疑是第 7 跳的骨干设备有问题,后来换了不同方向的目标地址对比,发现只有访问某个特定网段才这样,最终定位是对方网络的入口策略导致的路径绕行,跟中间设备性能无关。
3. 分组丢失:队列满了之后发生什么
3.1 缓冲区、丢弃策略与缓冲膨胀
分组丢失最直接的原因是输出队列的缓冲区满了。路由器收到分组,发现输出端口队列已满,只能丢弃。这个动作叫尾部丢弃(tail drop),最常见也最简单。也有设备实现随机丢弃或者基于优先级的丢弃,把不同业务分开处理。
缓冲区该开多大?有个经验公式:B = RTT × C,也就是带宽时延积。假设链路 10 Gbps、平均 RTT 20 毫秒,那么 B = 10^10 × 0.02 / 8 = 25 MB。这个公式的逻辑是:缓冲区要能装下一个 RTT 内链路能发出的全部数据,否则 TCP 的窗口就会被浪费。
但缓冲区不是越大越好。**缓冲膨胀(bufferbloat)**是个真实存在的坑:缓冲区开得太大,队列在拥塞时不会立刻丢包,而是让分组在里面排很久,延时从几毫秒涨到几百毫秒甚至秒级,TCP 还察觉不到拥塞,继续按原来的速率发。表现为"测速很正常,但网页打开就是慢半拍"。很多家用路由器出厂缓冲区设置偏大,玩游戏时如果家里有人在下载,延时能从 10 毫秒飙到 200 毫秒以上,就是这个原因。
提示:判断自己家有没有缓冲膨胀,最土的办法是"空闲时 ping 一次,同时跑满下载再 ping 一次",两次结果差 100 毫秒以上基本就中招了。有条件的话在路由器上开 SQM 或者限速到带宽的 85%,能显著改善。
3.2 丢包之后:TCP 与 UDP 的反应完全不同
丢包本身不可怕,可怕的是丢了之后怎么办。TCP 有重传机制:超时没收到 ACK 就重发,收到三个重复 ACK 就快速重传,同时拥塞控制会把发送窗口砍半,主动降速。这意味着丢包对 TCP 的影响不是"丢一个补一个"这么简单,而是一次丢包会连带引发降速,吞吐量掉得比丢包率本身严重得多。
经验数据是:丢包率在 0.1% 以内,TCP 基本感觉不到;到 1% 左右,长肥管道(高带宽高延时)上的吞吐量可能掉一半;超过 2% 到 3%,很多应用会明显卡顿。所以看到监控上 1% 的丢包别觉得"才 1%",对 TCP 来说这已经是重伤了。UDP 则完全不管,丢了就丢了,实时音视频靠上层自己的 FEC、NACK、抖动缓冲来补,这也是为什么网络一抖动,视频会议先花屏但下载还能继续。
无线环境下的丢包要区别对待。空口误码导致的丢包不代表拥塞,但 TCP 的所有实现都会把它当拥塞处理,一律降速,这就是无线链路上 TCP 性能差的老问题。所以无线接入网普遍会在链路层做本地重传(比如 802.11 的 ARQ),把误码掩盖掉,让上层看到的是"没丢包,只是延时大了一点"。
3.3 用 ping 估丢包率的正确姿势
直接用默认的ping 目标地址只看四个包,丢一个就是 25% 丢包率,完全没有统计意义。要得到可用的数字,至少跑上百个包。
# 200 个包,间隔 20ms,最后看统计摘要 ping -c 200 -i 0.02 目标地址 | tail -3 # 记录每个包的 RTT,便于事后看抖动分布 ping -c 200 -i 0.02 目标地址 | awk -F'time=' '/time=/{print $2}' | cut -d' ' -f1 > rtt.txt看结果时注意三点。第一,平均延时没有意义,要看最大值和分位数,P99 延时才反映真实体验。第二,丢包要判断发生在哪个方向,单向丢包在 ping 上看不出区别,得两端同时抓包对账。第三,ICMP 被限速会造成假丢包,如果 ping 显示丢包但 TCP 业务正常,八成是设备对 ICMP 做了限速,不是真丢。
我遇到过最典型的一次,某台服务器 ping 过去 30% 丢包,但 SSH 和 HTTP 全都正常,最后确认是中间防火墙对 ICMP 做了速率限制,实际业务流量一点问题没有。从那以后我看丢包一定交叉验证,至少用一个 TCP 层的探测做对照。
4. 吞吐量:端到端到底能跑多快
4.1 瞬时吞吐量与平均吞吐量
瞬时吞吐量是某一时刻接收方收到数据的速率,平均吞吐量是一段时间内的总量除以时间。看视频的时候瞬时吞吐量波动很大,缓冲一下就降下来,但平均吞吐量决定了能不能流畅播放。这两个指标经常被混用,说"我带宽 500 兆"其实指的是链路速率,不等于实际吞吐量。
实际吞吐量受一堆因素制约:链路速率、协议开销、TCP 窗口大小、服务器端的发送能力、中间设备的处理能力、甚至对端磁盘的读取速度。链路速率只是上限,真实吞吐量通常只有它的 60% 到 90%,无线环境更差。
4.2 瓶颈链路:木桶最短的那块板
对于一条端到端的路径,中间串了 n 条链路,速率分别是 R1、R2……Rn,端到端吞吐量约等于min(R1, R2, ..., Rn),也就是瓶颈链路速率。这个结论很朴素,但实际排查时经常被忽略。
一条路径上链路速率往往差别巨大:家里千兆入户,接入网可能只有几百兆共享,骨干网几十上百 G,服务器那边还可能有带宽限制或者 QoS 策略。真正决定你下载速度的,是最慢的那一段,而且这一段可能根本不在你控制范围内。
更隐蔽的是,瓶颈可能不是链路,而是设备。一台老防火墙的转发能力只有 200 Mbps,你链路是 1 Gbps 也白搭;一台虚拟机的网卡队列配置不当,跑到 2 Gbps 就开始丢包;甚至 CPU 跑满也会导致软中断处理不过来,吞吐量卡在某个奇怪的数值上不动。所以实测吞吐量上不去,别一上来就怀疑运营商,先看看自己这边的设备。
4.3 带宽时延积:窗口不够就跑不满
这是我觉得最值得记住的一个概念。带宽时延积 BDP = 带宽 × RTT,含义是"在收到第一个 ACK 之前,发送方最多能发出去多少数据"。如果 TCP 窗口小于 BDP,链路就吃不饱。
举个例子,100 Mbps 链路、RTT 100 毫秒,BDP = 10^8 × 0.1 / 8 = 1.25 MB。也就是说发送方必须有至少 1.25 MB 的在途数据,才能把这 100 Mbps 跑满。如果窗口只有 64 KB,理论上限就是 64 KB / 0.1 s = 5.2 Mbps,只有链路的 5%。
这就解释了为什么跨洲际的长距离传输特别依赖窗口缩放(window scaling)和 BBR 这类拥塞控制算法。老式的 TCP 实现窗口小,跨洋传输经常只能跑到几十兆,链路明明有几百兆带宽。调整窗口、换算法,吞吐量能翻好几倍,链路一点没变。
# 服务端 iperf3 -s # 客户端,单线程 TCP,跑 30 秒 iperf3 -c 服务端地址 -t 30 # 多线程并发,看看是不是单流受限 iperf3 -c 服务端地址 -t 30 -P 8 # UDP 测试,看丢包率 iperf3 -c 服务端地址 -u -b 100M -t 30实测的经验是:单线程跑不满但多线程能跑满,说明是单流窗口或算法的问题;单线程多线程都跑不满,说明瓶颈在链路或者设备转发能力。这个判断方法非常实用,比盲目调参数高效得多。
另外,测速时要注意 MTU 和分片。路径 MTU 不对会导致大包被丢,表现为小请求正常、大文件传输卡死。
# Linux 下探测路径 MTU,1472 + 28 字节头 = 1500 ping -M do -s 1472 目标地址如果 1472 不通但 1400 通,说明路径上有 MTU 小于 1500 的环节,常见的比如隧道封装或者某些专线。
5. 常见问题速查与踩坑实录
5.1 现象、原因、排查手段对照表
| 现象 | 最可能的原因 | 排查命令/手段 |
|---|---|---|
| 带宽升级后大文件下载变快,但游戏延时没变 | 传播延时和排队延时未改善 | ping看 RTT 最小值,mtr看逐跳 |
| 测速正常,网页打开慢半拍 | 缓冲膨胀,队列延时高 | 空闲与满载时分别ping对比 |
| ping 丢包但业务正常 | ICMP 被限速或过滤 | 用 TCP 层探测交叉验证 |
| traceroute 中间跳延时很高,后续正常 | 控制平面 ICMP 限速 | 对比多个目标地址的路径 |
| 单线程下载慢,多线程能跑满 | 单流窗口不足或受限于拥塞控制 | iperf3单流与多流对比 |
| 大文件传到一半卡死 | 路径 MTU 不匹配导致分片丢弃 | ping -M do -s二分探测 |
| 平均利用率 60%,但高峰期延时飙升 | 突发流量导致瞬时队列打满 | 看 P99 延时而非平均值 |
5.2 几个我实际踩过的坑
第一个坑是把ping 的 RTT 当成单向延时。早期做接口超时设置时,我把超时时间设成 ping 值的一半,结果线上偶发超时。后来才想明白,RTT 是往返总和,而且路径不对称时去程和回程的延时可能差很多。现在我的做法是超时时间至少给到 RTT 的 3 到 5 倍,再配合重试。
第二个坑是在虚拟机里测吞吐量。有一次测出来只有 300 Mbps,查了半天链路和交换机都没问题,最后发现是虚拟网卡的队列数默认是 1,中断全压在单个 CPU 核上。调多队列、开多队列 RSS 之后直接跑到 2 Gbps。教训是:测吞吐量之前先把测试环境本身的瓶颈排除掉。
第三个坑是拿平均延时评估实时业务体验。平均 30 毫秒看着挺好,但抖动很大,P99 到 200 毫秒,语音通话照样断续。实时业务看的是抖动和尾延时,不是平均值。做监控的时候一定要把 P95、P99 拉出来,光看均值会漏掉大部分问题。
5.3 面试里绕不开的几个点
这部分内容在面试中出现频率很高,几个问题几乎必问。传输延时和传播延时的区别是最常见的开场,答的时候最好带上量级对比,说清楚"加带宽治不了高延时"的原因,比背定义有说服力。流量强度 La/R 接近 1 时会发生什么,要答出排队延时的非线性增长和队列溢出丢包,能补一句"所以工程上利用率控制在 70% 到 80%"就更完整。
端到端吞吐量由什么决定,标准答案是瓶颈链路,但加一句"实际还要看设备转发能力、TCP 窗口和带宽时延积"会明显加分。为什么跨洋链路带宽很大但单流传输很慢,答案就是 BDP 和窗口缩放,能顺带提一句 BBR 这类基于带宽和 RTT 建模的拥塞控制思路。
还有个容易被追问的点:分组交换相比电路交换的优势和代价。优势是统计复用带来更高的资源利用率,代价是拥塞时的排队延时和丢包,没有服务质量保证。回答时最好举出课上那个 35 个用户对 10 个用户的例子,数字比形容词有力得多。
6. 这一讲真正想让你建立的直觉
学完这四个概念,我自己最大的收获不是会算公式了,而是建立了一套判断"网络问题出在哪一层"的直觉。看到延时高,先问是固定的还是波动的——固定的大概率是传播延时,地理距离决定的,没法优化;波动的大概率是排队延时,跟拥塞有关,能通过限速、扩容、调度改善。看到丢包,先问是链路上丢的还是队列满丢的——前者往往是物理问题或者设备故障,后者是容量问题。看到吞吐量上不去,先问是链路瓶颈还是端到端瓶颈——用单流和多流一对比基本就能分清。
这套直觉在实际工作里比背公式有用得多。有一次线上接口大面积超时,大家第一反应是服务端性能问题,我拉了个 mtr 一看,末跳延时和抖动都正常,但中间某跳的丢包率在波动,最后定位是机房出口的链路质量问题,跟应用一点关系没有。如果没有前面这些基础,很难在几分钟内把怀疑范围缩小到网络层。
还有一个体会是关于"平均值"的警惕。教材里的公式给的都是期望值,真实网络里分布的长尾才是决定体验的东西。平均 20 毫秒延时的链路,如果 P99 是 500 毫秒,用户照样骂街。所以后面再看 TCP 拥塞控制、CDN 调度这些内容,我都会多问一句:"这个优化改善的是平均值还是尾延时?"很多时候答案会让你重新评估方案的价值。