news 2026/10/17 1:42:41

网络负载:一条路能跑多少车,堵了会怎样

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
网络负载:一条路能跑多少车,堵了会怎样

一句话:网络负载就是网络上"正在跑的数据量"占了多少能力。负载低,数据畅通无阻;负载接近上限,数据开始排队,于是延迟升高、抖动变大,最后直接丢包。

前几篇讲了网关(数据从哪出去)、子网(谁和谁是邻居)、抖动缓冲(怎么扛波动)、CDN(怎么把大文件分散出去)。这一篇讲它们背后共同的问题:网络的能力是有限的,用多了会怎样,以及怎么少用。


一、生活里的例子:高速公路

一条三车道高速公路:

路况车流体验
凌晨车很少一路 120 码
平时车不少,但能跑偶尔要减速
节假日接近路的极限走走停停,时快时慢
出了事故超过极限堵死,后面的车根本上不了高速
高速公路网络
车道数带宽:最多能跑多少数据
实际车流吞吐量:实际跑了多少
车流 ÷ 能力负载率 / 利用率
收费站排队路由器、网卡里的队列
走走停停延迟升高、抖动变大
匝道封闭,车上不去丢包

关键:路堵不堵,不只看车多不多,要看"车流 ÷ 路的能力"。同样 1000 辆车,在单车道是灾难,在八车道很轻松。


二、几个必须分清的概念

概念意思单位
带宽(Bandwidth)链路的最大能力Mbps、Gbps
吞吐量(Throughput)实际传了多少Mbps
利用率(Utilization)吞吐量 ÷ 带宽%
包速率(PPS)每秒处理多少个包包/秒
延迟 / 抖动 / 丢包负载高的后果ms、ms、%

单位换算:最常见的误会

运营商说的 100M 宽带 = 100 Mbps(兆比特每秒) 下载软件显示的速度 = MB/s(兆字节每秒) 1 字节 = 8 比特 → 100 Mbps ÷ 8 ≈ 12.5 MB/s

所以"办了 100M 宽带,下载只有 12MB/s"是正常的,不是被骗。

带宽和 PPS 是两种不同的上限

情况先撞到哪个上限
下载大文件(每个包都接近 1500 字节)带宽
游戏同步(每个包只有几十到几百字节,但非常多)PPS

游戏服务器经常是带宽还很空,PPS 已经满了。每个包无论大小,都要经过一次中断、一次系统调用(前面讲过的recvfrom/sendto)、一次路由查表 ——处理开销是按"个"算的。很多云服务器也会对每台机器的 PPS 设上限。


三、负载高了为什么会变慢:排队

网络设备里,每个出口都有一个队列。包到得比发得快,就要排队。

一个反直觉的事实:利用率不是"越接近 100% 越好"

用最简单的排队模型估算,平均等待时间大约是:

平均耗时 ≈ 处理一个包的时间 ÷ (1 − 利用率)
利用率相对于空闲时的耗时
10%1.1 倍
50%2 倍
80%5 倍
90%10 倍
95%20 倍
99%100 倍
延迟 │ ╱ │ ╱ │ ╱ │ ╱ │ __╱ │____________________───── └──────────────────────────────────── 利用率 0% 50% 80% 100% ↑ 过了这里,延迟开始飙升

这就是为什么运维常说"链路利用率长期超过 70%~80% 就该扩容了"。不是因为满了,而是因为还没满,体验就已经崩了。

负载升高的完整过程

负载低 → 包几乎不用排队 延迟稳定 负载升高 → 队列时长时短 **抖动变大**(抖动缓冲区要变大) 接近上限 → 队列一直很长 **延迟持续升高** 超过上限 → 队列满了,新包被直接扔掉 **丢包**

上一篇提到的**缓冲膨胀(Bufferbloat)**就发生在第三步:路由器的队列设得太大,宁可让包排几百毫秒也不丢,结果"能用但巨卡"。


四、负载从哪来:家里、路上、服务器

