一个视频站点出口带宽从 1G 扩到 4G,用户反馈"还是卡"。看监控:带宽峰值只用到 1.2G,往返时延没有变化,首帧时间也没变。原因在别处——首帧慢是因为播放器要等一个 2MB 的 manifest 索引,而这个请求走的是另一条有小连接的线路,每次只有 40KB/s。
带宽是"每秒能搬多少",延迟是"第一个字节什么时候到"。用户感知到的是后者,而扩容买的是前者。这是性能问题里最常见的一次买错东西。
1. 两个量在什么情况下各自成为瓶颈
| 场景 | 瓶颈 |
|---|---|
| 大文件下载、视频传输 | 带宽 |
| 大量小请求、API 调用 | 延迟 |
| 首屏渲染(多次往返) | 延迟 × 往返次数 |
| 数据库同步、备份 | 带宽 |
两个量的关系可以写成一句话:总耗时 ≈ RTT × 往返次数 + 数据量 / 带宽。加带宽只作用在第二项,减往返作用在第一项——而当数据量不大时,第一项占九成。
这个公式还有个用法:把两个数代进去,就知道该优化哪一边。如果 RTT×次数 是 800ms、数据量/带宽是 40ms,那优化顺序一目了然。
第二项与第三项在业务系统里占绝对多数:一个页面有 60 个资源请求,每个 20KB,总数据量 1.2MB,但真正决定加载时间的是每个请求的往返时延乘以串行层数。
2. 为什么加带宽对小请求无效
| 机制 | 说明 |
|---|---|
| 每请求固定开销 | 握手、DNS、TLS 不受带宽影响 |
| 拥塞窗口增长 | 小请求在慢启动阶段就结束了 |
| 并发连接数限制 | 带宽再大也只能跑这么多条 |
| 队头阻塞 | 同一连接上的请求互相等 |
第二项最反直觉:TCP 的传输速率是慢慢涨上去的,一个 20KB 的响应可能在窗口还没涨到满速时就传完了,所以"带宽更大"对它没有任何帮助。
第四项与第三项常一起出现:同一连接上的请求必须按顺序返回,一个慢请求会堵住后面所有请求。这就是为什么"资源不多但页面慢"往往出自某一个慢接口。
第三项在 HTTP/1.1 时代是硬约束:浏览器对同一域名只开 6 条连接,第 7 个请求必须排队。带宽翻四倍,队列长度不变。
// 一个 20KB 资源在两种带宽下的耗时(RTT 40ms,忽略慢启动)
带宽 10Mbps:传输 16ms + RTT 40ms ≈ 56ms
带宽 100Mbps:传输 1.6ms + RTT 40ms ≈ 42ms
// 带宽翻 10 倍,耗时只降 25% —— 因为 RTT 是常数项
3. 延迟的四个组成部分
| 组成 | 可否优化 |
|---|---|
| 传播延迟(物理距离) | 只能靠就近部署 |
| 传输延迟(数据量/带宽) | 加带宽、减体积 |
| 排队延迟(拥塞) | 减少并发、加容量 |
| 处理延迟(服务端计算) | 优化代码与查询 |
四个组成里,只有"排队延迟"会随时间剧烈波动:同一链路在高峰与低谷可能差十倍,而其余三项基本稳定。所以"白天慢晚上快"这类现象几乎总能归到排队。
第一项最容易被忽略:跨洲的 RTT 是物理定律,光纤里每 1000 公里约 5ms 单程。北京到法兰克福往返约 180ms,任何带宽都无法改善。
第三项是"带宽够但延迟高"的常见原因:链路拥塞时数据包排队,此时扩容有效,但要看是"链路拥塞"还是"服务端排队"——后者加带宽无用。
4. 真正有效的四类手段
| 手段 | 作用对象 |
|---|---|
| 减少请求数(合并、内联) | 往返次数 |
| 就近部署(CDN、多区域) | 传播延迟 |
| 连接复用(keep-alive、HTTP/2) | 握手与队头阻塞 |
| 减少体积(压缩、按需) | 传输延迟 |
减请求数还有个"零成本"版本:去掉根本没人看的资源。埋点、旧版兼容脚本、重复引入的库,在三年积累的页面里通常占三成——这类删除不改变功能,只减少往返。
第一项的收益通常最大:60 个请求减到 15 个,等效于把延迟降到四分之一——而任何带宽升级都做不到这一点。
就近部署的收益可以用一个数估出来:每 1000 公里约 5ms 单程,往返翻倍。把服务从一地扩到三地(每地覆盖 1000 公里半径),对远端用户是 30ms 以上的净收益,而这是纯带宽买不到的。
// 就近部署的收益估算(单程 5ms / 1000km)
距离 3000km:单程 15ms,RTT 30ms
距离 300km :单程 1.5ms,RTT 3ms
// 每个串行请求省 27ms;10 层串行就省 270ms
第三项在 HTTP/2 下解决了队头阻塞(同一连接多路复用),但引入了新的约束:单连接上的流数有上限,超过后仍会排队。
// 三件低成本的事:连接复用、压缩、就近
keepalive_timeout 65;
gzip on;
gzip_types text/css application/javascript application/json;
// 静态资源交给 CDN,源站只保留动态请求
5. 怎么判断该加带宽还是降延迟
| 观察 | 结论 |
|---|---|
| 带宽利用率 > 70% 且延迟升高 | 加带宽 |
| 带宽利用率 < 30% 但延迟高 | 查延迟,别加带宽 |
| 单请求体积大、数量少 | 加带宽或压缩 |
| 请求多、体积小 | 减请求数、就近部署 |
第一项与第二项的差别就是开头那个案例:带宽只用到 30%,扩容必然无效。这条判据简单到可以在值班手册里写一句。
// 先量三个数:带宽利用率、RTT、每页请求数
sar -n DEV 1 5 | awk '/eth0/ {print $5*8/1e6 " Mbps in"}'
ping -c 20 target | tail -2 # RTT 与抖动
curl -s -w '%{time_connect} %{time_starttransfer} %{size_download}\n' -o /dev/null URL
// 三个数一起看,才知道瓶颈在带宽还是在往返
6. 排错:五种"扩容后没变化"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 带宽利用率没涨 | 瓶颈不在带宽 | 查 RTT 与请求数 |
| 首屏没变快 | 关键路径是串行往返 | 减依赖层级 |
| 单用户快、并发时慢 | 服务端排队 | 查后端而非网络 |
| 部分用户仍慢 | 就近部署没覆盖 | 查 CDN 命中与线路 |
| 传输大文件仍慢 | 单连接窗口限制 | 多连接或调窗口 |
排查时还应当区分"用户感知"与"指标":首字节时间(TTFB)反映服务端,加载完成时间反映网络与渲染。两个指标一起看,能立刻分辨是"后端慢"还是"传输慢"。
// 拆分三段耗时:连接、首字节、整体
curl -s -o /dev/null -w 'dns=%{time_namelookup} conn=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}
' URL
// conn 大 → 网络/握手;ttfb-conn 大 → 服务端;total-ttfb 大 → 传输/体积
第二项是最常见的误判:"首屏慢"被当成"网络慢",实际是关键路径上串了四个接口,每个 200ms——加带宽一分钱效果都没有。
7. 边界:什么情况带宽确实是答案
| 场景 | 为什么 | 做法 |
|---|---|---|
| 视频、大文件分发 | 数据量主导 | 加带宽或上 CDN |
| 备份与同步 | 吞吐主导 | 加带宽或压缩 |
| 内网批量传输 | RTT 很小 | 带宽几乎是唯一变量 |
| 出口被限速 | 硬上限 | 扩容直接有效 |
判断口径一句话:这次慢,是"每秒搬得少"还是"第一字节来得晚"。前者加带宽,后者减往返、就近部署、复用连接。
8. 自测
- 带宽与延迟分别主导哪类场景?为什么业务系统里多数是后者?
- 为什么"一个 20KB 的响应"从加带宽中获益很小?慢启动在其中起什么作用?
- HTTP/1.1 的连接数限制如何让带宽扩容失效?HTTP/2 解决了什么、又带来什么新约束?
- 延迟的四个组成部分里,哪一项完全无法通过带宽改善?为什么?
- 判断"该加带宽还是降延迟"的两个观察是什么?为什么"带宽利用率 < 30% 但延迟高"能直接排除扩容?
- 判断口径的那个问题是什么?