一个玩家打游戏,数据要经过好几段路,任何一段满了都会卡:

玩家电脑 ─ Wi-Fi ─ 家用网关 ─ 运营商接入 ─ 骨干网 ─ 机房出口 ─ 服务器网卡 ─ 游戏进程 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧
位置典型的负载问题
① 玩家电脑后台在下载、同步网盘、更新系统
② Wi-Fi信号差、邻居信道干扰、几十台设备抢一个路由器
③ 家用网关上行带宽小(很多宽带下行 300M,上行只有 30M);有人在直播、上传视频
④⑤ 运营商 / 骨干晚高峰拥塞、跨运营商互联口拥塞
⑥ 机房出口开服、版本更新、遭遇 DDoS
⑦ 服务器网卡带宽或 PPS 上限
⑧ 游戏进程处理不过来,接收缓冲区堆满,内核开始丢包

第 ⑧ 点容易被忽略:网络没满,是程序处理不过来。表现和网络拥塞一样(延迟高、丢包),但要优化的是代码,不是带宽。


五、动手查看网络负载

客户端 / 个人电脑

系统工具看什么
Windows任务管理器 → 性能 → Wi-Fi/以太网实时收发速度
Windows资源监视器 → 网络哪个进程在占带宽
macOS活动监视器 → 网络每个进程的流量
通用ping -t 服务器(边下载边 ping)负载升高时延迟有没有飙升

一个简单的"缓冲膨胀"自测:

1. 不开任何下载,ping 游戏服务器 → 比如 30ms 2. 开一个大文件下载,同时继续 ping → 如果涨到 200ms+,就是排队严重

Linux 服务器

nload# 实时看网卡收发带宽iftop# 看哪些连接在占流量sar-nDEV1# 每秒的收发字节数、包数(PPS)ss-s# 连接数统计netstat-su# UDP 统计:留意 "receive buffer errors"(程序来不及收)iperf3-c服务器IP# 测两台机器之间的实际带宽

netstat -su里的receive buffer errors一直在涨,说明内核替你丢包了:网络把包送到了,你的进程没来得及读。这时调大接收缓冲区(SO_RCVBUF)或优化处理逻辑,比加带宽有用。

Unity 里

工具用途
Profiler → Network Messages / Network Operations看每帧收发多少消息、多少字节
Netcode for GameObjects 的Network Profiler / Runtime Network Stats Monitor按对象、按消息类型统计流量
Wireshark抓包,看每个包到底有多大、多少个

六、算一笔账:射击游戏到底要多少带宽

假设一局64 人的射击游戏,服务器30Hz(每秒 30 次快照)。

每个玩家的状态(朴素写法)

字段类型字节
玩家 IDint4
位置 xyz3 × float12
朝向4 × float(四元数)16
速度 xyz3 × float12
血量、状态int + int8
合计52 字节

计算

每个快照: 63 个其他玩家 × 52 字节 ≈ 3276 字节 + IP/UDP 头部 28 字节 ≈ 3.3 KB ← 已经超过一个包大约 1200~1400 字节的安全上限,要拆成 3 个包 每个玩家下行:3.3 KB × 30 次/秒 ≈ 99 KB/s ≈ 790 Kbps 一局服务器出口:× 64 人 ≈ 6.3 MB/s ≈ 50 Mbps 一台机器跑 50 局:≈ 2.5 Gbps ← 普通千兆网卡直接打满 PPS:64 人 × 30 次 × 3 个包 × 50 局 ≈ 29 万包/秒

这还没算子弹、技能、特效事件、语音。所以带宽优化不是"锦上添花",而是决定一台服务器能开多少局、服务器账单是多少。

优化之后(第七节的技巧组合)

手段每个玩家的数据
朴素写法52 字节
量化 + 位压缩≈ 15 字节
只发变化的字段(增量)平均 ≈ 6 字节
只发附近 / 视野内的 20 人63 人 → 20 人
优化后每个快照:20 × 6 + 28 ≈ 150 字节 → 一个包就够 每个玩家下行:150 × 30 ≈ 4.5 KB/s ≈ 36 Kbps

从 790 Kbps 降到 36 Kbps,约二十分之一。同一台服务器可以多开很多局,玩家在差网络下也更稳。


七、降低负载的六个核心技巧

1. 量化:别用 float 存不需要那么精确的东西

地图范围 ±1024 米,精度 1 厘米就够:

可能的取值:2048 ÷ 0.01 = 204,800 种 2^18 = 262,144 ≥ 204,800 → 每个轴 18 位就够 位置:3 × 32 位 = 96 位 → 3 × 18 位 = 54 位
数据朴素量化后说明
位置96 位54 位1cm 精度
只有水平朝向(yaw)32 位10 位约 0.35° 精度
完整旋转128 位约 30 位"最小三分量"压缩四元数
血量 0~10032 位7 位
布尔状态(蹲下、开火…)各 8~32 位各 1 位
publicstaticclassQuantize{// 把 [min, max] 范围的 float 压到 bits 位整数publicstaticuintEncode(floatv,floatmin,floatmax,intbits){uintmaxValue=(1u<<bits)-1;floatt=UnityEngine.Mathf.InverseLerp(min,max,v);// 自动夹到 0~1return(uint)UnityEngine.Mathf.RoundToInt(t*maxValue);}publicstaticfloatDecode(uintq,floatmin,floatmax,intbits){uintmaxValue=(1u<<bits)-1;returnUnityEngine.Mathf.Lerp(min,max,q/(float)maxValue);}}

2. 位压缩:按"位"写,不按"字节"写

量化之后是 18 位、7 位、1 位这种零碎的长度,用字节存会浪费。写一个简单的位写入器:

publicclassBitWriter{readonlybyte[]buffer;intbitPos;publicBitWriter(intcapacity)=>buffer=newbyte[capacity];publicvoidWrite(uintvalue,intbits){for(inti=0;i<bits;i++){if(((value>>i)&1)!=0)buffer[bitPos>>3]|=(byte)(1<<(bitPos&7));bitPos++;}}publicvoidWriteBool(boolb)=>Write(b?1u:0u,1);publicintByteCount=>(bitPos+7)>>3;// 向上取整到字节publicbyte[]Buffer=>buffer;}

使用:

varw=newBitWriter(1200);w.Write(Quantize.Encode(pos.x,-1024,1024,18),18);w.Write(Quantize.Encode(pos.y,-64,192,15),15);// 高度范围小,位数更少w.Write(Quantize.Encode(pos.z,-1024,1024,18),18);w.Write(Quantize.Encode(yaw,0,360,10),10);w.Write((uint)health,7);w.WriteBool(isCrouching);// 合计 69 位 ≈ 9 字节,原来是 52 字节

这里的移位和按位与,和上一篇子网掩码的计算是同一套东西:>> 3是除以 8,& 7是对 8 取余。

3. 增量压缩:只发变了的

站着不动的玩家,位置每帧都一样,没必要重发。

每个字段前加 1 位"变了吗": 0 → 没变,后面不带数据 1 + 新值 → 变了 更进一步:以"客户端已确认收到的那一帧"为基准做差
情况位置字段
站着不动1 位
在走路1 位 + 54 位

基准必须是对方确认收到过的帧,否则丢一个包,后面的增量全都对不上。

4. 兴趣管理(AOI):只发你需要知道的

地图另一头的敌人,你看不见、听不见、打不着,就不用每帧告诉你。

距离 / 状态发送频率
视野内、50 米内每个快照都发(30Hz)
50~200 米每 3 个快照发一次(10Hz)
200 米外每秒 2 次,只发位置
墙后面、完全看不见不发(顺便防透视外挂)

第一篇讲遮挡剔除时说"看不见的不画";这里是"看不见的不发"。思路完全一样,甚至可以复用同样的空间划分和可见性数据。

5. 带宽预算 + 优先级:总量固定,重要的先发

给每个客户端每个快照一个固定预算(比如 1200 字节),按优先级装包,装不下的留到下一次 —— 但它的优先级会一直累积,不会被永远饿死。

usingSystem.Collections.Generic;publicclassPrioritySender{classEntry{publicintentityId;publicfloataccumulated;}readonlyList<Entry>entries=newList<Entry>();publicvoidAdd(intid)=>entries.Add(newEntry{entityId=id});// basePriority:根据距离、是否在视野内、是否在开火等计算publicList<int>Pick(System.Func<int,float>basePriority,System.Func<int,int>sizeOf,intbudgetBytes){foreach(vareinentries)e.accumulated+=basePriority(e.entityId);entries.Sort((a,b)=>b.accumulated.CompareTo(a.accumulated));varpicked=newList<int>();intused=0;foreach(vareinentries){intsize=sizeOf(e.entityId);if(used+size>budgetBytes)continue;// 这次放不下,下次优先级更高used+=size;picked.Add(e.entityId);e.accumulated=0;// 发了就清零}returnpicked;}}
实体基础优先级
正在朝你开火的敌人很高
附近的队友高
远处走动的敌人中
地上掉落的弹药箱低

6. 合并小包、控制发送频率

做法原因
一个快照的所有内容合进一个包每个包都有 28 字节头部,还占一次 PPS
包大小控制在约 1200 字节以内超过路径 MTU(常见 1500,各种隧道下更小)会被分片,丢一片整个包作废
客户端输入可以每帧带上最近几帧的输入多几个字节,换来丢一个包也不丢操作(冗余,用少量带宽换可靠性)
tick 率按需选择竞技射击 30~64Hz,休闲游戏 10~20Hz 往往足够

八、网络已经拥塞时怎么办:拥塞控制

TCP 自带"刹车"

TCP 发现丢包或延迟升高,会自动降低发送速度,等路况好了再慢慢提速。所以下载大文件时,网络不会被一个连接彻底堵死。

UDP 没有刹车,游戏要自己做

射击游戏大多用 UDP。如果路已经堵了还照常狂发,只会让队列更长、丢包更多,自己和别人都更卡。

自适应发送的简单思路:

观察到的信号反应
RTT 持续升高、抖动变大降低非关键数据的频率(远处实体、装饰性事件)
丢包率超过阈值(如 5%)降低快照频率,比如 30Hz → 20Hz
一段时间都很稳定慢慢恢复
极差提示"网络不佳",只保留最核心的数据

这和抖动缓冲区里"缓冲区根据抖动自动调整"是一个思路:持续测量,动态适应,变化要平滑。

客户端能做的

措施效果
游戏中自动暂停后台的资源下载不和自己的战斗流量抢上行
热更新下载限速边玩边下时不影响对局
语音降低码率网络差时优先保证操作同步

九、服务器扛不住时:把负载分出去

单台机器的带宽、PPS、CPU 都有上限。超过之后,就要把负载分摊到多台机器。

手段分摊的是什么前面讲过的关联
CDN安装包、补丁、热更资源上一篇:静态大流量完全不经过游戏服务器
负载均衡器登录、商城、匹配等 HTTP / TCP 请求把请求轮流或按负载分给多台后端
按对局分配服务器实时战斗一局只在一台机器上,新开的局放到最空闲的机器
多地区部署跨地区的长距离流量就近接入,减少骨干网负载和延迟
抗 DDoS / 清洗恶意流量攻击的本质就是人为制造网络负载
┌─ 静态资源 ─→ CDN 玩家 ─────────┼─ 登录/商城 ─→ 负载均衡器 ─→ 多台 Web 服务器 └─ 实时战斗 ─→ 匹配服务分配 ─→ 某台战斗服务器(某一局)

这又回到"架构原则":不同类型的负载走不同的路,各自独立扩容,一边出问题不拖垮另一边。


十、和前面几篇的联系

前面讲过的关系
网关家用网关的上行带宽往往是瓶颈;开启 QoS / SQM 可以让游戏包优先、减少排队
子网掩码网段越大,广播越多,局域网本身的负载就越高 —— 划分子网能减少广播负载
抖动缓冲区负载升高 → 排队变化 → 抖动变大 → 缓冲区被迫变大 → 延迟升高
CDN把最大的那部分流量(下载)从源站卸掉
遮挡剔除"看不见的不画"与兴趣管理"看不见的不发"同一个思路
系统调用每个包一次sendto/recvfrom;PPS 高时,可以用批量收发(如 Linux 的sendmmsg/recvmmsg)减少系统调用次数
对象池每个包都new byte[]会产生大量 GC,收发缓冲区应复用

十一、常见的坑

坑现象解决
把 Mbps 当 MB/s带宽规划差了 8 倍记住:除以 8
只看带宽不看 PPS带宽很空,服务器却在丢包同时监控 PPS;合并小包
利用率跑到 90% 才扩容还没满,延迟已经飙升70%~80% 就开始规划扩容
位置、旋转全用 float 原样发带宽是需要的好几倍量化 + 位压缩
全图所有实体发给所有人人一多带宽平方级增长(n 人 × n 人)兴趣管理 + 优先级
增量压缩的基准没被确认丢包后客户端状态全乱以客户端 ACK 过的帧为基准
包太大被分片丢包率莫名其妙偏高控制在约 1200 字节以内
UDP 拥塞时照常狂发越卡越发,越发越卡根据丢包和 RTT 自适应降频
进程处理慢误以为网络问题加了带宽依然丢包看netstat -su的 buffer errors;优化接收线程
对局中后台下载热更新玩着玩着突然延迟飙升对局中暂停或限速下载
只在局域网测试本地流畅,上线就卡用网络模拟工具限制带宽、加丢包测试
没算服务器出口账单上线后流量费超预算开发阶段就按"每人每秒多少字节"做预算

一句话总结

网络负载 = 实际流量 ÷ 网络能力。负载升高时,数据先排队(抖动变大、延迟升高),再丢包;而且延迟不是线性增长,利用率过了 70%~80% 就会急剧变差。
衡量负载要同时看带宽和

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 1:41:02

大模型时代敏感数据安全实战:从泄露路径到守护架构

最近一年&#xff0c;我身边越来越多的企业客户跑来问同一个问题&#xff1a;上了大模型之后&#xff0c;敏感数据到底怎么守&#xff1f;有些是金融行业的&#xff0c;想把客户风险评估交给大模型做&#xff1b;有些是做政务平台的&#xff0c;打算用 RAG 把政策文件库接进去&…

作者头像 李华
网站建设 2026/10/17 1:39:33

智能体冲击传统行业:老师傅与AI协作成核心资产

这两年我身边一批在传统行业干了很多年的朋友&#xff0c;心情都不太一样。有人焦虑&#xff0c;说AI这么猛&#xff0c;是不是迟早要把自己替代掉&#xff1b;也有人忙得脚不沾地&#xff0c;因为公司突然发现&#xff0c;能把AI真正用起来的人&#xff0c;恰恰是那些最懂业务…

作者头像 李华
网站建设 2026/10/17 1:36:50

深度负反馈到底多深?闭环增益、虚短虚断与四种反馈组态全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:36:24

Matlab控制系统建模:从传递函数到仿真实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:36:00

自制Chrome/Edge新标签页扩展:Manifest V3前端开发实战

简介&#xff1a;面向具备前端基础的开发者&#xff0c;这份压缩包围绕自制Edge与Chrome标签页扩展插件的完整开发流程整理而成。内容以JavaScript、HTML、CSS及jQuery为核心&#xff0c;从manifest.json配置与权限声明入手&#xff0c;逐步覆盖后台脚本、内容脚本、弹窗界面等…

作者头像 李华