한국어English日本語简体中文繁體中文DeutschไทยTiếng ViệtРусскийPortuguês (Brasil)EspañolBahasa Indonesia

游戏卡顿白皮书纯文本版

网络游戏为什么会一卡一卡、瞬移、掉线?本纯文本版从玩家画面到服务器数据库,逐层整理了 228 个原因。每个原因都附有症状、负责团队(研发团队、运维团队)、数值和权威出处。

含图示和可动手操作实验的完整版是游戏卡顿白皮书。本版把同样的原因、术语和出处集中在一个页面里,无需 JavaScript 即可阅读。每个原因另有单独页面(c/ID.html)。单个 Markdown 文件版本见 llms-full.txt。

按症状查找

卡顿源于四个因素: 延迟(距离、排队和处理耗时,让每个数据包都稳定地晚到一段时间。) 抖动(平均值没问题,但有的数据包来得快,有的来得慢。Wi-Fi、拥塞的线路、繁忙的 CPU 都会造成抖动。) 丢包(队列溢出、无线信号干扰、故障设备都会把数据包丢掉。线路短暂中断也属于连续丢包。) 停顿(服务器 tick 变慢或停住(GC、锁、同步调用、过载),或者自己电脑上的帧停住。线路完全正常时也会发生。)

负责团队与负责方代号

代号团队负责方范围
cli研发团队客户端开发游戏客户端代码:帧、GC、加载,插值、外推、预测,客户端的网络处理(包括发送心跳和自动重连)
srv研发团队服务器开发游戏服务器代码:tick、线程、锁,同步设计,连接处理(accept 循环、listen 参数),心跳响应与断开连接的清理,socket 选项,查询与事务设计
net运维团队网络运维线路与 IDC 网络设备(交换机、路由器、防火墙、负载均衡器、DDoS 防护),云上的网络 ACL、VPC 路由、负载均衡器,运营商与对等互联
sys运维团队系统运维服务器硬件与云实例(包括安全组和连接跟踪),操作系统与内核配置,网卡,发布与监控环境
dba运维团队数据库运维DB 服务器与存储,DB 配置、复制、备份,缓存服务器
ext外部外部玩家的电脑与家庭网络、运营商链路(不在我方合同范围内)、云厂商。无法直接修复,应对方式是引导玩家、向对方提需求或绕行

L1 客户端游戏进程

16 个原因 · 完整版章节

帧耗时尖峰 Frame hitch

ID cg-hitch · 主责 研发团队·客户端开发

某一帧的计算比平时多花了几倍时间,画面短暂停顿。

起因 技能特效激增、大量实体生成、UI 整体刷新挤在同一帧 → 结果 16.7 ms 内没能完成,要花 50~300 ms → 画面表现 画面顿一下,下一帧所有人一下子同时移动

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
只有我
何时出现
人多的时候, 做特定操作时, 偶尔随机
负责方
主责 研发团队·客户端开发
研发团队要做的事
把繁重的工作拆到多帧执行,用 profiler 找出耗时冲高的帧,限制特效数量上限。
数值参考
60 FPS 下一帧是 16.7 ms。只要有一帧超过 50 ms,就很容易觉得“卡了一下”。
监控图上
偶发随机尖峰 · 帧耗时
查看位置
用 PresentMon 录制游戏中的帧耗时(FrameTime),以及 CPU、GPU 在该帧上花费的时间(CPUBusy、GPUBusy)。移动端看 Android vitals 的慢速会话、渲染缓慢指标
确认依据
平时在 16.7 ms 附近的帧耗时冲到 50 ms 以上的时刻,与技能演出、大量实体生成、UI 整体刷新重合,而此时 ping 不变
排除依据
帧耗时平稳、只有其他角色顿一下,是网络侧问题,看“没有插值缓冲或缓冲过短”等。冲高间隔有规律,先看“客户端垃圾回收”
确认手段
需在玩家侧环境确认
出处 3 条

客户端垃圾回收 Client GC (Unity C#, Unreal, Lua)

ID cg-gc · 主责 研发团队·客户端开发

回收用完丢弃的内存(垃圾对象)期间,整个游戏会停住。特点是按固定间隔一卡一卡。

起因 每帧都创建并丢弃临时字符串、数组、列表 → 结果 垃圾对象堆积后,GC 暂停主线程来回收 → 画面表现 每隔几秒到几十秒,有规律地一卡一卡

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
只有我
何时出现
固定周期, 人多的时候
负责方
主责 研发团队·客户端开发
研发团队要做的事
减少分配(避免字符串拼接、LINQ、lambda 捕获),使用对象池,开启增量 GC(Unity 2020 起为默认值),在加载界面这类停一下也无妨的时机提前执行 GC。
数值参考
通常每次几 ms 到 100 ms,低端手机或内存占用大的游戏会更长(实验中约 150~170 ms)。Unity 的 GC 每次运行都要扫描整个堆,游戏占用的内存越多,耗时越长。
监控图上
周期性尖峰 · 帧耗时、GC 执行时刻
查看位置
在开发版本中查看 Unity Profiler 的 GC.Collect、GC.Alloc 标记。Unreal 看 stat GC 和 stat Hitches(超过 t.HitchFrameTimeThreshold 所设时长的帧会记入日志)
确认依据
每个冲高的帧里都有 GC.Collect 区段,其长度与冲高时长相近,间隔为几秒到几十秒且有规律。人多的地方每帧 GC.Alloc 增加
排除依据
冲高的帧里没有 GC 区段,看“主线程同步加载/Shader 编译”或“大规模同屏渲染负载”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
在 Unity 这类使用 C# 的客户端中很常见。每帧重新生成战斗日志字符串、伤害数字、UI 文本的代码是主要来源。如果只在人多的地方卡,说明有代码产生的垃圾对象随人数成比例增加。增量 GC 每帧只回收一小部分(Unity 默认 3 ms),但产生垃圾的速度一旦超过回收速度,最终还是会一次性停住。Unreal Engine 也有自己的 GC,用来清理不再使用的游戏对象。具体取决于引擎版本和设置,默认设置下约每 1 分钟运行一次,有时会造成间隔 1 分钟的短暂停顿。用 Lua 等脚本编写游戏规则的客户端,脚本本身的 GC 也会另外运行。
出处 5 条

主线程同步加载/Shader 编译 Synchronous asset load, shader compile

ID cg-sync-load · 主责 研发团队·客户端开发

第一次绘制没见过的区域、怪物、特效之前,要先读文件、生成 Shader,画面因此卡住。

起因 进入新区域,首次出现的技能、装备、怪物 → 结果 主线程等待文件读取和 Shader 编译 → 画面表现 只在第一次卡住 0.1~1 秒,第二次起就正常

症状
卡住, 一卡一卡
因素
停顿
谁会遇到
只有我
何时出现
移动中/切换地图时, 做特定操作时
负责方
主责 研发团队·客户端开发
研发团队要做的事
异步加载,提前加载(预热),在加载界面或首次启动时预编译 Shader,利用好加载界面。
数值参考
编译一个 Shader 要几十 ms,长的超过 100 ms。纹理加载视存储设备速度需要几十到几百 ms。
监控图上
开服/维护后激增 · 帧尖峰次数(版本更新、驱动更新后)
查看位置
用 PresentMon 把同一路线录两遍,对比第一次和第二次经过。开发版本中,Unreal 开启 r.PSOPrecache.Validation,查看 stat PSOPrecache 和日志中的“PSO PRECACHING MISS”;Unity 在 Profiler 的 Timeline 中查看冲高帧里的加载、Shader 区段
确认依据
只在第一次去的地方、第一次用的技能上冲高 0.1~1 秒,第二次就消失。版本更新或显卡驱动更新后反馈集中出现,随后减少
排除依据
同一地点每次都冲高,就不是 Shader 缓存问题。只在存储设备慢的 PC 上每次移动都重复出现,看“存储设备过慢导致资源流式加载滞后”;专用显存占满,看“显存(VRAM)不足”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
在 PC 上,显卡驱动会把编译好的 Shader 存进 Shader 缓存,之后重复使用。所以显卡驱动更新或游戏版本更新刚发布时,这份缓存会失效,原本流畅的玩家也会有一段时间重新一卡一卡。“版本更新后每到一个新地方就顿一下”是典型的反馈。
出处 5 条

存储设备过慢导致资源流式加载滞后 Slow storage stalls asset streaming

ID cg-asset-stream · 主责 研发团队·客户端开发 · 配合 外部·外部

在 HDD 这类慢速存储设备上,开放世界读取纹理、模型的速度跟不上移动,实体会晚出现,游戏也会因等待读取而一卡一卡。

起因 骑坐骑、传送等快速移动,或进入人多的地方,一下子需要大量新纹理、模型 → 结果 HDD 等慢速存储设备达不到所需的读取速度,读取请求堆积,部分加载还会让主线程一直等到完成 → 画面表现 纹理有一段时间是模糊的,建筑、角色出现得晚,等待读取的瞬间出现一卡一卡、卡住

症状
隐身/幽灵实体, 一卡一卡, 卡住
因素
停顿
谁会遇到
只有我
何时出现
移动中/切换地图时, 人多的时候
负责方
主责 研发团队·客户端开发 · 配合 外部·外部
研发团队要做的事
采用不在主线程等待读取的异步流式加载,根据移动方向和速度提前读取,先显示低分辨率贴图(mipmap)和简化模型、之后再替换,快速移动时流式加载跟不上就暂时限制移动速度或使用加载界面,在最低配置和推荐配置中写明是否需要 SSD。
外部要做的事
引导玩家把游戏装在 SSD 上,并检查同一块硬盘上是否有下载或杀毒扫描在运行。
数值参考
据 Microsoft 的说明,早期机械硬盘每秒读取几十 MB,NVMe SSD 每秒读取几 GB,上一代游戏的流式加载每秒用量在 50 MB 左右。以 SSD 速度为前提、读取量远超于此的游戏放到 HDD 上运行,读取很容易跟不上移动。
监控图上
仅部分偏高 · 帧尖峰次数(按存储设备类型)、磁盘读取延迟
查看位置
快速移动的同时,记录 Windows 性能监视器的 PhysicalDisk\Avg. Disk sec/Read(单次读取的平均耗时)、Current Disk Queue Length 与 PresentMon 帧耗时。开发版本中查看 Unreal 的 stat Streaming、stat AsyncLoad,以及 Unity Profiler 的 AssetBundle.asset/allAssets 警告(加载完成前就请求结果,主线程只能等待)
确认依据
快速移动时磁盘读取延迟和队列飙升,同一时刻帧耗时冲高,或纹理、实体出现得晚。同一场景在 SSD 上运行就消失
排除依据
磁盘空闲而纹理模糊,看“显存(VRAM)不足”;同一地点第二次去就正常,看“主线程同步加载/Shader 编译”
确认手段
需在玩家侧环境确认
深入了解
如果只在第一次停住、之后正常,更接近“主线程同步加载/Shader 编译”;只在存储设备慢的 PC 上每次移动都重复出现,就是这个原因。显存不够、纹理被反复卸载和加载的“显存(VRAM)不足”同样会让纹理模糊,所以要同时看磁盘读取等待和显存占用。杀毒软件的实时扫描在每次打开游戏文件时都会介入,读取会更慢。
出处 8 条

大规模同屏渲染负载 Render/animation cost of crowds

ID cg-crowd · 主责 研发团队·客户端开发

攻城战、世界 BOSS 这类数百人同屏的场景,光是绘制开销就承受不住。

起因 同一画面里数百人和特效叠在一起 → 结果 动画、阴影、头顶名字、特效的开销随人数成比例增加 → 画面表现 FPS 从 60 掉到 15,所有动作一卡一卡,操作也有延迟

症状
一卡一卡, 操作延迟
因素
停顿
谁会遇到
特定地点/分线, 只有我
何时出现
人多的时候
负责方
主责 研发团队·客户端开发
研发团队要做的事
按距离简化(LOD),限制显示人数上限,提供特效简化选项,降低动画更新频率。
数值参考
即使每个角色只要 0.02~0.1 ms,300 人就是 6~30 ms。60 FPS 的帧预算是 16.7 ms,光这一项就占去 1/3 以上,多时直接超出预算。
监控图上
随人数/负载上升 · 帧耗时、画面内角色数
查看位置
对比攻城战、世界 BOSS 前后 PresentMon 的帧耗时和 CPUBusy、GPUBusy。开发版本中看 Unreal stat Unit(游戏线程、渲染线程、GPU 耗时)
确认依据
画面内人数越多帧耗时越高,开启显示人数限制或特效简化选项后立刻好转
排除依据
与人数无关地冲高,看“帧耗时尖峰”或“客户端垃圾回收”。FPS 正常、只有别人的动作滞后,看“主线程数据包处理瓶颈”
确认手段
需在玩家侧环境确认
出处 4 条

主线程数据包处理瓶颈 Network processing on the main thread

ID cg-net-mainthread · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

收到的数据包每帧只处理固定数量时,一下子涌来的数据包会不断顺延到下一帧。

起因 人多的地方每秒涌入数千条更新 → 结果 主线程受每帧处理量上限限制,读不完 → 画面表现 其他人的动作体现得越来越晚,而且是集中补上

症状
快进, 操作延迟
因素
停顿, 延迟
谁会遇到
特定地点/分线, 只有我
何时出现
人多的时候
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:接收与解析放到单独的线程,同一对象的旧位置更新合并后只应用最新的。服务器:人多的地方降低远处角色的更新频率,减少发送量。
数值参考
处理不完的数据包不断堆积,几秒钟就足以积压 1 秒的量。
监控图上
随人数/负载上升 · 未处理的接收数据包数、从接收到应用的延迟
查看位置
让客户端把每帧没处理完、留下来的数据包数,以及数据包从到达到应用进游戏的延迟写进日志,和周围人数一起看
确认依据
人多的地方剩余数据包数和应用延迟持续增加,同一时刻 ping 和服务器的发送间隔正常
排除依据
没有应用延迟、数据包本身到得晚,是网络段的问题。帧耗时大幅上升,看“大规模同屏渲染负载”
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

没有插值缓冲或缓冲过短 Missing/short interpolation buffer

ID cg-no-buffer · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

收到服务器数据包就立刻绘制,抖动(到达间隔的波动)会原样暴露在画面上。

起因 收到的位置直接绘制,或缓冲比抖动短 → 结果 数据包晚到多久就停多久,扎堆到达就跳一下 → 画面表现 其他角色一顿一顿地移动

症状
一卡一卡
因素
抖动
谁会遇到
只有我
何时出现
一直
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:设置插值缓冲,并根据线路状况自动调节缓冲长度。服务器:采用延迟补偿(回溯判定),让玩家对着缓冲时长之前的画面射击也能正确判定。
数值参考
通常把服务器发包间隔的 2 倍左右(每秒收 20 次就是 100 ms)作为缓冲。
监控图上
一直偏高 · 数据包到达间隔、插值缓冲被取空的次数
查看位置
在客户端记录服务器数据包到达间隔的分布,以及因为没有下一张可插值的快照而停住或转为外推的帧数
确认依据
到达间隔的波动经常超过插值缓冲长度,每次缓冲被取空,其他角色就顿一下。加大缓冲后减少
排除依据
缓冲足够仍然顿一下,检查服务器的发送间隔本身是否不规律(tick 延迟)
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
缓冲加大后画面更平滑,但看到的对手也相应是过去的样子。所以攻击判定要配合延迟补偿:服务器回溯到“那个人当时看到的过去”再确认。
出处 3 条

过度外推(航位推测) Over-extrapolation / dead reckoning

ID cg-extrap · 主责 研发团队·客户端开发

数据包没到的这段时间,按最后的速度继续显示移动,发现猜错后再纠正回去。

起因 数据包接收中断,按最后的方向和速度继续移动 → 结果 实际上对方已经停下或转向 → 画面表现 对方角色往前走了好一段,又突然被挪到真实位置,或者穿墙而过。数据包到达间隔忽长忽短时,会反复冲到前面又被拽回,移动时像在颤动

症状
瞬移, 一卡一卡
因素
丢包, 抖动
谁会遇到
只有我
何时出现
偶尔随机
负责方
主责 研发团队·客户端开发
研发团队要做的事
设定外推时长上限(例如 200~250 ms),猜错时平滑收敛。
数值参考
速度为 6 m/s 时,只要错 300 ms,位置就会偏差 1.8 m。
监控图上
偶发随机尖峰 · 外推时长、位置校正距离
查看位置
记录用外推绘制其他角色的时长,以及新数据包到达后修正位置的距离
确认依据
每次数据包中断,外推时长都没有上限地拉长,之后的校正距离达到几 m
排除依据
外推时长很短仍出现瞬移,说明丢包、延迟本身过大,查线路和路由一侧
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

客户端预测不一致 Prediction mismatch / reconciliation

ID cg-predict · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

自己的客户端先把移动显示出来,服务器却算出了不同的结果,自己的角色就会被拽回去。

起因 客户端在服务器确认之前先移动(预测) → 结果 服务器对碰撞、移动速度、buff 的计算不同,或者没收到指令 → 画面表现 确认到达时,自己的角色被往回拽

症状
拉回
因素
丢包, 延迟
谁会遇到
只有我
何时出现
移动中/切换地图时, 偶尔随机
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:使用与服务器相同的移动代码,输入重复发送,校正要平滑。服务器:使用与客户端相同的移动代码,重复到达的输入按输入序号过滤,只处理一次。
数值参考
被拽回的距离是“偏差时长 × 移动速度”。只丢几条指令就有 1~3 m。
监控图上
偶发随机尖峰 · 服务器校正(预测失败)次数
查看位置
记录服务器下发的位置校正次数和校正距离。Unreal 统计服务器的 ClientAdjustPosition 校正,Unity Netcode for Entities 统计因预测失败而回滚重算的次数
确认依据
校正集中在拉回反馈的时刻,并且在特定 buff、地形、位移技能上校正距离反复偏大
排除依据
校正只在丢包严重时集中出现,是输入数据包丢失(线路)问题。没有校正、只有其他角色看起来被拽回,看“过度外推(航位推测)”
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

固定时间步长追赶失控 Fixed-timestep catch-up / spiral of death

ID cg-fixed-step · 主责 研发团队·客户端开发

停顿一次之后集中补算积压的计算,又因为这些计算再次积压。

起因 游戏模拟按固定间隔运行,中途停顿了一次 → 结果 把积压的步长挤到一帧里集中计算 → 画面表现 长帧接连出现、不断冲高,或者撞到上限后整个世界变慢

症状
一卡一卡, 快进, 慢动作
因素
停顿
谁会遇到
只有我
何时出现
偶尔随机, 人多的时候
负责方
主责 研发团队·客户端开发
研发团队要做的事
限制每帧追赶上限,剩余时间用插值处理。
监控图上
偶发随机尖峰 · 帧耗时、每帧固定步长次数
查看位置
在开发版本的 Profiler 中同时看一帧里固定步长跑了几次(Unity 看 FixedBehaviourUpdate 等 FixedUpdate 阶段标记的个数)和帧耗时
确认依据
一个长帧之后,接连出现多个要跑多次步长的长帧;碰到上限(Unity Maximum Allowed Timestep)后,游戏时间比实际流逝得慢
排除依据
长帧只出现一次就结束,看“帧耗时尖峰”或“客户端垃圾回收”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
Unity 的物理计算(FixedUpdate)是典型的固定步长(默认 0.02 秒,每秒 50 次)。Time 设置中的 Maximum Allowed Timestep(一帧内最多追赶的时间,默认约 0.33 秒)就是追赶上限。一帧比这更长时,超出的时间会被丢弃,游戏时钟也相应比实际时间慢。
出处 3 条

时钟同步误差 Clock sync error

ID cg-clock · 主责 研发团队·客户端开发

客户端估算的服务器时间不准时,插值时间点和冷却判定都会错位。

起因 连接时只对一次服务器时间,ping 变化后也不再调整 → 结果 插值时间点、冷却结束时刻与服务器错位 → 画面表现 对方偶尔顿一下,冷却已结束技能却被拒绝

症状
一卡一卡, 吞操作/回档
因素
延迟
谁会遇到
只有我
何时出现
开得越久越严重, 偶尔随机
负责方
主责 研发团队·客户端开发
研发团队要做的事
定期做时间同步(测出往返时间后校正),逐步对齐,避免突然跳变;计算经过的时间要用单调时钟(monotonic clock),不用 PC 的系统时间。
监控图上
缓慢爬升 · 估算的服务器时间误差
查看位置
定期记录客户端估算的服务器时间与服务器在数据包中下发的服务器时间(tick 序号)之差
确认依据
误差随连接时长增加而变大,或在 PC 时钟被校准的瞬间一下子跳变,同期技能被拒、顿一下的反馈增多
排除依据
误差一直很小技能仍被拒绝,查服务器判定、延迟一侧
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
用 PC 的日期时间(墙上时钟,wall clock)计算经过的时间时,一旦 Windows 按网络时间校准时钟,或用户修改时钟,游戏时间就会跳变。经过的时间必须用不会倒退的单调时钟(monotonic clock,如 Stopwatch)计量。
出处 3 条

float 时间精度丢失 Float time precision loss on long sessions

ID cg-float-time · 主责 研发团队·客户端开发

用精度较低的小数类型(float)保存游戏时间时,运行越久,时间分辨率(能区分的最小时间差)越低,动作和特效会发生颤动。

起因 把游戏启动后经过的时间累加到 float 里,或直接传给 Shader → 结果 运行越久,float 能表示的最小差值越大 → 画面表现 只有开了几天的客户端,角色、动画、流动特效不停颤动,重启后就正常

症状
一卡一卡
因素
抖动
谁会遇到
只有我
何时出现
开得越久越严重
负责方
主责 研发团队·客户端开发
研发团队要做的事
经过时间用 double(64 位)或整数保存,传给 Shader 的时间定期回绕,做持续好几天的长时间自动化测试。
数值参考
32 位 float 的有效数字只有 7 位多一点,运行一天(约 86,400 秒)后时间分辨率约为 8 ms,大约是 60 FPS 下一帧(16.7 ms)的一半;一周后约为 60 ms,超过一帧。
监控图上
缓慢爬升 · 颤动反馈(按客户端运行时长)
查看位置
收到颤动反馈时一并询问客户端已运行多久,对比重启前后。开发侧可把游戏启动时间设为几天前的值再跑测试
确认依据
只有开了几天的客户端出现颤动,重启后消失,运行越久越严重
排除依据
一启动就颤动,看“没有插值缓冲或缓冲过短”或“定时器精度”
确认手段
需在玩家侧环境确认
深入了解
在开着自动打怪、好几天都不关的手游 MMO 中尤其常见。Unity 的 Time.time 也是 float,所以 Unity 另外提供了 double 类型的 Time.timeAsDouble,并推荐使用它。
出处 2 条

垂直同步(V-Sync)与渲染队列 V-Sync, render queue

ID cg-vsync · 主责 研发团队·客户端开发 · 配合 外部·外部

GPU 画好的帧先在队列里攒几张,再按显示器刷新周期送出,这段时间里输入响应就会变迟。

起因 显卡驱动预先在队列里攒 1~3 帧 → 结果 输入体现到画面上要多花这么久 → 画面表现 ping 不高,操作却发沉、迟钝

症状
操作延迟, 一卡一卡
因素
延迟
谁会遇到
只有我
何时出现
一直
负责方
主责 研发团队·客户端开发 · 配合 外部·外部
研发团队要做的事
支持低延迟模式,缩短帧队列,提供略低于刷新率的帧率上限选项,手机上开启帧节奏控制(frame pacing)功能。
外部要做的事
引导玩家配合可变刷新率显示器设置略低于刷新率的帧率上限,并开启显卡驱动的低延迟模式。
数值参考
60 Hz 下每帧 16.7 ms。CPU 比 GPU 或屏幕刷新周期快、三帧队列(DirectX 11 默认值)被塞满时,会多出 50 ms。双缓冲垂直同步下,耗时 17 ms 的帧要等到下一次屏幕刷新时刻(33.3 ms),这期间上一帧会多显示一次。
监控图上
一直偏高 · 输入到画面的延迟
查看位置
切换垂直同步、低延迟模式、帧率上限,对比 PresentMon 的 MsClickToPhotonLatency、MsAllInputToPhotonLatency(从鼠标、键盘输入到送往屏幕)和 DisplayLatency。MsPCLatency(PC 收到输入后到送往屏幕)只在游戏发出 PC Latency 事件时才会记录
确认依据
开启垂直同步或不设帧率上限时,这段延迟增加一两帧(几十 ms),在低延迟模式或略低于刷新率的帧率上限下减少。ping 不变
排除依据
PC 内部延迟很低、操作仍然迟,看“显示器/输入设备/帧生成延迟”;ping 高则是网络侧
确认手段
需在玩家侧环境确认
深入了解
垂直同步(V-Sync)是只在显示器刷新画面的那一刻才送出新帧的设置。画面撕裂会消失,但等待这一刻会让输入响应变迟;FPS 低于 60 时,还会在 60 和 30 之间来回切换,出现一卡一卡。可变刷新率显示器会配合帧准备好的时间刷新画面,减少这段等待。手机上也会出现同样的情况。30 FPS 的游戏如果不能把帧均匀地对齐到 60 Hz 屏幕上送出,平均虽是 30 FPS,每帧停留的时间却像 49、16、33 ms 这样忽长忽短,出现一卡一卡(Android 开发文档中的例子)。可以用 Android 的帧节奏控制(frame pacing,让帧输出间隔均匀)库或引擎的同类选项来缓解。
出处 5 条

客户端内存泄漏 Client memory leak

ID cg-leak · 主责 研发团队·客户端开发

运行越久内存占用越大,游戏越来越慢,最终被强制关闭。

起因 切换区域时纹理、UI、特效没有释放 → 结果 GC 变频繁,系统内存不足,发生 swap → 画面表现 玩几个小时后越来越一卡一卡,最后被强制关闭(玩家看来像掉线)

症状
一卡一卡, 掉线
因素
停顿
谁会遇到
只有我
何时出现
开得越久越严重
负责方
主责 研发团队·客户端开发
研发团队要做的事
切换区域时测量内存占用,找出并修复未释放的纹理、UI、特效,做长时间自动化测试(长稳测试,soak test)。
监控图上
缓慢爬升 · 游戏进程内存
查看位置
用性能监视器把 Process(游戏)\Private Bytes 记录几个小时。移动端看 Android ApplicationExitInfo 的退出原因(REASON_LOW_MEMORY)和 iOS jetsam 报告
确认依据
每次往返切换区域,内存都上涨且不回落;运行越久,一卡一卡和被强制关闭的情况越多
排除依据
内存稳定、只是运行越久越颤动,看“float 时间精度丢失”
确认手段
需在玩家侧环境确认
深入了解
手机主要靠压缩内存硬撑。内存还是不够时,系统会直接把游戏杀掉(闪退)。内存越小的设备越先被杀。
出处 4 条

客户端崩溃 Client crash

ID cg-crash · 主责 研发团队·客户端开发 · 配合 外部·外部

未处理的错误导致游戏关闭。玩家看来像掉线,服务器其实正常。

起因 空引用、内存不足、显卡驱动错误 → 结果 游戏进程被强制结束 → 画面表现 “闪退了”的反馈。同一时刻其他人正常

症状
掉线
因素
停顿
谁会遇到
只有我
何时出现
做特定操作时, 偶尔随机
负责方
主责 研发团队·客户端开发 · 配合 外部·外部
研发团队要做的事
收集崩溃报告,按设备、驱动统计,先修集中度最高的错误。
外部要做的事
集中在特定显卡驱动版本时,引导玩家更新驱动。
监控图上
仅部分偏高 · 崩溃次数(按设备、显卡驱动、构建版本)
查看位置
按设备、驱动、构建版本查看崩溃报告和 Android vitals 的崩溃率。玩家 PC 看事件查看器“应用程序”日志中的事件 ID 1000(出错模块名)和“Display driver stopped responding and has recovered”记录
确认依据
掉线反馈的时刻有崩溃记录,同一时刻同一服务器的其他玩家正常。集中在特定设备、驱动版本、模块
排除依据
没有崩溃记录、只是连接断了,看“NAT 映射过期”或线路问题
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

游戏安全模块(反作弊)扫描 Anti-cheat scan and heartbeat

ID cg-anticheat · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

为防外挂,与游戏一起运行的安全模块会定期扫描。扫描太重,或与安全服务器之间的心跳(定期发送的存活确认信号)延迟,就会一卡一卡或掉线。

起因 安全模块定期扫描游戏内存、正在运行的程序和驱动 → 结果 扫描期间游戏线程停住,或者心跳没能按时发出 → 画面表现 按固定间隔顿一下,严重时弹出安全错误提示并掉线

症状
一卡一卡, 卡住, 掉线
因素
停顿
谁会遇到
只有我
何时出现
固定周期, 刚登录/维护结束后, 偶尔随机
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:重量级扫描放到游戏线程之外,拆成小块分批执行;按安全模块版本对比一卡一卡、掉线的统计(更新后集中出现就转交安全模块厂商)。服务器:允许心跳偶尔晚到一两次。
数值参考
轻量扫描通常不到 1 ms,但在游戏线程里跑的重量级扫描,视实现不同,一次可能占用几十到几百 ms。
监控图上
周期性尖峰 · 帧耗时、反作弊踢出次数
查看位置
在 PresentMon 帧耗时中测量冲高的间隔,并按安全模块版本、配置统计服务器收到的反作弊踢出原因(EOS 为 ClientActionReason 的 AuthenticationFailed / Authentication Timed Out 等)
确认依据
与游戏内情况无关,按固定间隔反复出现短暂停顿;安全模块更新后,特定配置上一卡一卡、认证超时被踢的情况增多
排除依据
与安全模块版本无关、所有配置上间隔都一样,看“客户端垃圾回收”或“后台进程占用 CPU”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
安全模块以驱动形式深入操作系统底层,有时会与杀毒软件、游戏内覆盖层、其他游戏的安全模块冲突。安全模块更新后,如果只有特定配置的玩家集中反馈一卡一卡、掉线,应首先怀疑这个原因。
出处 2 条

L2 客户端操作系统与设备

15 个原因 · 完整版章节

后台进程占用 CPU Background CPU contention

ID co-background · 主责 外部·外部 · 配合 研发团队·客户端开发

杀毒扫描、Windows 更新、直播软件、浏览器视频占着 CPU 核心时,游戏线程分不到 CPU,只能等待。

起因 其他程序长时间占用 CPU 核心 → 结果 游戏线程等待调度 → 画面表现 帧变慢,收到的数据包也处理得晚

症状
一卡一卡, 快进
因素
停顿
谁会遇到
只有我
何时出现
偶尔随机, 固定周期
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
调整游戏线程优先级,在出现卡顿时的日志中一并记录整机 CPU 使用率,以区分是不是其他程序导致的。
外部要做的事
引导玩家开启 Windows 游戏模式,游戏时关闭不必要的程序(杀毒扫描、Windows 更新、直播软件、浏览器视频)。
数值参考
Windows 通常每次把核心交给一个线程几 ms 到几十 ms。只要在调度中被挤掉一次,就会少一帧。
监控图上
偶发随机尖峰 · 整机 CPU 使用率、帧耗时
查看位置
把任务管理器“进程”选项卡的 CPU 列、性能监视器的 Processor Information(_Total)\% Processor Time 与 PresentMon 帧耗时一起记录。怀疑杀毒软件时,用 New-MpPerformanceRecording 录制,再用 Get-MpPerformanceReport 查看扫描耗时长的文件和进程
确认依据
卡顿的时刻其他程序(杀毒扫描、更新、直播软件)的 CPU 占用飙升,或游戏目录下的文件排在扫描耗时前列。关掉该程序或加入排除项后消失
排除依据
CPU 使用率不高,整个画面却顿一下、声音滋滋作响,看“网卡省电/驱动问题”(DPC 延迟)
确认手段
需在玩家侧环境确认
深入了解
Windows 会给前台窗口的程序稍高一点的优先级,但要做的事比核心多时,游戏也得等。比起占用 CPU,杀毒软件更常以“实时监控”的方式介入。游戏每次打开文件都会被扫描,读取资源那一刻的停顿就会变长。
出处 6 条

省电模式/发热降频 Power saving, thermal throttling

ID co-power · 主责 外部·外部 · 配合 研发团队·客户端开发

笔记本电池模式、手机省电模式、设备发热都会让 CPU、GPU 降速。发热的特点是一开始正常,过一阵子才变慢。

起因 处于电池模式或省电模式,或者设备发烫 → 结果 视设备不同,CPU、GPU 频率降低 30~50% → 画面表现 出现 FPS 下降、一卡一卡:省电模式下一开游戏就出现,发热则在玩几分钟到 20 分钟左右后出现

症状
一卡一卡, 操作延迟
因素
停顿
谁会遇到
只有我
何时出现
开得越久越严重, 一直
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
自动调节画质选项,用帧率上限控制发热,根据系统给出的发热等级(iOS 的 thermalState、Android 的热状态 API)提前降低选项,在可执行文件中标明双显卡笔记本要使用独立显卡(导出 NvOptimusEnablement、AmdPowerXpressRequestHighPerformance)。
外部要做的事
引导玩家关闭省电模式,笔记本建议插电使用;遇到“笔记本配置不错但 FPS 低”的反馈,确认游戏跑在哪块显卡上,并引导玩家在 Windows 图形设置中把游戏指定为高性能 GPU。
监控图上
缓慢爬升 · FPS、CPU/GPU 频率
查看位置
用 PresentMon 把 CPUFrequency、GPUFrequency、CPUTemperature、GPUTemperature 与帧耗时一起记录 20~30 分钟,并用任务管理器“进程”选项卡的“GPU 引擎”列确认游戏跑在哪块显卡上。移动端把 Android 热状态 API(getThermalHeadroom、热状态)和 iOS thermalState 与 FPS 一起记录
确认依据
温度升高、频率下降的时刻起 FPS 开始下降,或只在电池/省电模式下频率偏低。也可能是游戏跑在集成显卡上
排除依据
频率、温度不变,FPS 却下降,看“后台进程占用 CPU”或“客户端内存泄漏”
确认手段
需在玩家侧环境确认
深入了解
双显卡笔记本为了省电,有时会让游戏跑在较慢的集成显卡上。遇到“笔记本配置不错但 FPS 低”的反馈,先确认游戏跑在哪块显卡上。
出处 5 条

定时器精度 Timer resolution (Windows 15.6ms)

ID co-timer · 主责 研发团队·客户端开发

Windows 默认定时器以 15.6 ms 为单位,“只休眠 1 ms”实际要等到下一个定时器周期,最长会拉长到 15.6 ms。

起因 帧率限制、数据包发送用 Sleep(短暂等待)方式实现 → 结果 操作系统只以 15.6 ms 为单位唤醒线程 → 画面表现 帧间隔和输入发送间隔忽长忽短

症状
一卡一卡
因素
抖动
谁会遇到
只有我
何时出现
一直
负责方
主责 研发团队·客户端开发
研发团队要做的事
使用高精度定时器,用基于事件、垂直同步的节奏控制(pacing)代替等待。
数值参考
以 15.6 ms 为单位无法对齐 16.7 ms 的间隔,帧间隔会在 15.6 ms 和 31.2 ms 之间来回跳。
监控图上
一直偏高 · 帧间隔分布
查看位置
查看 PresentMon 的 MsBetweenPresents(帧间隔)分布,以及 powercfg /energy 报告中的“Platform Timer Resolution”项(修改过定时器精度的进程)
确认依据
帧间隔集中在 15.6 ms、31.2 ms 这类 15.6 ms 的整数倍上,游戏也没有请求更高的定时器精度
排除依据
间隔分布均匀,和定时器关系不大,看“后台进程占用 CPU”或帧负载
确认手段
需在玩家侧环境确认
深入了解
早期的 Windows 中,只要有一个程序把定时器改成 1 ms,就会对所有程序生效。所以曾有“开着浏览器游戏会更流畅”的说法。从 Windows 10 2004 版起只对发出请求的程序生效;Windows 11 对最小化、完全被遮挡且不发声的窗口,可能不采纳其请求。
出处 6 条

移动应用切到后台 App suspended in background

ID co-mobile-bg · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

为了看通知把应用暂时切到后台,几秒后系统就会挂起(suspend)应用,这期间服务器就会断开这个玩家的连接。

起因 因看消息、接电话把游戏切到后台 → 结果 游戏引擎暂停游戏运行,系统随后也会挂起应用和网络 → 画面表现 切回来时已经掉线,需要重连

症状
掉线
因素
停顿, 丢包
谁会遇到
只有我
何时出现
挂机一段时间后, 做特定操作时
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:切回来时不要等已断开的连接,直接用会话令牌自动重连(无需重新登录即可接上),一次性拉取最新状态对齐。服务器:心跳中断时清理连接,但在短暂的宽限期内保留角色会话(不要立即踢出),期间重连就用会话令牌接续。
数值参考
游戏引擎通常在切出的那一刻就暂停。iOS 几秒后会挂起应用,即使申请了额外时间,一般也在几十秒内挂起;Android 14 及以上会在应用离开屏幕约 10 秒后将其冻结(freeze)。
监控图上
连接成批断开 · 掉线次数(心跳超时)、应用挂起记录
查看位置
用会话 ID 把客户端日志中的应用挂起、恢复时刻(Unity 为 OnApplicationPause)与服务器的断线原因、时刻对上。Android 还要一并查看 ApplicationExitInfo 中记录的进程退出原因(REASON_LOW_MEMORY 等)
确认依据
服务器因心跳超时断开连接之前,客户端刚刚进入挂起状态,恢复后立即重连
排除依据
应用在前台时掉线,看“NAT 映射过期”“运营商共享 IP(CGNAT)”“Wi-Fi ↔ 4G/5G 切换”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
手机内存不够时,有时会直接结束切到后台的游戏。这就是从相机或支付、认证应用切回来后,游戏要从头启动的原因。配置越低的设备越常见。
出处 5 条

Wi-Fi ↔ 4G/5G 切换 Network switch changes IP

ID co-netswitch · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维

走出家门时 Wi-Fi 断开、切换到 4G 或 5G,自己的 IP 地址会改变,原有连接随之失效。

起因 Wi-Fi 信号变弱,切换到移动网络 → 结果 自己的 IP 地址改变,用旧地址建立的连接无法再收发数据 → 画面表现 画面短暂卡住后掉线或重连

症状
卡住, 掉线
因素
丢包
谁会遇到
只有我
何时出现
移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维
研发团队要做的事
服务器:用会话令牌在地址变化后仍识别为同一玩家并接续,旧地址的连接立即清理,评估地址变化后仍能保持连接的协议(如 QUIC 的连接迁移)。客户端:检测到网络切换时,不等心跳超时,直接用会话令牌重连。
运维团队要做的事
使用 QUIC 连接迁移时,把负载均衡器设置为按连接 ID 选择服务器,不按地址和端口选择(按地址和端口选择时,地址变化后的数据包会被发到别的服务器)。
监控图上
连接成批断开 · 掉线、重连次数,重连时变化的 IP
查看位置
在服务器连接日志中查找同一会话令牌用不同 IP 重连的记录,并与客户端默认网络变更回调(registerDefaultNetworkCallback)的时刻对照
确认依据
掉线后重连的 IP 从 Wi-Fi(家庭宽带)网段变为移动运营商网段,或者反过来,并且在此之前刚收到网络切换回调
排除依据
IP 没变也掉线,看“基站切换(移动中)”或“手机信号弱/信号盲区”
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

安全软件检查数据包 Antivirus / firewall inspection

ID co-security · 主责 外部·外部 · 配合 研发团队·客户端开发

杀毒软件、防火墙逐个检查数据包会增加延迟,检查过度时还会把游戏误判为攻击并拦截。

起因 安全软件逐个检查收发的数据包 → 结果 每个数据包都多出延迟,检查跟不上时就被丢弃 → 画面表现 ping 不规律地飙升,或连接被拦截

症状
一卡一卡, 连不上/无限加载
因素
抖动, 丢包
谁会遇到
只有我
何时出现
一直, 刚登录/维护结束后
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
维护安全软件兼容性列表,安装时在 Windows 防火墙中为游戏添加例外。
外部要做的事
引导玩家在安全软件中把游戏加入例外;游戏被误判为攻击时,请安全软件厂商修正误报。
数值参考
正常情况下检查数据包所需的时间通常不到 1 ms。问题出在检查模块处理不过来或有 bug 时,以及把游戏通信误判为攻击时。
监控图上
仅部分偏高 · RTT、连接失败(按玩家)
查看位置
暂时关闭安全软件,或把游戏加入例外后对比。Windows 在审核策略中开启 Audit Filtering Platform Connection、Audit Filtering Platform Packet Drop 后,安全日志会记录 5157(连接被阻止)、5152(数据包被阻止);用性能监视器的 WFPv4\Packets Discarded/sec 查看丢弃的数据包数
确认依据
有发往游戏服务器地址的连接、数据包被拦截的记录,或关闭安全软件后跳 ping、连不上的问题消失
排除依据
与安全软件无关、同一家庭的其他设备也一样,是路由器或线路问题
确认手段
需在玩家侧环境确认
出处 6 条

接收缓冲区溢出 Socket receive buffer overflow

ID co-rcvbuf · 主责 研发团队·客户端开发

游戏太忙,从 socket(操作系统提供的网络收发接口)里取数据包取得晚,操作系统的缓冲区就会溢出。

起因 帧处理积压,游戏读 socket 读得晚 → 结果 操作系统接收缓冲区填满后,UDP 直接丢弃,TCP 缩小接收窗口让发送方停止发送 → 画面表现 瞬移(UDP)或快进(TCP)

症状
瞬移, 快进
因素
丢包, 停顿
谁会遇到
只有我
何时出现
人多的时候
负责方
主责 研发团队·客户端开发
研发团队要做的事
专用接收线程,调整缓冲区大小(SO_RCVBUF)。
数值参考
默认接收缓冲区视操作系统和设置为几十到几百 KB。人多的地方,更新量有时可达每秒几百 KB。
监控图上
随人数/负载上升 · UDP 接收缓冲区丢弃数、帧耗时
查看位置
把 Windows 性能监视器的 Microsoft Winsock BSP\Dropped Datagrams(因 socket 接收缓冲区不足而丢弃的 UDP 数)、UDPv4\Datagrams Received Errors 与帧耗时一起记录,游戏侧统计收到的数据包序号中的空缺
确认依据
人多的地方或长帧之后 Dropped Datagrams 增加,同一时刻游戏的序号出现空缺。同一时刻线路侧没有丢包
排除依据
Dropped Datagrams 不变、只有序号缺失,是路径上的丢包
确认手段
需在玩家侧环境确认
出处 5 条

客户端内存不足/swap Paging / swap on client

ID co-swap · 主责 外部·外部 · 配合 研发团队·客户端开发

同时开着几十个浏览器标签页和游戏时,操作系统会把游戏的一部分内存换出到磁盘。

起因 整机内存不足 → 结果 操作系统把暂时不用的游戏内存移到磁盘 → 画面表现 再次用到那部分内存时,视存储设备不同卡住几十到几百 ms

症状
卡住, 一卡一卡
因素
停顿
谁会遇到
只有我
何时出现
移动中/切换地图时, 偶尔随机
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
减少内存占用,剩余内存不足时显示警告。
外部要做的事
向玩家说明最低配置,引导玩家游戏时关闭其他程序(浏览器标签页等)。
监控图上
偶发随机尖峰 · 硬缺页、内存占用
查看位置
把性能监视器的 Memory\Pages Input/sec(为处理硬缺页而从磁盘读取的页数)、任务管理器“性能”选项卡的内存占用和已提交量与帧耗时一起记录
确认依据
卡住的瞬间 Pages Input/sec 飙升,内存几乎占满。关闭浏览器等其他程序后消失
排除依据
内存有余量、Pages Input/sec 平稳,看“主线程同步加载/Shader 编译”或“存储设备过慢导致资源流式加载滞后”
确认手段
需在玩家侧环境确认
出处 3 条

显存(VRAM)不足 VRAM over-commit

ID co-vram · 主责 研发团队·客户端开发 · 配合 外部·外部

画质选项要求的内存超过显卡显存时,操作系统要把纹理换到系统内存再取回,画面就会一卡一卡。

起因 高纹理选项,加上人多处的各种装备、特效,把显存占满 → 结果 操作系统把暂时不用的纹理移到系统内存,需要时再经由较慢的 PCIe 总线取回 → 画面表现 每当出现新场景或新角色就顿一下,纹理一段时间内是模糊的

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
只有我
何时出现
人多的时候, 移动中/切换地图时
负责方
主责 研发团队·客户端开发 · 配合 外部·外部
研发团队要做的事
按显存大小设定选项默认值,超出显存预算时自动降低纹理质量,人多的地方简化角色纹理。
外部要做的事
引导玩家降低纹理选项;双开时把选项再调低一些。
数值参考
显存每秒能读几百 GB,而与系统内存往来的 PCIe 总线视代际不同每秒约 16~64 GB,慢十倍以上。
监控图上
触顶后走平 · 专用 GPU 内存、共享 GPU 内存
查看位置
把任务管理器“性能”选项卡 GPU 项的专用 GPU 内存、共享 GPU 内存曲线(“详细信息”选项卡中也可添加按进程的列)与 PresentMon 帧耗时一起看
确认依据
专用 GPU 内存贴着上限走平、共享 GPU 内存上升期间,频繁顿一下,降低纹理选项后消失
排除依据
专用 GPU 内存有余量,看“存储设备过慢导致资源流式加载滞后”或“主线程同步加载/Shader 编译”
确认手段
需在玩家侧环境确认
深入了解
在 Windows 任务管理器的 GPU 项中,“专用 GPU 内存”占满、“共享 GPU 内存”上升,就是这种状态。同一台电脑双开时会更快占满(看“内存/显存不足导致流式加载失败”)。
出处 4 条

Wi-Fi 后台扫描 Periodic Wi-Fi background scan

ID co-wifi-scan · 主责 外部·外部 · 配合 研发团队·客户端开发

操作系统为了搜索周围的 Wi-Fi,会定期切换信道,这期间通信会短暂停顿。

起因 操作系统、驱动按固定周期搜索周围的 Wi-Fi → 结果 搜索期间收发短暂停顿 → 画面表现 按严格固定的间隔(例如每 60 秒)跳 ping

症状
一卡一卡, 瞬移
因素
抖动
谁会遇到
只有我
何时出现
固定周期
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
游戏中请求减少无线扫描的模式(Android 为低延迟 Wi-Fi 模式 WIFI_MODE_FULL_LOW_LATENCY,Windows 为 WlanSetInterface 的媒体流模式。视设备和驱动不同,有时没有效果)。
外部要做的事
引导玩家改用有线连接,调整定位服务和 Wi-Fi 自动搜索设置,更新无线网卡驱动。
数值参考
通常每次几十到几百 ms。规律得过分时,先怀疑这个原因。
监控图上
周期性尖峰 · 到路由器的 RTT
查看位置
游戏中用 ping /t 对路由器地址(ipconfig 中的默认网关)持续测几分钟,测出冲高的间隔。换成有线再做同样的测量
确认依据
到路由器的 ping 按严格固定的间隔(例如 60 秒)跳到几十到几百 ms,有线时消失
排除依据
冲高间隔不规律,看“Wi-Fi 干扰/信号弱”。到路由器正常、只有路由器以外的路段冲高,是线路或运营商段
确认手段
需在玩家侧环境确认
出处 5 条

网卡省电/驱动问题 NIC power saving, driver bugs

ID co-driver · 主责 外部·外部 · 配合 研发团队·客户端开发

有线网卡、Wi-Fi 芯片在数据包之间进入省电状态时,重新唤醒需要时间。

起因 网络设备的省电功能开着,或驱动太旧 → 结果 唤醒(wake-up)延迟,偶尔设备重启 → 画面表现 不规律的延迟,偶尔卡住几秒

症状
一卡一卡, 卡住
因素
抖动, 丢包
谁会遇到
只有我
何时出现
挂机一段时间后, 偶尔随机
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
Android 客户端在游戏中请求低延迟 Wi-Fi 模式(WIFI_MODE_FULL_LOW_LATENCY),关闭 Wi-Fi 省电。
外部要做的事
引导玩家更新网卡驱动,在设备管理器中关闭网络设备的省电功能;整个画面顿一下、声音滋滋作响时,引导玩家用 LatencyMon 找出导致问题的驱动。
监控图上
偶发随机尖峰 · DPC/ISR 时间、到路由器的 RTT
查看位置
用 Windows 性能记录器(WPR)录制,在 Windows 性能分析器(WPA)的 DPC/ISR 图中找出长时间运行的驱动(Module 列),并在设备管理器中检查网络适配器的电源管理(省电)设置
确认依据
顿一下的时刻,网络驱动的 DPC、ISR 持续几 ms,或关闭省电后不规律的延迟消失
排除依据
DPC 很短、关闭省电后也一样,看“Wi-Fi 干扰/信号弱”或“Wi-Fi 后台扫描”
确认手段
需在玩家侧环境确认
深入了解
驱动处理中断时长时间占用 CPU(Windows 中称为 DPC 延迟),这段时间游戏线程也用不了这个核心。此时 CPU 使用率不高,整个画面却会顿一下,声音也滋滋作响。用 LatencyMon 之类的工具可以找出是哪个驱动,常见原因是 Wi-Fi、有线网卡驱动。
出处 4 条

同一设备上其他应用占用带宽 Other apps saturating the link

ID co-other-apps · 主责 外部·外部 · 配合 研发团队·客户端开发

云同步、大文件下载、游戏更新在同一台 PC 上运行时,游戏数据包要在队列里排队。

起因 其他应用把上传、下载带宽用满 → 结果 PC 和路由器的队列里堆积了游戏数据包 → 画面表现 ping 暴涨、操作延迟、快进

症状
操作延迟, 快进
因素
延迟, 抖动
谁会遇到
只有我, 同一家庭
何时出现
偶尔随机
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
我方的启动器、更新程序在游戏运行期间暂停后台下载或限速。
外部要做的事
引导玩家限制下载速度,游戏时关闭自动更新。
监控图上
随人数/负载上升 · RTT、PC 收发流量
查看位置
把性能监视器的 Network Interface\Bytes Sent/sec、Bytes Received/sec 与 ping 一起记录。方法与开着 ping 故意发起大流量传输的缓冲区膨胀测试相同
确认依据
下载、上传接近线路速率期间 ping 上升几十到几百 ms,停止传输后立刻恢复
排除依据
PC 收发流量很低、ping 却上升,看同一家庭其他设备造成的“缓冲区膨胀(路由器队列)”,或运营商段
确认手段
需在玩家侧环境确认
出处 4 条

窗口最小化/失去焦点时处理受限 Minimized / unfocused window throttling

ID co-unfocused · 主责 研发团队·客户端开发

切到别的窗口或最小化游戏时,游戏和 Windows 为了省电会让游戏降速运行。切回来时,积压的数据包会一下子涌来,或者已经掉线。

起因 用 Alt+Tab 切到别的窗口,或最小化游戏 → 结果 游戏不可见时大幅降低 FPS 或直接暂停,Windows 也会降低不可见程序的优先级 → 画面表现 切回来的瞬间出现快进,切出时间长就会掉线

症状
快进, 一卡一卡, 掉线
因素
停顿
谁会遇到
只有我
何时出现
做特定操作时, 挂机一段时间后
负责方
主责 研发团队·客户端开发
研发团队要做的事
窗口不可见时,数据包接收和心跳仍在独立线程中继续,检查引擎的“后台运行”设置,切回时一次性对齐到最新状态。
数值参考
窗口不可见时把 FPS 降到 5~10,一帧就是 100~200 ms。逐帧处理数据包的游戏,读取数据包也会相应变慢。
监控图上
断流后集中到达 · 帧间隔(切换窗口前后)、已处理的数据包数
查看位置
开着 PresentMon 试着 Alt+Tab、最小化,查看窗口不可见时的帧间隔。在游戏日志中记录窗口焦点变化的时刻,与断线原因对照
确认依据
窗口不可见期间帧间隔拉长到 100 ms 以上或记录中断,切回瞬间一次性处理积压的数据包,出现快进。切出时间长会因心跳超时而掉线
排除依据
窗口一直在前台也一样,看“后台进程占用 CPU”或网络侧
确认手段
需在玩家侧环境确认
深入了解
Windows 11 不保证给最小化、完全被遮挡且不发声的窗口程序提供 1 ms 定时器。用电池运行的笔记本会把这类程序降到最省电的速度;在混合核心架构的 CPU 上,还可能让它跑在较慢的能效核上。同一台电脑上的两个客户端如果只有后台那个异常,也请看“后台窗口处理受限”。
出处 4 条

游戏内覆盖层干扰 Overlays and screen hooks

ID co-overlay · 主责 外部·外部 · 配合 研发团队·客户端开发

聊天软件、启动器、录屏软件、FPS 显示工具为了在游戏画面上叠加绘制自己的 UI,会介入游戏的渲染过程(hook)。每帧的工作量随之增加,偶尔还会与游戏冲突,导致顿一下或游戏被强制关闭。

起因 聊天软件、游戏启动器、显卡工具、录屏软件的覆盖层处于开启状态 → 结果 每次把帧送往屏幕时,覆盖层都会介入并叠加绘制自己的 UI → 画面表现 帧略微变慢,弹出通知的瞬间顿一下,或出现画面异常、被强制关闭(玩家看来像掉线)

症状
一卡一卡, 卡住, 掉线
因素
停顿
谁会遇到
只有我
何时出现
一直, 偶尔随机
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
在崩溃报告和卡顿日志中一并收集正在运行的覆盖层列表。
外部要做的事
收到反馈时,引导玩家关闭所有覆盖层后再试。
监控图上
仅部分偏高 · 帧耗时、崩溃次数(开启覆盖层的玩家)
查看位置
关闭所有覆盖层,对比同一场景的 PresentMon 帧耗时;有崩溃时,查看事件查看器事件 ID 1000 中的出错模块名称(Faulting module name)
确认依据
关闭覆盖层后顿一下、画面异常消失,或崩溃的出错模块是覆盖层程序的 DLL
排除依据
关闭所有覆盖层后仍一样,看显卡驱动或“客户端崩溃”
确认手段
需在玩家侧环境确认
深入了解
只有特定玩家一卡一卡或游戏闪退,又无法用配置解释时,先怀疑覆盖层与游戏安全模块的冲突。
出处 3 条

显示器/输入设备/帧生成延迟 Display, input device and frame generation latency

ID co-display-input · 主责 外部·外部 · 配合 研发团队·客户端开发

ping 正常、操作却发沉,可能是电视的画面处理、无线手柄或帧生成功能在输入和画面之间增加了延迟。

起因 电视游戏模式没开,或使用蓝牙/无线手柄,或开启了帧生成(DLSS、FSR 帧生成) → 结果 电视做画质处理时会延后送出帧,无线输入会因传输周期和干扰而晚到,帧生成要等下一帧到来才能生成中间帧 → 画面表现 ping 和 FPS 数字都不错,按下后要过一会儿画面才有反应,形成操作延迟

症状
操作延迟
因素
延迟
谁会遇到
只有我
何时出现
一直
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
把帧生成做成可选项,并提示开启后操作延迟可能增加;使用帧生成时同时接入显卡厂商的低延迟功能(NVIDIA Reflex、AMD Anti-Lag 2);在游戏内显示 PC 侧输入到画面的延迟;Android TV 和机顶盒版本用 Window.setPreferMinimalPostProcessing(true) 请求电视进入低延迟模式(ALLM)。
外部要做的事
引导玩家开启电视、显示器的游戏模式(ALLM),竞技内容中使用有线手柄并关闭帧生成,蓝牙设备放近一些,Wi-Fi 使用 5 GHz。
数值参考
60 Hz 屏幕光是送出一帧就要 16.7 ms,120 Hz 要 8.3 ms。早期 Xbox 手柄每 8 ms 读取并发送一次输入。电视画面处理增加的延迟因机型而异,很难用一个数字概括,游戏模式就是减少这类处理的设置。AMD 建议在生成前帧率达到 60 FPS 以上时再使用帧生成。
监控图上
一直偏高 · 输入到画面的延迟
查看位置
开关帧生成,对比 PresentMon 的 MsAllInputToPhotonLatency(从键盘、鼠标输入到送往屏幕),并用 FrameType(仅在驱动、SDK 提供时记录)查看是否混入了生成的中间帧。这个值不包括手柄的无线段和电视内部处理,这部分要切换电视游戏模式、改用有线手柄来对比
确认依据
ping 正常,关闭帧生成后输入到画面的延迟减少,或切换到电视游戏模式、有线手柄后体感延迟消失
排除依据
这些设置全改了还一样,且 ping 高或跳动,是网络侧。PC 侧延迟因垂直同步、帧队列偏高,看“垂直同步(V-Sync)与渲染队列”
确认手段
需在玩家侧环境确认
深入了解
网络延迟能从 ping 上看出来,这类延迟却不会反映在 ping 上。所以遇到“ping 很低却卡”的反馈,要先检查这里。帧生成能让画面上的 FPS 数字提高到两倍左右,但生成中间帧要等下一个真实帧,输入体现到画面上的时间反而变长(AMD 明确表示其设计本身会增加延迟)。蓝牙设备和 Wi-Fi 使用同一个 2.4 GHz 频段,受到干扰时输入可能中断或跳变。增大 PC 内部延迟的垂直同步、渲染队列,请看“垂直同步(V-Sync)与渲染队列”一项。
出处 9 条

L3 家庭网络

10 个原因 · 完整版章节

Wi-Fi 干扰/信号弱 Wi-Fi interference, weak signal

ID hn-wifi · 主责 外部·外部 · 配合 研发团队·客户端开发

信号弱或有干扰时,无线段要重发好几次,数据包到达就会忽快忽慢。

起因 墙体、距离、微波炉、蓝牙、邻居家的路由器导致信号质量下降 → 结果 无线段发送失败 → 重传好几次 → 画面表现 数据包到达忽快忽慢(抖动),角色一顿一顿,严重时丢包导致瞬移

症状
一卡一卡, 瞬移, 拉回
因素
抖动, 丢包
谁会遇到
只有我, 同一家庭
何时出现
偶尔随机, 一直
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
根据线路状况自动调节插值缓冲长度,抖动、丢包大时在画面上显示网络状态。
外部要做的事
引导玩家改用有线连接,使用 5 GHz、6 GHz,挪动路由器位置。
数值参考
每次重传大约多花 1~4 ms。信号弱时会以低速率反复重发,还要等信道空出来,有时一下跳到 50~200 ms。容易被忽视的是,平均 ping 看起来一切正常。
监控图上
偶发随机尖峰 · 到路由器的 RTT
查看位置
用 ping /t 对路由器地址(ipconfig 中的默认网关)测几分钟,用 netsh wlan show networks mode=bssid 查看自己路由器的信号强度和信道。在同一位置换成有线对比
确认依据
到路由器这一段的 ping 就已不规律地跳到几十到几百 ms,偶尔丢包,信号强度低。换有线或靠近路由器后消失
排除依据
到路由器平稳、只有路由器以外的路段冲高,是线路或运营商段。只按固定间隔冲高,看“Wi-Fi 后台扫描”
确认手段
需在玩家侧环境确认
深入了解
Mesh 组网的 Wi-Fi 中,如果路由器(节点)之间是无线互联(无线回程),负责中继的节点在接收时不能发送,还要和使用同一信道的前后两段分摊发送机会,繁忙时吞吐量会下降,延迟会增加。带有回程专用无线频段的产品影响会小一些;节点之间用有线(以太网)互联,这一段就不再走无线。电力猫(PLC)也和 Wi-Fi 一样,采用先确认介质空闲再发送的方式(CSMA/CA),质量会随家电产生的噪声和家电的开关不断变化,可能导致重传和抖动。
真实案例
Square Enix 2021: FINAL FANTASY XIV 资料片上线时的拥挤与登录排队错误
出处 8 条

Wi-Fi 信道拥挤 Crowded Wi-Fi channel

ID hn-channel · 主责 外部·外部 · 配合 研发团队·客户端开发

公寓楼这种有几十台路由器的地方,大家共用同一信道,要排队等发送机会。

起因 几十台路由器使用同一个 2.4 GHz 信道 → 结果 要发送就得等其他设备发完、信道空出来 → 画面表现 大家下班回家的晚上,抖动(到达间隔的波动)增大,出现一卡一卡

症状
一卡一卡, 操作延迟
因素
抖动, 延迟
谁会遇到
同一家庭
何时出现
晚高峰
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
抖动增大时自动加长插值缓冲。
外部要做的事
引导玩家使用 5 GHz、6 GHz,选择不拥挤的信道,或改用有线连接。
监控图上
特定时段偏高 · 到路由器的 RTT、抖动
查看位置
用 netsh wlan show networks mode=bssid 查看周围 Wi-Fi 的信道和信号强度,晚上和白天分别测到路由器的 ping 并对比
确认依据
在 2.4 GHz 同一信道上能看到很多周围的路由器,到路由器的抖动只在晚上变大。换到 5 GHz、6 GHz 或不拥挤的信道后减少
排除依据
不分时段都冲高,看“Wi-Fi 干扰/信号弱”。到路由器正常、晚上只有路由器以外的路段变差,看“高峰时段对等互联链路拥塞”
确认手段
需在玩家侧环境确认
出处 4 条

缓冲区膨胀(路由器队列) Bufferbloat

ID hn-bufferbloat · 主责 外部·外部 · 配合 研发团队·客户端开发

家里有人上传视频或下载大文件时,路由器队列里会堆积几百 ms 的数据包,游戏数据包也得排在后面等。

起因 家人上传视频、云备份,自己直播推流,大文件下载,把线路占满 → 结果 路由器或光猫把装不下的数据包堆进大队列 → 画面表现 游戏数据包也在队列后面等待,ping 暴涨到几百 ms

症状
操作延迟, 快进, 瞬移
因素
延迟, 抖动
谁会遇到
同一家庭, 只有我
何时出现
偶尔随机, 晚高峰
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
ping 突然升到几百 ms 时在画面上显示网络状态(提示同一线路上可能有大流量传输)。
外部要做的事
引导玩家使用支持 SQM(fq_codel、CAKE)或 QoS 的路由器,SQM 速率设为线路速率的 90~95%(这样队列才会出现在路由器内部,才有效果),限制上传速度。
数值参考
上行 10 Mbps 的线路配 1 MB 缓冲区,队列可长达 800 ms。
监控图上
随人数/负载上升 · RTT、线路上行/下行用量
查看位置
开着 ping 用测速把线路占满,或使用测量负载下延迟的网页测试(见 Bufferbloat.net 的说明)。与路由器界面上的上行、下行用量一起看
确认依据
上行或下行占满线路期间 ping 升到几百 ms,传输结束后恢复(负载下延迟超过 50 ms 就值得怀疑)。开启 SQM 后消失
排除依据
线路空闲时 ping 仍冲高,看“Wi-Fi 干扰/信号弱”或“线路质量差”
确认手段
需在玩家侧环境确认
深入了解
上行方向尤其容易堵,因为有线电视宽带(Cable)、移动网络的上行往往比下行窄得多。光纤带宽充足的家庭,Wi-Fi 段会成为瓶颈,同样的情况会发生在路由器的无线队列里。游戏数据包很小,几乎不占带宽,但同样要在队列里等。只有上行堵塞时,变慢的只是自己的输入,别人的动作正常。手机上,同一部手机的照片备份、应用更新会塞满手机基带和基站的队列,引起同样的问题。
出处 4 条

NAT 映射过期 NAT mapping timeout

ID hn-nat · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

路由器会把一段时间没有数据包往来的空闲连接从 NAT 表中删除。这是挂机一段时间后一动就掉线的常见原因。

起因 路由器把“内网设备 ↔ 外部服务器”的连接记录在 NAT 表(地址转换表)中 → 结果 一段时间没有数据包就从表中删除(UDP 通常为 30~120 秒) → 画面表现 服务器的数据包进不了家里,掉线

症状
掉线
因素
丢包
谁会遇到
只有我, 同一家庭
何时出现
挂机一段时间后
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:以不超过最短空闲超时一半的间隔发送心跳(UDP 映射只有从家里往外发的数据包才能可靠刷新,所以由客户端发),断开后自动重连。服务器:响应心跳,一段时间收不到就主动清理连接;映射被删、外部地址和端口变化时,用会话令牌(连接时拿到的确认码)确认是同一玩家并接续。
监控图上
连接成批断开 · 掉线次数(心跳超时)、断开前的空闲时长
查看位置
汇总服务器的断线原因,以及断开前该连接最后一个数据包往来后经过的时间(空闲时长),看其分布。测试时把 UDP 发包间隔逐步拉长到 30 秒、60 秒、120 秒,测出响应中断时的间隔
确认依据
只有挂机中的连接会断,空闲时长集中在 30~120 秒这类特定值之后。把心跳间隔缩短到比它更短后消失
排除依据
正在操作时也掉线,是线路或路由问题。只在特定移动运营商下集中在较短的值上,看“运营商共享 IP(CGNAT)”
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

路由器性能不足/过热 Router CPU / session table exhaustion

ID hn-router · 主责 外部·外部

便宜的路由器上挂了几十台设备、几千条连接,路由器本身就处理不过来。

起因 几十台设备,加上 P2P、BT 下载开了几千条连接 → 结果 路由器 CPU 和会话表饱和 → 画面表现 数据包处理延迟、丢包,新连接建立失败

症状
一卡一卡, 连不上/无限加载, 掉线
因素
丢包, 抖动
谁会遇到
同一家庭
何时出现
开得越久越严重, 偶尔随机
负责方
主责 外部·外部
外部要做的事
引导玩家重启路由器(临时措施)、更换路由器,关掉会开大量连接的程序(P2P、BT 下载)。
监控图上
触顶后走平 · 路由器 CPU/连接数、到路由器的 RTT
查看位置
在路由器管理界面查看 CPU 使用率、连接(会话)数、接入设备数(如果路由器支持),并对比重启前后到路由器本身的 ping
确认依据
连接数多时,到路由器这一段的 ping 就已冲高或丢包,新连接建立失败。重启后能好一阵子,之后又变差
排除依据
到路由器正常、只有路由器以外的路段差,是线路或运营商段
确认手段
需在玩家侧环境确认
出处 2 条

基站切换(移动中) Cellular handover

ID hn-handover · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发

坐公交、地铁移动时,切换基站期间通信会中断。

起因 移动中接入的基站发生变化 → 结果 通常只中断几十 ms,信号差导致切换失败时,可能中断几百 ms 到几秒 → 画面表现 画面卡住后瞬移,中断久了就掉线

症状
卡住, 瞬移, 掉线
因素
丢包
谁会遇到
只有我
何时出现
移动中/切换地图时
负责方
主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发
研发团队要做的事
客户端:超时要能容忍短暂中断,并快速重连。服务器:超时设得中断几秒也不立即踢下线,重连后接回原会话。
外部要做的事
向玩家说明:移动中(公交、地铁)掉线是基站切换造成的。
监控图上
断流后集中到达 · 收到的数据包数、RTT
查看位置
确认掉线反馈是否发生在移动中(公交、地铁),查看客户端日志中接收中断的时刻,以及网络类型和信号的变化
确认依据
只在移动中出现几百 ms 到几秒收不到数据、随后集中到达,静止时无法复现
排除依据
静止时也一样,看“手机信号弱/信号盲区”或“5G↔4G 频繁切换(5G 覆盖边缘)”
确认手段
需在玩家侧环境确认
出处 2 条

RRC 状态切换延迟(移动网络无线省电) Radio state promotion (RRC)

ID hn-rrc · 主责 研发团队·客户端开发

手机一段时间没有通信,就会把无线连接降到低功耗状态,下次收发数据包时要重新激活,所以会变慢。

起因 短时间没有通信,手机就把无线连接切到省电状态 → 结果 要发下一个数据包,就得重新激活连接 → 画面表现 挂机后的第一个操作特别慢

症状
操作延迟
因素
延迟
谁会遇到
只有我
何时出现
挂机一段时间后
负责方
主责 研发团队·客户端开发
研发团队要做的事
用轻量的周期性发送保持激活状态(代价是更耗电)。
数值参考
4G 通常在 10 秒左右没有通信后进入省电状态,重新激活要几十到几百 ms(实测示例:约 0.3~0.6 秒)。3G 要 1 秒以上。
监控图上
仅部分偏高 · 空闲后首次请求的 RTT(移动网络)
查看位置
把游戏内 RTT 按距上一次通信的间隔分组查看。在移动网络下,对比空闲 10 秒以上后发出的第一个数据包与连续发出的数据包的 RTT
确认依据
移动网络下只有空闲后的第一个数据包慢几百 ms,紧接着发出的数据包正常。Wi-Fi 下没有差别
排除依据
连续发送也慢,是信号、线路或路由问题
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

手机信号弱/信号盲区 Weak cellular signal

ID hn-weak-cell · 主责 外部·外部 · 配合 研发团队·客户端开发

在电梯、地下室、建筑物深处,重传增多、速度下降,最终掉线。

起因 移动到信号弱的地方 → 结果 无线重传增加,速度下降,短暂中断 → 画面表现 抖动、丢包导致一卡一卡、瞬移,最终掉线

症状
一卡一卡, 瞬移, 掉线
因素
抖动, 丢包
谁会遇到
只有我
何时出现
移动中/切换地图时
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
完善重连流程,显示网络质量。
外部要做的事
向玩家说明这是在信号弱的地方(电梯、地下室、建筑物深处)才会出现的问题。
监控图上
仅部分偏高 · RTT、丢包(按移动网络玩家统计)
查看位置
确认掉线反馈时所在的地点(电梯、地下室、建筑物内)和手机的信号格,在信号好的地方重复同样的操作对比
确认依据
只在信号弱的地方 RTT、丢包增加并断开,移到信号好的地方就消失
排除依据
信号好也一样,是运营商段或服务器侧
确认手段
需在玩家侧环境确认
出处 1 条

5G↔4G 频繁切换(5G 覆盖边缘) 5G NSA / LTE switching

ID hn-5g-flip · 主责 外部·外部 · 配合 研发团队·客户端开发

在 5G 信号弱的建筑物内或 5G 覆盖边缘,手机会频繁在 5G 和 4G 之间来回切换,每次切换都会跳 ping 或短暂断网。

起因 处在 5G 信号时强时弱的地方(建筑物内、5G 覆盖边缘) → 结果 手机在 5G 和 4G 之间不时切换,每次都会出现短暂中断 → 画面表现 原地不动也会无规律地跳 ping,偶尔卡住、瞬移

症状
一卡一卡, 瞬移, 卡住
因素
抖动, 丢包
谁会遇到
只有我
何时出现
偶尔随机, 移动中/切换地图时
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
抖动增大时自动加长插值缓冲;在出现延迟时的日志中一并记录网络类型(5G、4G)的变化,以便区分原因。
外部要做的事
引导玩家在设置中切换为 4G 优先模式对比一下,建议使用 Wi-Fi。
数值参考
每次切换几十到几百 ms。韩国的 5G 大多采用与 4G 绑定使用的非独立组网(NSA)方式,5G 这一路很容易时连时断。
监控图上
偶发随机尖峰 · RTT、网络类型(5G/4G)变化
查看位置
把手机设置改为 4G 优先,在同一位置对比。如果客户端把 Android TelephonyDisplayInfo 的网络显示(OVERRIDE_NETWORK_TYPE_NR_NSA 等)变化与 RTT 一起记录,判断会更准
确认依据
RTT 冲高的时刻与 5G↔4G 显示切换的时刻重合,4G 优先模式下冲高消失
排除依据
网络显示没变也冲高,看“手机信号弱/信号盲区”或线路问题
确认手段
需在玩家侧环境确认
出处 3 条

公共 Wi-Fi/公司网络限制 Captive portal, restrictive network

ID hn-captive · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发

咖啡馆 Wi-Fi 的登录页面或公司防火墙拦截了游戏连接。

起因 尚未在登录页面完成认证,或防火墙屏蔽了游戏端口、UDP → 结果 连接请求本身被拦截,或只有一部分能通过 → 画面表现 连不上,能登录却进不了游戏

症状
连不上/无限加载
因素
丢包
谁会遇到
只有我
何时出现
刚登录/维护结束后
负责方
主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发
研发团队要做的事
客户端:被拦截时提示原因(尚未在登录页面认证、UDP 被屏蔽等),UDP 被屏蔽时自动切换到备用路径。服务器:提供 TCP 443 这类备用路径。
外部要做的事
引导玩家在公共 Wi-Fi 上先完成登录页面认证,在公司网络这类受限网络中改用其他网络。
监控图上
仅部分偏高 · 连接失败次数(按网络)
查看位置
让连接失败的玩家改用移动数据等其他网络试试,在服务器连接日志中查看 UDP 首包是否到达,以及走 TCP 443 备用路径能否连上
确认依据
只在特定 Wi-Fi(咖啡馆、公司)上失败,换其他网络立刻能连。尚未在登录页面认证,或只有 UDP 到不了服务器
排除依据
在任何网络上都失败,看账号、服务器或“DNS 故障/延迟”。某个国家或运营商整体失败,看“国家/运营商级 UDP 限制与包检测”
确认手段
需在玩家侧环境确认
出处 2 条

L4 公网链路

14 个原因 · 完整版章节

传播延迟(物理距离) Propagation delay

ID isp-distance · 主责 运维团队·系统运维 · 配合 运维团队·网络运维, 研发团队·服务器开发

光在光纤里每秒也只能走约 20 万 km。服务器离得远,条件再好也会慢。

起因 服务器离得远(海外服务器、其他大洲) → 结果 往返时间随距离增加(每 1,000 km 至少 10 ms) → 画面表现 所有动作都有固定的操作延迟,判定上吃亏

症状
操作延迟
因素
延迟
谁会遇到
特定地区/运营商
何时出现
一直
负责方
主责 运维团队·系统运维 · 配合 运维团队·网络运维, 研发团队·服务器开发
研发团队要做的事
物理定律无法靠代码改变,只能缓解:提供区域选择,让玩家选近的区服;用延迟补偿(回溯)减少判定上的劣势。
运维团队要做的事
服务器/OS:在玩家多的地区就地部署服务器。网络:在靠近玩家的地方设接入节点(边缘),选择绕行少的线路和路由。
数值参考
首尔–东京约 30 ms,首尔–新加坡约 75 ms,首尔–美国西海岸约 140 ms,首尔–欧洲约 230~270 ms(往返,按实际路径)。去欧洲的直线方向上几乎没有大容量光缆,要绕道东南亚、苏伊士或美国,所以比按距离推算的长得多。
监控图上
一直偏高 · RTT(按国家/地区)
查看位置
给接入 IP 标上国家,看各国的 RTT 分布;再从当地云区域的 VM 或 RIPE Atlas 探针(按国家、ASN 挑选)向服务器跑 ping、traceroute
确认依据
远方国家的 RTT 不分时段一直偏高,数值接近按距离算出的最小延迟(每 1,000 km 往返 10 ms)和公开的延迟统计
排除依据
比距离能解释的值高得多,看“路由绕行”;只在晚上升高,看“高峰时段对等互联链路拥塞”
确认手段
运维工具即可确认(无需游戏代码)
真实案例
Riot Games 2015: 绕远路的 League of Legends 流量与 Riot Direct
出处 4 条

卫星互联网(低轨、静止轨道) Satellite internet (LEO, GEO)

ID isp-satellite · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发

卫星互联网的信号要在太空中往返。静止轨道卫星光是往返就超过 0.5 秒;Starlink 这类低轨卫星平时很快,但在重新分配路径的瞬间延迟会波动,还可能短暂中断。

起因 在家、船上或飞机上,通过静止轨道或低轨卫星互联网、走卫星的机上 Wi-Fi 接入 → 结果 静止轨道高度约 36,000 km,往返距离本身就长;低轨卫星以很短的周期重新分配终端、卫星、地面站之间的路径,切换瞬间会短暂出现延迟和丢包 → 画面表现 静止轨道:所有动作都有很大的操作延迟;低轨:平时正常,每隔固定间隔出现一卡一卡、瞬移

症状
操作延迟, 一卡一卡, 瞬移
因素
延迟, 抖动, 丢包
谁会遇到
只有我, 同一家庭, 特定地区/运营商
何时出现
一直, 固定周期
负责方
主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发
研发团队要做的事
客户端:按抖动自动加长插值缓冲;输入重复发送,扛住短时丢包;显示连接质量。服务器:设定判定窗口和延迟补偿上限时考虑卫星线路的延迟;超时不要因为 1 秒左右的空白就把玩家踢下线。
外部要做的事
告知玩家卫星互联网的延迟可能很大,或会周期性飙升;竞技玩法尽量使用地面有线网络。
数值参考
静止轨道(高度 36,000 km)仅信号穿越太空的单程就要 260 ms,往返超过 520 ms(ITU-T G.114)。低轨的 Starlink 按官方数据(15 秒平均值),2024 年美国高峰时段中位数为 33 ms,最差的 1%(p99)也低于 65 ms;测量研究发现,每 15 秒重新分配路径的瞬间延迟会变化,并出现不到 1 秒的短暂中断。2018 年的机上互联网测量中,卫星方式的往返延迟平均为 750 ms。
监控图上
仅部分偏高 · RTT、抖动(按卫星互联网服务商 ASN)
查看位置
查接入 IP 的 ASN 是否属于卫星互联网服务商,把这些服务商玩家的 RTT 分布和时间序列单独画出来。从该 ASN 的 RIPE Atlas 探针向服务器连续 ping 几分钟,或让玩家开着 ping,测量飙升的间隔
确认依据
静止轨道服务商的 RTT 一直超过 500 ms;低轨服务商平时几十 ms,约每 15 秒 RTT 变化一次或短暂中断
排除依据
不是卫星服务商而 RTT 一直偏高,看“传播延迟(物理距离)”或“路由绕行”;不规则飙升则是 Wi-Fi 或移动信号的问题
确认手段
运维工具即可确认(无需游戏代码)
深入了解
低轨卫星离地面近(Starlink 单段 1.8~3.6 ms),平时延迟可以和地面线路差不多。但如果地面站接入互联网的出口(PoP)离游戏服务器远,路径就相应变长;经卫星间激光链路绕行时,延迟还会再增加。测量研究认为,15 秒周期的波动来自全球在同一时刻统一执行的路径重新分配,与卫星之间的切换无关。机上 Wi-Fi 的延迟因方式(卫星、地面基站)差别很大,采用静止轨道卫星的方式就会出现上面那样的长往返。
出处 5 条

路由绕行 Suboptimal routing

ID isp-routing · 主责 运维团队·网络运维 · 配合 外部·外部

受运营商之间互联协议的限制,离得近的服务器也可能要绕远路才能到。

起因 自己的运营商和服务器所在的运营商之间没有直连 → 结果 绕经其他国家或城市,距离和经过的设备都增加 → 画面表现 只有特定运营商的用户 ping 特别高

症状
操作延迟
因素
延迟
谁会遇到
特定地区/运营商
何时出现
一直
负责方
主责 运维团队·网络运维 · 配合 外部·外部
运维团队要做的事
与多家运营商互联(多线接入),按运营商监控 ping,找出绕路的运营商,与运营商协商调整路由。
外部要做的事
请相关运营商调整路由。
数值参考
即使在同一个国家内,不同路径的 ping 也可能相差两三倍。
监控图上
一直偏高 · RTT(按运营商/ASN)
查看位置
按运营商(ASN)比较 RTT,用慢的运营商的 RIPE Atlas 探针或玩家提供的 traceroute、mtr 结果,看路径经过哪些国家和城市。IPv4 和 IPv6 分开测(mtr -4, -6)
确认依据
同一地区只有特定运营商一直偏高,路径中有绕经其他国家或远方城市的跳。或者只有一个地址族(IPv4 或 IPv6)偏高
排除依据
所有运营商都差不多高,看“传播延迟(物理距离)”;只在晚上高,看“高峰时段对等互联链路拥塞”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
IPv4 和 IPv6 的路由各自独立,同一台服务器也可能只有一边绕远路而变慢(2016 年 APNIC 测量:同一运营商内单独出现了几组 IPv6 比 IPv4 慢 15、25、75 ms 的用户)。采用 Happy Eyeballs(RFC 8305,IPv6 和 IPv4 谁先连上就用谁)的应用会先尝试 IPv6,IPv6 在建议值 250 ms 内连上就不再尝试 IPv4。所以即使 IPv6 稍慢,也容易走这条路径连上。如果只有特定运营商 ping 高,就把 IPv4 和 IPv6 分开测。
真实案例
Riot Games 2015: 绕远路的 League of Legends 流量与 Riot Direct
出处 6 条

高峰时段对等互联链路拥塞 Peak-hour congestion at peering

ID isp-peak · 主责 运维团队·网络运维 · 配合 外部·外部

晚上 9~11 点前后视频流量激增,运营商之间的互联链路(对等互联)容易拥塞。

起因 晚间流媒体、下载集中 → 结果 对等互联链路出现排队和丢包 → 画面表现 只在晚上,特定运营商的用户出现一卡一卡、瞬移

症状
一卡一卡, 瞬移, 拉回
因素
抖动, 丢包, 延迟
谁会遇到
特定地区/运营商
何时出现
晚高峰
负责方
主责 运维团队·网络运维 · 配合 外部·外部
运维团队要做的事
增加与该运营商的直连,绕开拥塞路径,按运营商监控晚间的丢包和 ping。
外部要做的事
请该运营商为对等互联链路扩容。
监控图上
特定时段偏高 · RTT、丢包(按运营商)
查看位置
按运营商(ASN)把 RTT、丢包按时段画出来;从该运营商的 RIPE Atlas 探针或玩家处分别取得晚上和白天的 mtr 结果,看丢包从哪一跳开始
确认依据
只有特定运营商每天晚上 9~11 点前后 RTT、丢包上升,mtr 中从运营商互联那一跳一直到目的地都持续有丢包和延迟
排除依据
所有运营商一起上升,是我方线路或服务器的问题。只有同一家庭在晚上变差,看“Wi-Fi 信道拥挤”
确认手段
运维工具即可确认(无需游戏代码)
出处 2 条

海底光缆/国际线路故障 Submarine cable fault

ID isp-cable · 主责 外部·外部 · 配合 运维团队·网络运维

海底光缆一旦中断,修好之前的几周(长则几个月)里流量要走很远的绕行路径,剩下的线路也会拥挤。

起因 光缆断裂、设备故障 → 结果 流量挤到很远的绕行路径和剩余线路上 → 画面表现 海外玩家 ping 暴涨并伴随丢包,持续几天到几周

症状
操作延迟, 瞬移
因素
延迟, 丢包
谁会遇到
特定地区/运营商
何时出现
一直
负责方
主责 外部·外部 · 配合 运维团队·网络运维
运维团队要做的事
准备走其他路径的线路,故障时把流量切到那条路径。
外部要做的事
向海外玩家公告原因和预计恢复时间,向线路运营商确认修复进度。
监控图上
某一时刻起台阶式上升 · RTT(按海外国家)
查看位置
在按国家划分的 RTT、丢包曲线上找出上升时刻,与 Cloudflare Radar 的互联网中断汇总和海底光缆运营商的公告对照,再用 traceroute 确认路径是否绕经其他大洲
确认依据
从某一时刻起特定海外地区的 RTT 台阶式上升,维持几天到几周,同期有光缆故障报告。路径变成与平时不同的远距离绕行路线
排除依据
几天内恢复原状且没有故障报告,看“BGP 路由变更/收敛”或运营商段
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

BGP 路由变更/收敛 Route change / BGP convergence

ID isp-bgp · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部

互联网的路由信息发生变化后需要重新收敛,在这几秒到几十秒(少数情况下几分钟)里会丢包。

起因 某个运营商段的路由信息发生变化 → 结果 几秒到几十秒内数据包丢失,或切换到新路径 → 画面表现 突然卡住几秒,之后 ping 值变了(例如 40 → 70 ms)

症状
卡住, 瞬移
因素
丢包, 延迟
谁会遇到
特定地区/运营商
何时出现
偶尔随机
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部
研发团队要做的事
超时要扛得住短暂中断(连接停顿几秒不要马上断开)。
运维团队要做的事
路由监控(监视我方 IP 段的路由和 ping 变化);我方线路故障用 BFD 在 1 秒内检测并切换(BGP 默认保持时间为 90~180 秒);路由切到远路后一直不恢复,就把流量转到其他线路。
外部要做的事
路由频繁变化的运营商段,请该运营商查明原因。
监控图上
某一时刻起台阶式上升 · RTT、traceroute 路径
查看位置
比较 RTT 变化前后的 traceroute、mtr 路径,用 RIPEstat BGPlay 查看我方地址段(prefix)的 BGP 路由变更记录
确认依据
伴随几秒的卡住,RTT 移到另一个水平,同一时刻有 BGP 更新和 AS 路径变更
排除依据
没有路由变更记录而只在晚上升高,看“高峰时段对等互联链路拥塞”;只有部分连接差,看“ECMP 单条路径故障”
确认手段
运维工具即可确认(无需游戏代码)
真实案例
Cloudflare 2020: Cloudflare 骨干网配置错误导致部分城市流量丢失
Meta 2021: 一条骨干网命令让 Facebook 连 DNS 都消失的故障
Cloudflare 2025: Cloudflare 公共 DNS 1.1.1.1 故障
出处 4 条

ECMP 单条路径故障 ECMP / link bundle member fault

ID isp-ecmp · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部

运营商和数据中心通往同一目的地的路径往往有多条,每个连接固定走其中一条。只要有一条路径出故障,分到这条路径上的人就会一直卡。

起因 在多条线路捆绑的链路上,某一条线路或某台设备故障或拥塞 → 结果 路径按地址、端口组合(哈希)确定,只有分到该路径的连接出现丢包和延迟 → 画面表现 同一地区、同一运营商,只有部分人持续瞬移。重新连接后有时会恢复正常

症状
瞬移, 拉回, 一卡一卡
因素
丢包, 抖动
谁会遇到
只有我, 特定地区/运营商
何时出现
一直
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部
研发团队要做的事
按连接记录丢包和重传统计,以便提取受影响玩家的 IP、端口和时间(TCP 用 TCP_INFO 里的重传次数,UDP 按缺失的包序号计算)。
运维团队要做的事
收集受影响玩家的 IP、端口和时间,转交给运营商或数据中心;按路径监控丢包;路径测量要用和游戏相同的协议、端口(mtr --tcp、--udp 加 --port);如果是我方设备上的路径,就把故障线路或设备从捆绑组中摘除。
外部要做的事
请运营商确认并更换故障路径;引导玩家通过重新连接临时避开(重连后端口会变的情况下)。
数值参考
有 4 条路径时,只有约四分之一的用户会遇到。ping 测量可能走的是和游戏不同的路径,结果反而正常。
监控图上
仅部分偏高 · 各连接的丢包、重传(按 IP/端口)
查看位置
按源 IP、源端口拆分查看各连接的丢包和重传;用 mtr 以 UDP(-u)发往游戏端口(-P),并固定源端口(-L)测量,再换几个源端口重复多次。只给 -P 不给 -L 时,每个请求的源端口都会变,多条路径的结果会混在一起
确认依据
同一地区、同一运营商内,只有特定的源端口(或地址)组合持续丢包,重新连接换了端口后恢复正常
排除依据
换了端口仍然都差,是某一段整体拥塞或故障
确认手段
运维工具即可确认(无需游戏代码)
深入了解
为了不打乱同一连接内数据包的顺序,设备会按地址和端口(视设备配置,也可能只用地址)算出的值,给每个连接固定一条路径(ECMP、LAG)。只用地址计算的地方,重新连接也还是同一条路径,不会好转。所以同时收到“ping 正常,只有游戏卡”“重连之后好了”这类反馈时,就要怀疑这个原因。
出处 3 条

运营商限速/流量管理 Traffic shaping, data caps

ID isp-shaping · 主责 外部·外部 · 配合 研发团队·服务器开发, 运维团队·网络运维

套餐流量用超,或套餐对特定流量做管控时,数据包会被延后或丢弃。

起因 套餐流量用完后限速,或限制特定流量 → 结果 数据包排队或被丢弃 → 画面表现 用到一定流量后开始卡,移动网络尤其明显

症状
操作延迟, 瞬移
因素
延迟, 丢包
谁会遇到
只有我, 特定地区/运营商
何时出现
一直, 晚高峰
负责方
主责 外部·外部 · 配合 研发团队·服务器开发, 运维团队·网络运维
研发团队要做的事
减少游戏流量(压缩、只发必要的数据)。
运维团队要做的事
如果只在特定运营商出现游戏流量被延后或丢弃,收集证据向运营商升级反馈。
外部要做的事
引导玩家确认套餐流量是否用完、是否被限速,以及同一部手机上是否有其他应用在用网;请运营商确认是否限制了游戏流量。
数值参考
韩国的移动套餐流量用完后通常限速到 1~5 Mbps,便宜的套餐限到几百 kbps。游戏本身流量不大,但同一部手机上的其他应用在用网时,限速设备前就会形成排队。
监控图上
触顶后走平 · 吞吐量、RTT
查看位置
让玩家在运营商 App 里查看剩余流量和是否限速,并用测速看最高速率。服务器侧按运营商比较丢包和 RTT
确认依据
吞吐量停在 1~5 Mbps 或几百 kbps 这样的某个值上不去,从那时起同一部手机上的其他应用一用网,RTT 和丢包就增加。充值流量或换成 Wi-Fi 后消失
排除依据
没有限速而只有特定运营商差,看“高峰时段对等互联链路拥塞”或“路由绕行”
确认手段
需在玩家侧环境确认
出处 2 条

国家/运营商级 UDP 限制与包检测 UDP blocking, throttling and inspection by networks

ID isp-udp-block · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发, 运维团队·网络运维

有些网络会封锁特定的 UDP 地址和端口,或限制 UDP 速率;包检测设备还会过滤掉它识别不了的协议。用 UDP 通信的游戏在这类网络里会连不上,或频繁掉线。

起因 从限制 UDP 速率的部分运营商网络,或部署了国家/运营商级流量检测(审查)设备的网络接入 → 结果 封锁特定的 UDP 地址/端口,或在繁忙时段限制 UDP 速率,或过滤掉白名单以外的端口和协议,或只放行最初几个包之后就封锁 → 画面表现 只有特定国家或运营商的玩家出现连不上/无限加载、连上后很快掉线,繁忙时段因丢包而瞬移

症状
连不上/无限加载, 掉线, 瞬移
因素
丢包
谁会遇到
特定地区/运营商
何时出现
刚登录/维护结束后, 一直, 晚高峰
负责方
主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发, 运维团队·网络运维
研发团队要做的事
客户端:UDP 几秒内连不上就自动切换到 TCP/TLS 443 备用通道;一开始连上、随后很快断开的情况也要能检测到,并改走备用通道重试;在日志里记下走的是哪条通道。服务器:同一套游戏协议也通过 TCP 443(TLS)接收;备用通道的延迟可能更高,要相应调整超时。
运维团队要做的事
新增海外国家/地区之前,先在当地运营商网络中测量 UDP 能否到达以及高峰时段的丢包;在靠近当地的位置部署接收 TCP 443 备用通道的中继或网关;按国家、ASN 监控 UDP、TCP 连接成功率;对已确认限制 UDP 速率的运营商,收集证据升级反馈。
外部要做的事
向相关运营商或机构询问 UDP 限制的标准和放宽办法;引导玩家换一个网络连接对比。
数值参考
按 IETF 文档引用的测量,3~5% 的网络会完全封锁 UDP。Google 统计 2016 年 QUIC(基于 UDP)的使用情况时发现,4.4% 的客户端因为 UDP/QUIC 被封或路径 MTU 太小而用不了,其中大多在企业防火墙后面,没有观察到整个运营商封锁的情况。另有 0.3% 的客户端所在网络在高峰时段丢包大增,看起来在限制 UDP 速率;Google 向运营商提出要求后,这一比例已从 2015 年的 1% 降了下来。
监控图上
仅部分偏高 · UDP 连接成功率(按国家/ASN)
查看位置
按国家、ASN 分别查看 UDP 连接成功率和 TCP 443 备用通道成功率。在该运营商网络里的云 VM 或玩家电脑上,分别向游戏 UDP 端口和 TCP 443 做连接测试,并用 mtr -u -P(游戏端口)和 mtr -T -P 443 对比从哪一跳开始没有响应
确认依据
只有特定国家或 ASN 收不到 UDP 的首个响应,或几秒后就断开,同一位置的 TCP 443 正常。如果是限速,只在高峰时段 UDP 丢包明显增加,TCP 受影响较小
排除依据
TCP 也一起失败,看路径故障、IP 被封或“DNS 故障/延迟”。所有国家都一样,看我方服务器或防火墙配置;不分 UDP、TCP,只在瞬时发送量大时丢包,看“流量监管丢弃超额流量”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
包检测设备可能按地址、端口、协议挑出 UDP 流加以封锁,也可能采用除放行协议外一律封锁的方式(白名单)(IRTF 调研文档)。设备只看数据包的部分字段做判断时,协议稍作改动就可能被封。QUIC 早期,曾有一台防火墙在报头的 1 个比特变化后,先放行最初几个包,再拦截之后的包,导致客户端改用 TCP 连接的逻辑没能生效。新增海外国家/地区时,这类问题可能以“国内正常,只有那个国家的部分运营商连不上”这样的反馈暴露出来。如果只在咖啡馆、公司这类某个场所的网络里被封,请看“公共 Wi-Fi/公司网络限制”。
出处 4 条

线路质量差 Faulty last-mile line / modem

ID isp-line · 主责 外部·外部

接头接触不良、线路老化或调制解调器异常,会造成持续丢包和周期性的线路中断。

起因 线缆损坏、接触不良、调制解调器或光猫异常 → 结果 误码导致数据包被丢弃;偶尔线路要重新连接,会中断几秒到 1 分钟左右 → 画面表现 持续少量丢包,偶尔卡住几秒或掉线

症状
瞬移, 卡住, 掉线
因素
丢包
谁会遇到
同一家庭
何时出现
偶尔随机
负责方
主责 外部·外部
外部要做的事
引导玩家确认其他游戏、视频通话是否也会断;如果也断,就向运营商报修。
监控图上
偶发随机尖峰 · 丢包率、线路重连记录
查看位置
用 pathping(或 mtr)测几分钟到运营商第一跳的丢包,并在路由器管理页面的互联网(WAN)连接记录里查看重连时间
确认依据
线路空闲时也从运营商第一跳开始持续丢包,路由器记录的线路重连时间与卡住、掉线的时间重合。其他游戏、视频通话也一起断
排除依据
丢包从到路由器的无线段就开始,看“Wi-Fi 干扰/信号弱”;从运营商较远的段开始,是运营商路径的问题
确认手段
需在玩家侧环境确认
出处 3 条

DNS 故障/延迟 DNS failure / slowness

ID isp-dns · 主责 外部·外部 · 配合 研发团队·客户端开发

DNS 负责把服务器域名解析成地址。DNS 慢或失败时,就找不到登录服务器和更新服务器。

起因 运营商 DNS 故障或配置错误 → 结果 找不到登录服务器、更新服务器的地址 → 画面表现 点击登录按钮后长时间等待,或连不上。已经在线的人正常

症状
连不上/无限加载
因素
延迟, 丢包
谁会遇到
特定地区/运营商, 只有我
何时出现
刚登录/维护结束后
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
缓存地址(记住上次成功连接的服务器地址);准备多个 DNS(一个失败就换另一个 DNS 重新查询)。
外部要做的事
引导玩家把 DNS 换成公共 DNS 等其他服务器试试。
监控图上
仅部分偏高 · 登录失败数(按运营商)、DNS 查询耗时
查看位置
用 Resolve-DnsName -Server(或 nslookup)分别向运营商 DNS 和公共 DNS 查询登录服务器域名,对比响应时间和结果
确认依据
只有运营商 DNS 无响应或很慢,换成公共 DNS 后马上能连上。已经在线的玩家正常
排除依据
用哪个 DNS 都能马上解析出地址却连不上,是路径、防火墙或服务器的问题
确认手段
需在玩家侧环境确认
真实案例
Meta 2021: 一条骨干网命令让 Facebook 连 DNS 都消失的故障
Cloudflare 2025: Cloudflare 公共 DNS 1.1.1.1 故障
AWS 2025: AWS us-east-1 DynamoDB DNS 故障与漫长的恢复
出处 4 条

DDoS 导致共享线路饱和 DDoS saturating shared links

ID isp-ddos-path · 主责 运维团队·网络运维 · 配合 外部·外部

针对游戏公司或同一网络中其他目标的大流量攻击,会把共享线路占满。

起因 出现大量攻击流量 → 结果 走同一线路的正常流量也被挤压、丢弃 → 画面表现 大量玩家同时瞬移、掉线、连不上

症状
瞬移, 掉线, 连不上/无限加载
因素
丢包, 延迟
谁会遇到
全服, 特定地区/运营商
何时出现
偶尔随机, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 外部·外部
运维团队要做的事
使用 DDoS 防护服务;遭到攻击时把流量引到其他路径;隐藏服务器地址(放在防护设备后面,不暴露真实地址)。
外部要做的事
如果攻击针对的是同一网络中的其他目标,请运营商在上游拦截。
监控图上
触顶后走平 · 线路入向流量(bps/pps)、接口丢包
查看位置
把我方线路和设备的接口入向流量、丢弃的包数、DDoS 防护服务的攻击检测记录,与掉线集中的时间放在一起看
确认依据
线路入向流量顶到线路容量后走平,丢包增加,同一时刻多个地区、运营商的玩家一起瞬移、掉线
排除依据
线路还有余量而只有部分运营商差,是运营商段的拥塞或路由问题
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

运营商共享 IP(CGNAT) Carrier-grade NAT

ID isp-cgnat · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维

移动网络和部分运营商让多个用户共用一个 IP,并会在很短时间内清掉空闲连接的映射。

起因 运营商设备管理着海量用户的会话表 → 结果 会话表有上限,空闲超时短 → 画面表现 挂机一会儿就掉线;共用同一 IP 的人被一起封禁的误判

症状
掉线, 连不上/无限加载
因素
丢包
谁会遇到
特定地区/运营商
何时出现
挂机一段时间后
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维
研发团队要做的事
客户端:心跳间隔不超过最短空闲超时(移动网络有时只有 30 秒左右)的一半(运营商 CGNAT 的映射只有内部发出的包才能可靠刷新,所以由客户端发送);断开后自动重连。服务器:响应心跳,一段时间收不到就主动清理连接;映射变化导致地址、端口改变时,凭会话令牌识别为同一玩家并接续会话;一个 IP 可能由多人共用,按 IP 封禁的策略要慎重(结合账号、设备维度判断)。
运维团队要做的事
按运营商共享 IP 的情况,调整防火墙、DDoS 防护设备的单 IP 连接数和每秒新建连接数限制(移动运营商的地址段提高阈值或加入例外)。
数值参考
移动网络的 UDP 空闲超时有时只有 30 秒左右。
监控图上
连接成批断开 · 断开次数(心跳超时)、断开前的空闲时长(按运营商)
查看位置
在连接日志里看同一 IP 同时在线的账号数和所属运营商(ASN),按运营商汇总空闲后断开的连接的空闲时长。玩家侧在路由器管理页面查看互联网(WAN)地址
确认依据
移动运营商地址段内一个 IP 上挂着多个账号,断开前的空闲时长集中在 30~60 秒左右。路由器的 WAN 地址属于 100.64.0.0/10(运营商 NAT 用的共享地址),或与服务器看到的地址不同
排除依据
与运营商无关、集中在家用路由器用户上,看“NAT 映射过期”
确认手段
需要游戏服务器/客户端的日志和指标
出处 4 条

经由 VPN/游戏加速器 VPN / game accelerator detour

ID isp-vpn · 主责 外部·外部 · 配合 运维团队·网络运维, 研发团队·服务器开发

开启 VPN 或游戏加速器后,数据包会经过该公司的中转服务器。中转服务器远或拥挤时,反而会变慢。

起因 VPN、加速器把游戏数据包全部转到中转服务器 → 结果 叠加了到中转服务器的距离和拥塞;隧道报头还让 MTU(一次能发送的最大包长)变小 → 画面表现 ping 升高并丢包;与使用同一中转地址的人一起被封,连不上

症状
操作延迟, 瞬移, 连不上/无限加载
因素
延迟, 丢包
谁会遇到
只有我
何时出现
一直, 刚登录/维护结束后
负责方
主责 外部·外部 · 配合 运维团队·网络运维, 研发团队·服务器开发
研发团队要做的事
UDP 包保持在 1,200 字节以内(隧道报头让 MTU 变小时也不会分片);按 IP 封禁时要考虑 VPN、加速器的公共中转地址,结合账号、设备维度判断。
运维团队要做的事
海外玩家多的地区,自建就近的接入节点;“开了加速器就好了”这类反馈集中的运营商,要检查路由。
外部要做的事
引导玩家关掉 VPN、加速器对比一下。
数值参考
中转服务器近,只增加几 ms;绕经其他国家,会增加几十到 100 ms 以上。
监控图上
仅部分偏高 · RTT(按玩家)、接入 IP 所属服务商
查看位置
查接入 IP 的 ASN 是否属于 VPN、加速器或托管服务商,并让玩家关掉 VPN、加速器后对比 ping 和 traceroute
确认依据
只有开着 VPN、加速器时 RTT 和丢包增加或连接被拦,traceroute 里能看到经过中转服务器的一跳
排除依据
关和开都一样,是线路或运营商段的问题。开了反而更好,是原本的运营商路由(“路由绕行”“高峰时段对等互联链路拥塞”)有问题
确认手段
需在玩家侧环境确认
深入了解
反过来,运营商路由差的时候,加速器走更好的路径,ping 反而会降低。“开了加速器就好了”这类反馈,是路由绕行、晚间拥塞这类运营商路由问题的线索。
出处 3 条

L5 数据中心网络设备

11 个原因 · 完整版章节

防火墙会话表耗尽 Firewall session table exhaustion

ID dc-firewall · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发

防火墙会把放行的每个连接记到会话表里加以跟踪。表满之后就无法接受新连接。

起因 连接激增或攻击使会话数达到上限 → 结果 没有空位记录新连接,只能拒绝 → 画面表现 新进来的人连不上/无限加载,部分已有连接也会掉线

症状
连不上/无限加载, 掉线
因素
丢包
谁会遇到
全服
何时出现
刚登录/维护结束后, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发
研发团队要做的事
服务器:用登录排队系统调节集中涌入的连接;复用连接,避免反复建立短连接;心跳中断的连接主动先清理(以免死连接长时间占着会话表)。客户端:心跳间隔不超过最短空闲超时的一半;断开后自动重连,但重试间隔要逐步拉长并随机打散(以免再次同时涌入)。
运维团队要做的事
扩大会话表;尽快清理已结束的短连接(缩短已关闭会话的超时);缩短空闲会话超时时,把新值告知研发团队,以便对齐心跳间隔;拦截攻击;对会话数使用率设告警。
监控图上
触顶后走平 · 防火墙会话数、新建连接失败数
查看位置
把防火墙设备的并发会话数和会话上限画在同一张图上,在设备日志里找因建不了会话而丢弃的记录。Linux 防火墙对比 nf_conntrack_count 与 nf_conntrack_max,并看 dmesg 里的“nf_conntrack: table full, dropping packet”;AWS 实例看 ethtool -S 的 conntrack_allowance_exceeded
确认依据
从会话数在上限处走平的时刻起,新建连接失败增加,会话创建失败记录或丢弃计数器也一起增加
排除依据
会话数离上限还远却连不上,看“连接队列(backlog)溢出”或登录服务器。只有空闲连接断开,看“云安全组连接跟踪过期”
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

DDoS 防护引流/误判 DDoS scrubbing latency, false positives

ID dc-ddos · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

为防御攻击把流量牵引到清洗中心后,路径会变长,还可能把正常用户误判为攻击而拦截。

起因 检测到攻击后(或常态)把入向流量牵引到清洗中心 → 结果 路径变长,部分正常数据包被判为攻击 → 画面表现 整体 ping 升高,只有特定地区或运营商连不上

症状
操作延迟, 连不上/无限加载, 瞬移
因素
延迟, 丢包
谁会遇到
全服, 特定地区/运营商
何时出现
人多的时候, 偶尔随机
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发
研发团队要做的事
整理游戏流量特征(端口、包大小、每秒包数)并提供给运维团队;UDP 包保持在 1,200 字节以内。
运维团队要做的事
按游戏流量特征定制防护规则;按地区部署清洗节点;在隧道段调小 TCP 包大小(MSS 调整);通过各地区、运营商的连接失败率确认误判。
数值参考
清洗节点在同一国家时增加几 ms,经过其他国家的节点会增加 30~100 ms 以上。通常只有入向流量绕行,服务器的响应直接发出。清洗后的流量通过隧道回注时,一次能发送的大小(MTU)也会变小,可能引发只有大包丢失的问题。
监控图上
某一时刻起台阶式上升 · RTT(ping)、按地区/运营商的连接失败率
查看位置
把防护设备或服务的牵引(清洗)开始、结束记录和拦截日志,与 RTT 曲线、各地区/运营商的连接失败率放在同一时间轴上看。在出问题的地区用 mtr、traceroute 确认路径中是否夹入了清洗节点
确认依据
开启牵引的时刻 RTT 台阶式上升并保持,关闭后恢复。或者拦截日志里有正常玩家的地址,且只有该地区/运营商的连接失败率上升
排除依据
没有牵引、拦截记录的时刻 RTT 上升,看“路由绕行”或“BGP 路由变更/收敛”。只有大包丢失,看“MTU 不匹配(只有大包丢失)”
确认手段
运维工具即可确认(无需游戏代码)
出处 2 条

负载均衡器空闲超时 Load balancer idle timeout

ID dc-lb-idle · 主责 运维团队·网络运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发

负载均衡器会在一段时间后清除空闲连接。游戏这边仍以为连接还在,结果就掉线了。

起因 玩家一段时间内没有发送任何数据包(停在对话框、暂时离开) → 结果 负载均衡器清理空闲连接(常见默认值 60~350 秒) → 画面表现 再次操作的瞬间掉线

症状
掉线
因素
丢包
谁会遇到
只有我, 全服
何时出现
挂机一段时间后
负责方
主责 运维团队·网络运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发
研发团队要做的事
客户端:心跳间隔不超过最短空闲超时的一半(经过 ALB 时,60 秒超时对应 30 秒以内);断开后自动重连。服务器:响应心跳,一段时间收不到就主动清理连接;凭会话令牌接续会话。
运维团队要做的事
确认路径上各负载均衡器的空闲超时值并告知研发团队,必要时调大。
数值参考
默认值:AWS ALB 为 60 秒,NLB 为 TCP 350 秒、UDP 120 秒,Azure Load Balancer 为 TCP 4 分钟。ALB 和 NLB 的 TCP 值可以修改,NLB 的 UDP 120 秒不能改。ALB 超时后会把服务器侧的连接也关掉;NLB 则悄悄清除,服务器往往并不知情。
监控图上
连接成批断开 · 断开次数、断开前的空闲时长
查看位置
确认路径上各负载均衡器的空闲超时配置,收集每个断开连接从最后一个包到断开所经过的时间。AWS NLB 还要看 CloudWatch 的 TCP_ELB_Reset_Count(负载均衡器发出的 RST 数)
确认依据
断开连接的空闲时长集中在配置值(ALB 60 秒、NLB TCP 350 秒等)刚过的位置;静止超过这个时间后再操作即可复现。NLB 在那一时刻 TCP_ELB_Reset_Count 增加
排除依据
断开与空闲时长无关,则不是这个原因。不经负载均衡器直连的服务器上集中在 350 秒附近,看“云安全组连接跟踪过期”;问题在玩家家用路由器一侧,看“NAT 映射过期”
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

云安全组连接跟踪过期 Cloud security group connection tracking timeout

ID dc-cloud-conntrack · 主责 运维团队·系统运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发

云服务器上挂载的防火墙(安全组)同样会跟踪连接,空闲连接的跟踪条目到了规定时间就会过期。即使服务器不经负载均衡器、由玩家直接连接,挂机一段时间的玩家也可能掉线。

起因 安全组采用会跟踪游戏连接的配置(只放行特定地址、限制出站规则、经由 NLB 等) → 结果 连接空闲一段时间后跟踪条目过期,之后到达的数据包被安全组悄悄丢弃 → 画面表现 暂时离开后再操作,先是没有反应,随后掉线。服务器程序很长时间都察觉不到

症状
掉线
因素
丢包
谁会遇到
只有我, 全服
何时出现
挂机一段时间后
负责方
主责 运维团队·系统运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发
研发团队要做的事
客户端:心跳间隔不超过最短空闲超时的一半(TCP 为 350 秒时即 175 秒以内,UDP 流为 180 秒时即 90 秒以内);断开后自动重连。服务器:响应心跳,一段时间收不到就主动清理连接;凭会话令牌接续会话。
运维团队要做的事
确认实例的连接跟踪时长(TcpEstablishedTimeout),必要时调大(UDP 最大就是 180 秒,无法再调大);评估不产生跟踪的安全组配置(游戏端口对所有地址放行,出站规则全部放行。经由 NLB 的连接仍会被跟踪);迁移到新一代实例时做空闲测试。
数值参考
以 AWS 为例,Nitro v6 实例类型默认在 350 秒后清除空闲 TCP 连接的跟踪条目(其他类型为 5 天)。UDP 方面,请求和响应往返多次的流(stream)默认 180 秒,只朝一个方向发送或请求/响应只有一次的流默认 30 秒。
监控图上
连接成批断开 · 断开次数、断开前的空闲时长
查看位置
确认实例的连接跟踪时长配置和安全组规则(是否属于会产生跟踪的配置),收集断开连接的空闲时长。刚断开时在服务器上用 ss -tnoi 看该连接是否仍停留在 ESTABLISHED、重传定时器(timer:(on,…))在运转且 backoff 不断变大
确认依据
断开连接的空闲时长集中在 TCP 350 秒、UDP 流 180 秒、UDP 单向 30 秒刚过的位置;服务器侧 socket 没察觉到断开,仍停留在 ESTABLISHED(服务器有数据要发时只会反复重传)
排除依据
安全组属于不跟踪的配置(游戏端口对所有地址放行、出站规则全部放行、不经由 NLB),则不是这个原因。经由 NLB 时,与“负载均衡器空闲超时”的值对比
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

云 NAT 网关连接/端口上限 Cloud NAT gateway connection / port limits

ID dc-nat-gateway · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

私有子网中的服务器访问外部(平台认证、支付、外部 API)时,由 NAT 网关替换地址和端口后发出。发往同一目的地的并发连接超过网关的端口上限,新连接就会失败。

起因 多台服务器向平台认证、支付这类同一外部地址大量建立短连接,或长时间保持连接不关 → 结果 NAT 网关无法再为该目的地分配源端口,新连接失败 → 画面表现 游戏内一切正常,只有登录、支付、奖励发放这类需要调用外部的功能失败或变慢(连不上/无限加载、吞操作/回档)

症状
连不上/无限加载, 吞操作/回档
因素
丢包, 延迟
谁会遇到
仅特定功能, 全服
何时出现
刚登录/维护结束后, 晚高峰, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发
研发团队要做的事
调用外部 API 时复用连接(HTTP keep-alive、连接池),不要每个请求都新建连接;池中保留的空闲连接要以短于 NAT 空闲超时(AWS 350 秒)的间隔发送 keepalive,或者主动先关闭;失败时逐步拉长重试间隔并随机打散;按外部调用分别记录失败率和延迟。
运维团队要做的事
给 NAT 网关增加 IP 地址(AWS 公有 NAT 网关默认只能关联 2 个弹性 IP,更多需申请提高配额);按可用区、子网拆分网关;对端口分配失败指标设告警(AWS ErrorPortAllocation、Azure SNAT Connection Count 的 Failed、Google Cloud dropped_sent_packets_count 的 OUT_OF_RESOURCES);Google Cloud NAT 调大每台 VM 的最小端口数,或改用动态端口分配。
数值参考
AWS NAT 网关每个 IP 地址对同一目的地(IP、端口、协议)最多可建立 5.5 万个并发连接,最多可关联 8 个 IP 来扩展。350 秒没有数据的连接会被清除,之后再通过这个连接发送的数据包会收到 RST。Azure NAT Gateway 每个公网 IP 有 64,512 个 SNAT 端口(最多 16 个 IP)。Google Cloud NAT 把每个 NAT IP 的 64,512 个端口分给各台 VM,而每台 VM 的最小端口数默认为 64 个(静态分配),所以在默认设置下,一台 VM 向同一目的地同时能建立的连接通常被限制在 64 个。
监控图上
触顶后走平 · NAT 网关并发连接数、端口分配失败数
查看位置
AWS 看 CloudWatch 的 NAT 网关指标 ErrorPortAllocation、ActiveConnectionCount、PacketsDropCount(Azure 看按 Failed 状态筛选的 SNAT Connection Count 和 Dropped Packets,Google Cloud 看 dropped_sent_packets_count 中 reason 为 OUT_OF_RESOURCES 的值),与游戏服务器外部调用失败的时刻对照
确认依据
外部调用失败的时刻 ErrorPortAllocation(Azure 为 Failed 状态的 SNAT Connection Count,Google Cloud 为 OUT_OF_RESOURCES 丢弃)大于 0,且失败集中在发往认证、支付服务器这类连接密集的一两个目的地的调用上
排除依据
端口分配失败为 0,而游戏服务器的 connect 以 EADDRNOTAVAIL 失败、TIME_WAIT 数接近临时端口范围,看“服务器间连接的临时端口耗尽”。能连上只是响应慢,看“依赖外部服务”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
“服务器间连接的临时端口耗尽”是单台服务器的临时端口用光;这里的上限卡在 NAT 网关上,由网关后面的服务器共用(Google Cloud NAT 按 VM 分配)。服务器侧的 TIME_WAIT 和临时端口范围都还有余量,却只有外部调用失败,就是这个原因。已关闭连接的端口也不会马上再用于同一目的地(Azure 有冷却期,Google Cloud 在 TIME_WAIT 期间不可用),所以短连接重复得越多,越快碰到上限。
出处 7 条

负载均衡倾斜/健康检查误判 LB imbalance, bad health checks

ID dc-lb-imbalance · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

连接全都挤到一台服务器上,或者一直把玩家分配到已经挂掉的服务器。

起因 分配规则不合适,或健康检查反映不了实际状态 → 结果 只有一台服务器过载,或连接请求被发往挂掉的服务器 → 画面表现 只有部分分线、部分玩家出现慢动作、连不上/无限加载

症状
慢动作, 连不上/无限加载
因素
停顿, 丢包
谁会遇到
特定地点/分线
何时出现
刚登录/维护结束后, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发
研发团队要做的事
实现健康检查:根据实际游戏状态(tick 是否推进、DB 连接)回应负载均衡器的探测请求,并一起上报服务器负载值。
运维团队要做的事
把健康检查改为确认实际游戏响应的方式;按服务器负载分配;监控各服务器连接数的差异。
监控图上
仅部分偏高 · 各服务器的连接数、CPU 使用率
查看位置
把负载均衡器后面每台服务器的连接数(ss -s)和 CPU 使用率叠加在同一张图上看,并把负载均衡器上目标的健康状态(AWS 为 CloudWatch 的 HealthyHostCount、UnHealthyHostCount)与游戏服务器的实际状态对比
确认依据
只有一两台服务器的连接数、CPU 明显高于其他服务器;或 tick 已停住的服务器健康状态仍为“正常”,继续接收新连接
排除依据
各服务器连接数均匀,却只有一个分线慢,看该分线内部的负载(“单线程区域过载(热点)”)
确认手段
运维工具即可确认(无需游戏代码)
真实案例
AWS 2025: AWS us-east-1 DynamoDB DNS 故障与漫长的恢复
出处 3 条

交换机微突发 Switch microburst drops

ID dc-microburst · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维, 运维团队·系统运维

多台服务器在同一瞬间向数千名玩家集中发包时,这些流量汇聚的交换机端口缓冲区很小,不到 1 ms 就会溢出。

起因 世界 BOSS 登场、大范围技能,或多台服务器的 tick 恰好在同一瞬间对齐,集中发送 → 结果 在多个端口汇聚到一个端口、或从高速端口转到低速端口的位置,缓冲区(每个端口几百 KB 到几 MB)瞬间被占满 → 画面表现 部分数据包被丢弃,很多人同时瞬移、吞技能

症状
瞬移, 吞操作/回档
因素
丢包
谁会遇到
特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·网络运维, 运维团队·系统运维
研发团队要做的事
把发送均匀分散到一个 tick 内(平滑发送,pacing);让各服务器的 tick 起始时间稍微错开。
运维团队要做的事
网络:选用大缓冲交换机;分散流量(把服务器分布到多台交换机、多个端口上);监控交换机各端口的丢弃计数器。服务器/OS:限制服务器整体发送速率上限(Linux tc 流量整形)。
数值参考
10 Gbps 端口 1 ms 内能发出约 1.25 MB。两个端口的流量同时涌向一个端口时,每 1 ms 就会积压 1.25 MB。即使 1 秒平均利用率只有 10%,按 1 ms 粒度看仍可能溢出。
监控图上
随人数/负载上升 · 交换机端口出方向丢弃数
查看位置
以尽可能短的间隔采集服务器所连交换机端口及其流量汇聚端口的出方向丢弃计数器(ifOutDiscards,视设备而定也叫 output drops),与 BOSS 登场、大规模战斗的时刻对照。只看 1 秒、1 分钟平均利用率曲线看不出来
确认依据
平均利用率不高,但每当人群聚到一处时出方向丢弃就增加,同时有多名玩家反馈瞬移、吞技能
排除依据
丢弃在平均利用率高的时段持续增加,看“数据中心线路饱和”。输入错误(CRC)增加,看“线缆不良/端口错误”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

网络设备故障切换(主备切换) Network device failover

ID dc-failover · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发

某台路由器或防火墙发生故障、切换到备用设备(故障切换)的几秒内,所有人都会卡住。

起因 设备故障或维护时切换到备用设备 → 结果 切换需要几秒;会话信息未同步时连接会被重置 → 画面表现 该服务器的所有玩家同时卡住,大量掉线

症状
卡住, 掉线
因素
丢包
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发
研发团队要做的事
服务器:超时设置要能扛住短暂中断(几秒);断开后重连时,凭会话令牌接续会话。客户端:断开后自动重连(重试间隔随机打散,避免同时涌入)。
运维团队要做的事
采用共享连接状态的冗余方案;用 BFD 在 1 秒内检测故障;定期做切换演练。
数值参考
设备能立即检测到故障时,大约 1~3 秒。没有快速故障检测(BFD)、只靠 BGP 默认定时器时,邻居设备要 90~180 秒才能察觉,期间路由可能一直中断。
监控图上
连接成批断开 · 连接数、服务器整体收发流量
查看位置
查看路由器、防火墙的事件日志(VRRP 角色变化、BFD/BGP 会话 down、故障切换记录),以及同一时刻服务器整体的连接数、收发流量
确认依据
设备日志记录的切换时刻,该设备后面所有服务器的流量有几秒降为 0,或连接数同时下跌
排除依据
只有一台服务器的连接数下跌,看“服务器崩溃”或“网卡驱动/固件问题”。设备日志干净,停住的服务器是单台云虚拟机,看“云宿主机维护/热迁移”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

线缆不良/端口错误 Bad cable / optics (CRC errors)

ID dc-bad-cable · 主责 运维团队·网络运维

光模块或线缆不良时,经过这条路径的数据包会按一定比例损坏。

起因 光模块、线缆不良导致误码 → 结果 损坏的数据包被设备悄悄丢弃 → 画面表现 只有走这条路径的部分服务器、玩家持续丢包,出现瞬移、拉回

症状
瞬移, 拉回
因素
丢包
谁会遇到
特定地点/分线
何时出现
一直
负责方
主责 运维团队·网络运维
运维团队要做的事
监控端口错误(CRC)计数器并设告警;更换光模块、线缆等部件;更换前先把问题链路摘除,让流量绕行。
监控图上
仅部分偏高 · 各端口 CRC 错误数、各服务器/路径的丢包率
查看位置
看链路两端的 CRC 计数器。交换机看端口的 FCS 错误(dot3StatsFCSErrors)、输入错误(ifInErrors),服务器看 ip -s -s link 的 RX errors 中的 crc(内核统计项 rx_crc_errors)
确认依据
某个端口的 CRC 错误与流量大小、时段无关地持续增加,只有经过该端口的服务器、玩家有丢包
排除依据
没有 CRC 错误,只有出方向丢弃增加,属于拥塞(“交换机微突发”“数据中心线路饱和”)
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

MTU 不匹配(只有大包丢失) MTU black hole

ID dc-mtu · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发

中间某段的 MTU(一次能发送的最大尺寸)变小,而包过大通知又被拦截时,只有大包会一直丢失。

起因 隧道、VPN 段的 MTU 变小 → 结果 包过大通知(ICMP)被防火墙拦截,发送方不知情 → 画面表现 只有打开背包、角色列表这类数据量大的界面时才卡住,随后掉线

症状
卡住, 掉线, 连不上/无限加载
因素
丢包
谁会遇到
特定地区/运营商, 只有我
何时出现
做特定操作时, 刚登录/维护结束后
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
研发团队要做的事
要在服务器侧直接调小,就设置 socket 的最大报文段长度 TCP_MAXSEG(只在游戏代码里把消息切小防不住);UDP 包保持在 1,200 字节以内。
运维团队要做的事
网络:在隧道段调小 TCP 包大小(MSS 调整);在防火墙、云网络 ACL 中放行包过大通知(ICMP)。服务器/OS:服务器防火墙、云安全组同样放行包过大通知(ICMP);开启服务器内核的 MTU 探测(tcp_mtu_probing=1),这是停顿几秒后才会生效的最后一道保险。
数值参考
通常为 1,500 字节,经过隧道后会降到 1,400 左右。
监控图上
仅部分偏高 · 按地区/运营商的掉线数、大响应失败数
查看位置
在出问题的玩家电脑上向服务器发送带“禁止分片”标志(DF)的 ping,并不断改变包大小。Windows 用 ping /f /l 1472 SERVER_IP,Linux 用 ping -M do -s 1472 SERVER_IP(1,472 是 MTU 1,500 减去 IP 头 20 字节和 ICMP 头 8 字节后的值)。逐步减小尺寸,找出能通过的最大值;并确认服务器侧安全组、防火墙是否放行 ICMP 包过大通知(Fragmentation Needed)
确认依据
小 ping 能通,1,472 字节的 DF ping 却失败(无响应,或报需要分片的错误),能通过的最大尺寸只有 1,400 左右。同一地区的玩家只在打开数据量大的界面时卡住
排除依据
1,472 字节的 DF ping 也能通,则不是路径 MTU 问题。小 ping 也不通,说明 ICMP 本身被拦截,用这个方法无法判断
确认手段
需在玩家侧环境确认
出处 5 条

L6 服务器网卡

9 个原因 · 完整版章节

网卡中断集中在单个核心 Single-queue NIC / no RSS

ID nic-irq · 主责 运维团队·系统运维

网卡把数据包到达中断只发给一个 CPU 核心时,这个核心就会成为瓶颈。

起因 只有一个接收队列,或者把负载分散到多个核心的 RSS 没有开启 → 结果 单个核心跑满 100%,来不及取出数据包 → 画面表现 人多时全服出现丢包和延迟(瞬移、操作延迟)

症状
瞬移, 拉回, 操作延迟
因素
丢包, 延迟
谁会遇到
全服
何时出现
人多的时候
负责方
主责 运维团队·系统运维
运维团队要做的事
配置 RSS(由网卡分散)、RPS(由内核分散);把中断分散到多个核心;让 UDP 连端口一起参与队列划分(ethtool -N 的 rx-flow-hash udp4 sdfn);把处理中断的核心与游戏 tick 线程所在核心分开;监控各核心的 %soft。
数值参考
单个核心经内核协议栈能处理的量,视包大小和配置而定,大约每秒几十万个包。在各核心的使用率中,接收处理所占的比例(mpstat 的 %soft)只集中在一个核心上,就是这种情况。
监控图上
触顶后走平 · 各核心 %soft、每秒接收包数
查看位置
用 mpstat -P ALL 1 看各核心的 %soft(软中断处理占比),用 /proc/interrupts 看网卡各队列的中断分到了哪个核心,用 ethtool -l 看队列数,用 ethtool -S 看各队列的包数(名称因驱动而异)
确认依据
只有一个核心的 %soft 贴近 100%,其余核心空闲,中断和数据包集中在一个队列。从那时起每秒接收包数再也涨不上去
排除依据
%soft 均匀分布在多个核心上,则不是这个原因。CPU 空闲却有丢包,看“超出云 PPS 上限”或“环形缓冲区不足”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
即使有多个队列,如果大部分流量像网关、代理那样来自少数几个地址,也会集中到一个队列。对 UDP,有些网卡默认只按地址划分队列,要改成连端口一起计算才能分散均匀。
出处 4 条

环形缓冲区不足 RX ring buffer overflow

ID nic-ring · 主责 运维团队·系统运维

网卡用来暂存数据包的环形缓冲区太小时,流量瞬间涌入就会溢出,数据包被丢弃。

起因 环形缓冲区保持默认值,容量偏小(因驱动而异,256~2,048 个槽位) → 结果 突发时 CPU 还没来得及取走,缓冲区就溢出了 → 画面表现 只在突发的瞬间丢包(瞬移、吞技能)。游戏服务器日志里没有任何痕迹

症状
瞬移, 吞操作/回档
因素
丢包
谁会遇到
全服
何时出现
人多的时候
负责方
主责 运维团队·系统运维
运维团队要做的事
调大环形缓冲区(ethtool -G);监控丢弃(drop)计数器(ethtool -S 的 rx_missed_errors 等,名称因驱动而异)。
数值参考
每秒涌入 100 万个包时,1,024 个槽位约 1 ms 就会被占满。这期间 CPU 只要晚来一次就会溢出。大多数网卡可以调到几千个。
监控图上
偶发随机尖峰 · 网卡接收丢弃计数器
查看位置
以较短间隔采集 ethtool -S 的接收丢弃计数器(rx_missed_errors、rx_fifo_errors 等,名称因驱动而异)和 ip -s -s link 的 missed,用 ethtool -g 查看当前环形缓冲区大小和最大值
确认依据
突发瞬间丢弃计数器增加,当前环形缓冲区远小于最大值。调大后丢弃减少
排除依据
丢弃计数器不变却有丢包,看内核的下一环节(“内核 socket 缓冲区不足”)或网络链路。单个核心的 %soft 达到 100%,看“网卡中断集中在单个核心”
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

中断合并过度 Interrupt coalescing

ID nic-coalesce · 主责 运维团队·系统运维

为减轻 CPU 负担,网卡把数据包攒一批再一次性通知 CPU,攒包花了多少时间,就会晚多少。

起因 网卡攒够一定时间或一定数量后再发中断 → 结果 攒包期间数据包在等待 → 画面表现 延迟略有增加。通常很小,设置过度时可达 ms 级

症状
操作延迟
因素
延迟
谁会遇到
全服
何时出现
一直
负责方
主责 运维团队·系统运维
运维团队要做的事
使用自适应合并,按游戏服务器的需要调整数值(ethtool -C)。
数值参考
通常为几十到几百 µs。对游戏来说一般可以忽略,设置过度时会增大到 ms 级。
监控图上
一直偏高 · 同一数据中心内的往返时间
查看位置
用 ethtool -c 查看当前合并设置(adaptive-rx、rx-usecs、rx-frames),比较修改设置前后与同一数据中心其他服务器之间的 ping 往返时间
确认依据
rx-usecs 设得很大(几百 µs 以上),调小后同一数据中心内的往返时间相应缩短
排除依据
调小后往返时间不变,则不是这个原因
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

超出云 PPS 上限 Cloud PPS / bandwidth allowance

ID nic-cloud-pps · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部

云服务器每种规格都有每秒包数和带宽上限,超出部分会被悄悄丢弃。

起因 同时在线人数增加,每秒包数超过实例上限 → 结果 云网络丢弃超出部分 → 画面表现 原因不明的丢包导致瞬移、吞技能。服务器 CPU 有余量

症状
瞬移, 吞操作/回档
因素
丢包
谁会遇到
全服
何时出现
人多的时候, 晚高峰
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部
研发团队要做的事
合并数据包(一个 tick 的消息放进一个包);避免频繁发送极小的包。
运维团队要做的事
查看超限计数器(AWS 为 pps_allowance_exceeded、conntrack_allowance_exceeded 等)并设告警;换更大的实例;连接跟踪上限通过不产生跟踪的安全组配置来规避。
外部要做的事
向云厂商询问各实例规格的每秒包数和连接跟踪上限。
数值参考
上限因实例大小而异,每秒包数上限很多时候并不公开。小规格实例标称的“最高 10 Gbps”是只在积分未用完时(通常 5~60 分钟)才能达到的突发速率,平时的基准速率要低得多。
监控图上
触顶后走平 · 每秒包数、allowance 超限计数器
查看位置
以较短间隔采集 ethtool -S 中的 ENA 计数器 pps_allowance_exceeded、bw_in_allowance_exceeded、bw_out_allowance_exceeded、conntrack_allowance_exceeded,与每秒包数一起看。也可以用 CloudWatch 代理上报这些计数器并设告警
确认依据
丢包的时刻 allowance 超限计数器增加,每秒包数卡在某个值上不再上涨。服务器 CPU 有余量
排除依据
超限计数器不变,则不是这个原因。单个核心的 %soft 达到 100%,看“网卡中断集中在单个核心”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
conntrack_allowance_exceeded 表示连接跟踪表已满、新连接被丢弃。如果跟踪表还有余量,只是空闲连接因跟踪过期而断开,请看“云安全组连接跟踪过期”。
出处 2 条

网卡带宽饱和 NIC bandwidth saturation

ID nic-saturate · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

流量用到 1 Gbps、10 Gbps 网卡的极限时,发送队列会越排越长,最终数据包被丢弃。

起因 广播增多,发送量达到网卡极限 → 结果 发送队列变长,溢出后丢弃 → 画面表现 全服延迟、丢包(操作延迟、瞬移)

症状
操作延迟, 瞬移
因素
延迟, 丢包
谁会遇到
全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
减少发送量(AOI 过滤、压缩、只发增量)。
运维团队要做的事
升级网卡(换更快的网卡,云上换更大的实例);对网卡利用率设告警。
监控图上
触顶后走平 · 网卡发送量、发送丢弃
查看位置
把 sar -n DEV 1 的 txkB/s 和 %ifutil(相对接口速率的利用率)与网卡速率、实例带宽对比,同时看 ip -s link 的 TX dropped
确认依据
发送量在网卡或实例带宽附近走平,从那时起发送丢弃和全服延迟增加
排除依据
带宽有余量,则不是这个原因。小包多且有丢包,看“超出云 PPS 上限”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

虚拟化开销/邻居干扰 Noisy neighbors in virtualization

ID nic-noisy · 主责 运维团队·系统运维 · 配合 外部·外部

同一台物理服务器上的其他虚拟机大量占用网络和 CPU 时,自己这台服务器的处理会不规律地被拖后。

起因 同一台物理服务器上的其他虚拟机大量占用资源 → 结果 自己这台虚拟机的数据包处理不规律地延迟 → 画面表现 没有明显原因,偶尔出现抖动(到达间隔的波动),表现为一卡一卡

症状
一卡一卡
因素
抖动
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 运维团队·系统运维 · 配合 外部·外部
运维团队要做的事
使用专用主机、性能有保障的实例;抖动持续的实例先停止再启动,迁移到其他宿主机。
外部要做的事
向云厂商报告有问题的宿主机。
监控图上
偶发随机尖峰 · 同一数据中心内的往返时间抖动、%steal
查看位置
持续向同一数据中心的其他服务器发 ping,记录往返时间的抖动,连同 mpstat 的 %steal 一起与同配置的其他实例对比
确认依据
只有这台实例的往返时间抖动或 %steal 不规律地跳变,同配置的其他实例很平稳。停止后再启动、换到其他宿主机后问题消失
排除依据
同配置的实例全都一样跳变,则不是宿主机问题。看游戏服务器侧的负载或网络链路
确认手段
运维工具即可确认(无需游戏代码)
出处 2 条

云宿主机维护/热迁移 Cloud host maintenance / live migration

ID nic-host-maintenance · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部

云厂商维护物理服务器(宿主机)时,会把虚拟机迁到其他宿主机(热迁移)或暂停片刻。这期间整台服务器停住,停住时间一长,连接就会断开。

起因 云厂商因宿主机维护或预测到故障,把虚拟机迁到其他宿主机,或短暂挂起 → 结果 迁移期间 CPU、内存、网络变慢,最后虚拟机会短暂完全停住(视厂商和方式而定,从不到 1 秒到 30 秒左右) → 画面表现 该服务器上所有人同时卡住,随后出现快进、瞬移;停住时间超过超时设置时大量掉线

症状
卡住, 快进, 瞬移, 掉线
因素
停顿, 丢包
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部
研发团队要做的事
超时设置要能扛住几秒的停顿;停顿后追赶的 tick 数设上限;经过时间用单调时钟(monotonic clock)计算;建立收到维护通知后保存进度、把玩家转移到其他服务器的流程。
运维团队要做的事
订阅维护通知并设告警(Google Cloud maintenance-event、AWS 计划事件和 AWS Health、Azure Scheduled Events);收到通知后在玩家少的时段提前替换服务器;厂商允许时调整维护时间(Azure Maintenance Configuration,部分类型的 AWS 计划事件);把维护记录与故障记录对照。
外部要做的事
向云厂商确认维护计划和影响范围;同一实例反复停顿时向厂商报告。
数值参考
Google Compute Engine 称热迁移的停顿通常远短于 1 秒,停顿期间系统时钟最多可能向前跳 5 秒。元数据中的 maintenance-event 值会在迁移前 60 秒变化(前提是此前至少查询过一次这个值)。Azure 不需要重启的维护几乎总是停顿不到 10 秒,偶尔(通用规格每 18 个月不超过一次)停顿约 30 秒,热迁移通常不超过 5 秒。Azure Scheduled Events 至少提前 15 分钟通知这类停顿(Freeze)。不过宿主机硬件突然故障时,会不发通知直接开始恢复。
监控图上
断流后集中到达 · 服务器收发包数、tick 间隔
查看位置
把停住的时刻与厂商的记录对照。Google Cloud 看审计日志中的 compute.instances.migrateOnHostMaintenance,AWS 看 describe-instance-status、AWS Health 中的计划事件,Azure 看活动日志(Activity Log)中的 Microsoft.Compute/virtualMachines/liveMigration/action,以及 VM 可用性指标(VmAvailabilityMetric)降到 0 的时刻。服务器内部看停住期间指标、日志是否出现空白,紧接着时钟是否跳变(时间同步日志)
确认依据
整台服务器停住的时刻与厂商记录的维护、迁移时刻重合,那几秒内服务器内部的指标和日志全部空白
排除依据
厂商记录中没有,且短暂停顿频繁反复,看“CPU 窃取时间(虚拟机)”。内核日志里有网卡重置记录,看“网卡驱动/固件问题”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
AWS 通过计划事件通知。system-reboot 表示会重启并迁到新的宿主机,system-maintenance 表示可能因网络、电源维护受到短暂影响。即使只停了几秒,这期间发给服务器的数据包没收到 ACK 的客户端会把重传等待时间逐次翻倍,所以恢复之后 TCP 连接可能还要停得更久(“TCP RTO 与指数退避”)。停住后恢复时时钟会跳变,可能引发“系统时钟跳变(NTP step)”;负载均衡器健康检查也可能失败,把这台服务器暂时摘除。无法迁移的实例(如 Google Cloud 的裸金属实例)在维护时会被停止或重启。
出处 6 条

网卡驱动/固件问题 NIC hang / reset

ID nic-reset · 主责 运维团队·系统运维

驱动 bug 或某项功能异常导致网卡停住、重启,这期间所有收发都会中断。

起因 驱动 bug、卸载(offload)功能异常 → 结果 网卡停住后重启(几秒) → 画面表现 该服务器上所有人一起卡住,随后瞬移或掉线

症状
卡住, 掉线
因素
丢包
谁会遇到
全服
何时出现
偶尔随机, 开得越久越严重
负责方
主责 运维团队·系统运维
运维团队要做的事
检查内核日志中的“transmit queue … timed out”“Link is Down”记录并设告警;更新驱动和固件;关闭有问题的功能(offload 等)。
监控图上
断流后集中到达 · 服务器收发包数
查看位置
用 dmesg 在内核日志中查找“NETDEV WATCHDOG … transmit queue N timed out”、驱动重置以及“Link is Down”“Link is Up”记录,并看同一时刻服务器的收发包数
确认依据
停住的时刻内核日志里有发送队列超时或链路 down/up 记录,那几秒内收发包数为 0
排除依据
内核日志干净、交换机侧端口也正常,看游戏服务器进程的停顿(“服务器 GC 全局停顿”“死锁”)或“网络设备故障切换(主备切换)”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

GRO/LRO 合并等待延迟 GRO/LRO batching

ID nic-offload · 主责 运维团队·系统运维

这是把多个数据包合并成一个、以减轻 CPU 负担的功能。视配置而定,较小的游戏数据包可能要短暂等待下一个可合并的包。

起因 网卡和内核把到达的数据包合并处理 → 结果 开启了硬件合并(LRO)或合并等待时间设置时,会为等下一个包而短暂停留 → 画面表现 延迟略有增加(一般不超过几十 µs)

症状
操作延迟
因素
延迟
谁会遇到
全服
何时出现
一直
负责方
主责 运维团队·系统运维
运维团队要做的事
按游戏流量调整(关闭 LRO,检查合并等待时间设置);影响通常较小,排在其他原因之后再查。
监控图上
一直偏高 · 同一数据中心内的往返时间
查看位置
用 ethtool -k 查看 lro、gro 状态,查看设备 sysfs 配置中的 gro_flush_timeout 值,并比较修改前后同一数据中心内小包的往返时间
确认依据
LRO 已开启或 gro_flush_timeout 大于 0,关闭或设为 0 后小包的往返时间缩短
排除依据
修改后差异在几 µs 以内,则不是这个原因
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

L7 服务器操作系统(内核)

14 个原因 · 完整版章节

连接队列(backlog)溢出 Listen backlog / SYN queue overflow

ID so-backlog · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发

维护结束后数万人同时连接时,内核的连接队列(backlog)会溢出,连接请求被丢弃。

起因 维护一结束,连接涌入的速度就超过了游戏服务器用 accept 处理连接的速度 → 结果 内核的连接队列(backlog,取服务器代码传给 listen 的值与内核上限中较小的一个)已满 → 画面表现 连接请求被丢弃,客户端反复重试,表现为连不上/无限加载

症状
连不上/无限加载
因素
丢包
谁会遇到
全服
何时出现
刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发
研发团队要做的事
服务器:调大代码里传给 listen 的值;不要让接受连接的线程因为其他工作而停住;做登录排队系统。客户端:拉长重试间隔(随机打散)。
运维团队要做的事
调大内核 somaxconn(要和服务器代码里的 listen 值一起调大才有效);保持 SYN Cookie 开启;监控溢出次数(nstat 的 TcpExtListenOverflows)。
数值参考
Linux 内核上限(somaxconn)从 5.4 起默认为 4,096(之前为 128),但服务器代码传给 listen 的值更小时,以这个值为上限。Linux 在队列满时会不报任何错误,悄悄丢弃连接请求。客户端操作系统会从 1 秒后开始重发几次,所以玩家看到的往往是长时间加载,很少直接出现“连接失败”。Windows 服务器则会回复拒绝,客户端马上就会看到“连接失败”。
监控图上
开服/维护后激增 · 连接队列溢出次数(ListenOverflows)、连接请求数
查看位置
看 nstat -az 中 TcpExtListenOverflows、TcpExtListenDrops 的增量,用 ss -ltn 对比监听 socket 的 Recv-Q(等待 accept 的连接数)和 Send-Q(backlog 上限)
确认依据
连接集中涌入的时刻 ListenOverflows 增加,监听 socket 的 Recv-Q 贴着 Send-Q 的值
排除依据
ListenOverflows 没有变化则不是这个原因。连接已经建立但加载一直不结束,看“登录激增与 N+1 查询”;人数一到某个固定值就进不来,看“文件描述符上限”
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

文件描述符上限 File descriptor limit (ulimit)

ID so-fd · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

每个连接都要占用一个文件描述符(fd,操作系统给打开的文件、socket 分配的编号),而一个进程能打开的 fd 数量是有限的。

起因 同时在线人数达到进程的文件描述符上限 → 结果 服务器无法接受新连接(Too many open files),打开日志、建立 DB 连接也一起失败 → 画面表现 从某个固定人数开始谁都进不来,表现为连不上/无限加载

症状
连不上/无限加载
因素
丢包
谁会遇到
全服
何时出现
刚登录/维护结束后, 人多的时候
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
结束连接时务必关闭 socket(防止 fd 泄漏);accept 因 EMFILE(fd 不足)失败时,先暂停接受连接一会儿,或用预先留出的备用 fd 接下连接后立即关闭(以免反复处理同一个连接通知、白白消耗 CPU)。
运维团队要做的事
检查 ulimit 和服务配置(systemd 的 LimitNOFILE);接近上限时告警。
数值参考
Linux 在不单独配置服务的情况下,上限仍常常是 1,024。游戏服务器通常会调到数万到数十万。Windows 没有这么低的默认上限。
监控图上
触顶后走平 · 进程打开的 fd 数、同时在线人数
查看位置
用 pidstat -v 看游戏服务器进程的 fd-nr(打开的文件描述符数),用 /proc/PID/limits 看打开文件数上限,在服务器日志里找 accept 失败(EMFILE、Too many open files)
确认依据
fd 数在上限值处走平,从那一刻起 accept 以 EMFILE 失败
排除依据
fd 数离上限还很远则不是这个原因。连接请求在内核被丢弃,看“连接队列(backlog)溢出”;问题在连接跟踪,看“服务器 conntrack 表耗尽”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
没被接受的连接仍留在内核连接队列(backlog)里。视服务器代码而定,进程可能会不断收到“有新连接”的通知,白白浪费 CPU。
出处 5 条

内核 socket 缓冲区不足 Small socket buffers

ID so-sockbuf · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

收发缓冲区太小时,一旦突发流量涌来,UDP 收到的数据包会被丢弃,TCP 发送则因缓冲区没有余量而阻塞。

起因 SO_SNDBUF、SO_RCVBUF 用的是默认值或设得太小 → 结果 突发流量或接收线程短暂停住期间,UDP 接收缓冲区溢出而丢包;TCP 发送缓冲区没有余量,只能等待 → 画面表现 瞬移(UDP 丢包)或快进(TCP 等待)

症状
瞬移, 快进
因素
丢包, 停顿
谁会遇到
全服
何时出现
人多的时候
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
在代码中按流量设置缓冲区大小(SO_SNDBUF、SO_RCVBUF);注意 TCP 手动指定大小后会关闭 Linux 的自动调节;设得太大会让旧数据堆在缓冲区里、延迟变大,要适度;不要让接收线程停住。
运维团队要做的事
调整内核上限(rmem_max、wmem_max,代码里设置的缓冲区大小也不能超过这个值)和默认值(rmem_default);监控缓冲区溢出计数器(RcvbufErrors)。
数值参考
Linux UDP 接收缓冲区默认约 208 KB。即使是小数据包,每个包占用的内核内存也远大于实际大小,几十到几百个就会填满。每秒接收 10 万个包的服务器,接收线程只要停住几 ms 就会溢出。
监控图上
偶发随机尖峰 · UDP 接收缓冲区溢出(UdpRcvbufErrors)
查看位置
看 nstat -az 的 UdpRcvbufErrors 增量和 ss -uamn 的 skmem(rb 为接收缓冲区大小,d 为没能放进 socket 而丢弃的包数);TCP 看 ss -tm 的 skmem 中发送排队内存(w)是否达到发送缓冲区大小(tb)
确认依据
突发流量或接收线程停住的时刻 UdpRcvbufErrors(或 socket 的 d)增加,rb 在默认值(约 208 KB)附近。TCP 则是 w 贴着 tb,send 阻塞
排除依据
计数器没有变化却有丢包,查网卡层(“环形缓冲区不足”)或网络链路
确认手段
运维工具即可确认(无需游戏代码)
出处 6 条

线程过多与上下文切换 Thread oversubscription, context switching

ID so-context · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

线程数远多于核心数时,操作系统光是让它们轮流运行就要耗掉大量 CPU。

起因 比如每个连接创建一个线程,线程数达到几百到几千个 → 结果 上下文切换(更换正在执行的线程)的开销和缓存未命中增加 → 画面表现 CPU 很忙但吞吐量低,tick 忽快忽慢,表现为一卡一卡、慢动作

症状
一卡一卡, 慢动作
因素
停顿, 抖动
谁会遇到
全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
线程数与核心数匹配;使用异步 I/O(epoll、IOCP)。
运维团队要做的事
监控上下文切换次数和等待运行的线程数(vmstat 的 cs、r)。
数值参考
一次上下文切换需要几 µs,再加上之后的缓存未命中开销,代价更大。
监控图上
随人数/负载上升 · 每秒上下文切换次数、等待运行的线程数
查看位置
把 vmstat 1 的 cs(每秒上下文切换次数)、r(正在运行或等待 CPU 的数量)与核心数对比,用 pidstat -w -t 看游戏服务器各线程的自愿(cswch/s)、非自愿(nvcswch/s)上下文切换
确认依据
同时在线人数上升时 r 远超核心数,cs 也跟着飙升,非自愿上下文切换多的线程有几百个
排除依据
r 保持在核心数以下则不是这个原因。只有自愿切换多,说明线程在等锁或 I/O(“锁竞争”“阻塞 I/O 模型”)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

CPU 窃取时间(虚拟机) CPU steal time

ID so-steal · 主责 运维团队·系统运维 · 配合 外部·外部

物理服务器(hypervisor)把虚拟机的 CPU 时间暂时让给其他虚拟机期间(CPU 窃取),游戏服务器会停住。

起因 同一宿主机上的其他虚拟机大量占用 CPU → 结果 本虚拟机每次失去几 ms 到几十 ms 的运行机会 → 画面表现 tick 耗时莫名飙升,表现为一卡一卡、卡住

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 运维团队·系统运维 · 配合 外部·外部
运维团队要做的事
监控 steal 指标(top、vmstat 的 st);使用独占核心或专用宿主机;避开 CPU 积分耗尽就降速的突发型实例;steal 持续偏高的实例先停止再启动,迁到其他宿主机。
外部要做的事
向云厂商报告 steal 持续偏高的宿主机。
监控图上
偶发随机尖峰 · %steal、服务器 tick 耗时
查看位置
把 mpstat -P ALL 1 的 %steal 和服务器 tick 耗时放在同一时间轴上看
确认依据
tick 冲高的时刻 %steal 也一起冲高,停止再启动、迁到其他宿主机后下降
排除依据
%steal 接近 0 而 tick 冲高,看游戏服务器内部的原因(“服务器 GC 全局停顿”“锁竞争”)。容器环境看“容器 CPU 限流(CFS 配额)”
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

容器 CPU 限流(CFS 配额) Container CPU throttling (CFS quota)

ID so-cpu-quota · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

给容器设置 CPU 上限后,一旦在规定周期(通常为 100 ms)内用完配额,周期剩下的时间里就会被强制停住(限流)。

起因 在 Kubernetes 等环境中给游戏服务器容器设置了 CPU 上限(limit) → 结果 tick 计算集中的时刻用完配额,停住几十 ms,直到下一个周期 → 画面表现 平均 CPU 不高,tick 却周期性冲高,表现为一卡一卡、慢动作

症状
一卡一卡, 慢动作
因素
停顿, 抖动
谁会遇到
全服
何时出现
人多的时候, 偶尔随机
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
让工作线程数与 CPU 上限匹配(避免运行时按宿主机的全部核心数创建线程)。
运维团队要做的事
给足 CPU 上限或去掉上限,分配独占核心;监控被限流的次数(nr_throttled)。
数值参考
上限为 2 核的服务器上有 8 个线程同时工作时,100 ms 周期的配额 25 ms 就用完了,剩下 75 ms 都在停住。
监控图上
随人数/负载上升 · 限流次数(nr_throttled)、服务器 tick 耗时
查看位置
在容器 cgroup 的 cpu.stat 中看 nr_throttled、throttled_usec(cgroup v1 为 nr_throttled、throttled_time)的增量,与服务器 tick 耗时一起看
确认依据
平均 CPU 使用率低于上限,nr_throttled、throttled_usec 却持续增加,且与 tick 冲高的时刻重合
排除依据
nr_throttled 不增加则不是这个原因。虚拟机本身被挤占,看“CPU 窃取时间(虚拟机)”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

服务器电源管理(C-state/调频)导致延迟跳变 CPU power management latency (C-states, frequency scaling)

ID so-cstate · 主责 运维团队·系统运维

空闲的 CPU 核心为了省电会进入深度省电状态(C-state),频率也会降低。数据包或定时器到来时,唤醒和升频都需要时间,处理小数据包时就会多出一段延迟。

起因 操作系统的调频策略(governor)或 BIOS 电源设置允许深度 C-state 和低频率 → 结果 空闲核心每次从深度省电状态唤醒都要多花最多几百 µs;频率被压在低位时,tick 计算本身也会变慢 → 画面表现 通常很难察觉,但服务器间调用多时会累积,出现空闲时反而响应更慢的操作延迟。频率被压在低位时,人一多 tick 就会滞后,表现为慢动作

症状
操作延迟, 慢动作
因素
延迟, 抖动, 停顿
谁会遇到
全服
何时出现
一直, 偶尔随机
负责方
主责 运维团队·系统运维
运维团队要做的事
BIOS 电源设置调到性能优先;操作系统 governor 设为 performance(cpufreq 的 scaling_governor);延迟敏感的服务器限制深度 C-state(tuned latency-performance 配置文件、PM QoS 的 /dev/cpu_dma_latency、内核参数 intel_idle.max_cstate);调整后对比同一数据中心内的往返时间、tick 耗时抖动和耗电。
数值参考
按 Linux 6.12 intel_idle 驱动中的表,Intel 服务器 CPU 浅层的 C1 唤醒需要 1~2 µs,深层的 C6 需要 133 µs(Skylake-SP)~290 µs(Sapphire Rapids)。单次很小,但一个请求要经过多台服务器时就会相应累积。内核预计的空闲时间越长,选的状态就越深,所以在数据包稀稀拉拉的空闲服务器上更常出现。通用 cpufreq 的 powersave governor 会固定在允许范围内的最低频率(intel_pstate 中同名的算法则按负载调节)。
监控图上
一直偏高 · 同一数据中心内的往返时间、核心频率
查看位置
用 cpupower monitor 看各核心停留在各 C-state 的比例和实际频率,确认 /sys/devices/system/cpu/cpu0/cpuidle/ 下每个 state 的 name、latency(唤醒所需的 µs)、usage,cpufreq 的 scaling_governor,以及 tuned-adm active 显示的当前配置文件
确认依据
核心空闲时长时间停留在最深的 C-state,或频率被压在最低值附近;改为 performance governor、浅层 C-state 后,小请求的往返时间和抖动下降
排除依据
调整后差别在几十 µs 以内,这个原因可以忽略。以 ms 为单位冲高,看“CPU 窃取时间(虚拟机)”或其他层
确认手段
运维工具即可确认(无需游戏代码)
深入了解
裸金属 IDC 服务器要同时检查 BIOS(固件)的电源设置和操作系统设置。云上只有部分实例类型允许操作系统修改 C-state 和频率;AWS 的默认设置偏向最高性能,大多保持默认即可。Red Hat 系的 tuned latency-performance 配置文件会把 governor 设为 performance,并通过 PM QoS 只使用浅层 C-state。关闭省电会增加耗电,只对延迟敏感的服务器使用。
出处 7 条

OOM Killer Out-of-memory killer

ID so-oom · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

Linux 在内存耗尽时,会挑出占用内存最多的进程强制杀掉,通常就是游戏服务器。

起因 泄漏或暴增导致内存耗尽,或者达到容器内存上限 → 结果 内核强制终止游戏服务器进程 → 画面表现 这台服务器上的所有人同时掉线,最近的进度可能回档

症状
掉线, 吞操作/回档
因素
停顿
谁会遇到
全服
何时出现
开得越久越严重, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
修复泄漏;设定内存使用上限,并做好接近上限时先存盘再正常关闭的流程。
运维团队要做的事
设置内存告警;按实际用量设置容器内存上限;调整优先杀掉的顺序(oom_score_adj)。
数值参考
内核日志(dmesg)里会留下“Out of memory: Killed process”,在 Kubernetes 中显示为 OOMKilled。Windows 没有 OOM Killer,服务器多半是因内存分配失败而报错退出。
监控图上
连接成批断开 · 连接数、内存使用量
查看位置
把 dmesg 中的“Out of memory: Killed process”记录、Kubernetes Pod 状态里的 OOMKilled、cgroup v2 下 memory.events 中 oom_kill 的增加,与掉线时刻对照
确认依据
连接集中断开的时刻有游戏服务器进程被杀的记录,且在那之前内存使用量一路涨到上限
排除依据
没有 OOM 记录而进程死了,看“服务器崩溃”的崩溃日志和 core dump
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

内存回收/规整导致的停顿 Memory compaction / reclaim stalls (THP)

ID so-reclaim · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

操作系统为了凑出大页(huge page)而做内存规整,或为补充空闲内存而回收内存时,进程会停住。

起因 空闲内存减少,或大页功能(THP)触发内存规整 → 结果 申请内存的线程要一直等到回收或规整结束 → 画面表现 不规则的服务器停顿(几 ms 到几百 ms)

症状
卡住, 一卡一卡
因素
停顿
谁会遇到
全服
何时出现
开得越久越严重, 偶尔随机
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
减少运行期间的大块内存分配(启动时预先申请好,之后复用)。
运维团队要做的事
把大页(THP)设为只在需要的地方使用(madvise);提高空闲内存基准(vm.min_free_kbytes 等)。
监控图上
偶发随机尖峰 · 服务器 tick 耗时、内存 PSI
查看位置
把 /proc/pressure/memory 的 some、full(因等待内存而停住的时间比例)和 /proc/vmstat 中 compact_stall 的增量与服务器 tick 耗时一起看,并确认 /sys/kernel/mm/transparent_hugepage/defrag 的设置
确认依据
tick 冲高的时刻内存 PSI 上升,compact_stall 增加。defrag 为 always
排除依据
PSI 和 compact_stall 没有变化则不是这个原因。swap 用量增加,看“swap”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

系统时钟跳变(NTP step) Wall-clock jump (NTP step)

ID so-timejump · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

服务器时钟一次性向前或向后调整几秒时,依赖系统时钟的定时器会一下子集中触发或停住。

起因 时间同步一次性大幅调整时钟 → 结果 定时器集中触发或停住,超时判定出错 → 画面表现 buff/冷却异常、集体掉线、快进

症状
快进, 掉线, 吞操作/回档
因素
停顿
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
经过时间、超时、冷却时间都用不会跳变或倒退的单调时钟(monotonic clock)计算,墙上时钟(wall clock)只用于显示和记录。
运维团队要做的事
时钟采用缓慢调整(chrony 的 makestep 只在刚启动时生效);监控时间同步状态(时钟偏差)。
数值参考
ntpd 在偏差超过 0.128 秒时一次性校准,偏差更小时缓慢校准,消除 1 秒偏差要 30 分钟出头。现在常用的 chrony 在推荐设置(makestep)下,只在启动后的前几次一次性校准,之后都缓慢校准。虚拟机短暂停住后恢复时,时钟也会跳变。
监控图上
偶发随机尖峰 · 定时器触发次数、断开次数,时钟调整记录
查看位置
在时间同步服务的日志里找一次性大幅调整时钟的记录,与出现异常的时刻对照。chrony 在调整幅度超过 logchange 设定值(默认 1 秒)时会写入 syslog
确认依据
出现 buff/冷却异常、集体掉线、快进的时刻有时钟调整记录,且调整幅度与异常的大小相近
排除依据
没有时钟调整记录则不是这个原因。虚拟机还要看停住后恢复的情况(“云宿主机维护/热迁移”)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

定时任务 Cron jobs (log rotation, backup, scans)

ID so-cron · 主责 运维团队·系统运维

每天在同一时刻运行的日志压缩、备份、安全扫描会占用 CPU 和磁盘。

起因 操作系统任务在固定时刻运行 → 结果 与游戏服务器争用 CPU 和磁盘 → 画面表现 每天凌晨 4 点这样的固定时刻出现一卡一卡、慢动作

症状
一卡一卡, 慢动作
因素
停顿
谁会遇到
全服
何时出现
固定周期
负责方
主责 运维团队·系统运维
运维团队要做的事
错开任务时间;降低优先级(nice、ionice);与游戏服务器分开(放到单独的服务器上运行)。
监控图上
周期性尖峰 · CPU 使用率、磁盘队列、服务器 tick 耗时
查看位置
用 crontab 和 systemctl list-timers 汇总定时任务的执行时刻,在 tick 冲高的时刻用 pidstat -u -d 看哪个进程在用 CPU 和磁盘
确认依据
tick 每天(或每小时)在同一时刻冲高,且那个时刻定时任务进程占用 CPU 和磁盘
排除依据
冲高时刻与每天的固定时刻对不上,则不是这个原因。每隔几秒或几分钟冲高,看“服务器 GC 全局停顿”“定时器集中触发”
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

OS/内核/驱动/固件更新后的性能变化 Performance regression after OS / kernel / driver / firmware update

ID so-os-update · 主责 运维团队·系统运维

游戏代码没变,服务器的 OS、内核、驱动、固件更新之后却开始变慢。更新可能会改变默认值、调度器、CPU 漏洞缓解措施(mitigations)和驱动行为。

起因 定期安全补丁或新的服务器镜像更换了内核、驱动、固件 → 结果 默认值、调度器变了,或新的漏洞缓解被开启,同样的工作要花更多 CPU 时间,线程获得 CPU 的顺序也变了 → 画面表现 原本正常的服务器从更新那天起一直慢一点,表现为操作延迟;人一多就一卡一卡、慢动作

症状
操作延迟, 一卡一卡, 慢动作
因素
延迟, 停顿, 抖动
谁会遇到
全服
何时出现
一直, 人多的时候
负责方
主责 运维团队·系统运维
运维团队要做的事
更新先在部分服务器上应用,与旧版本对比 tick 耗时、延迟、CPU 使用率后再扩大范围;与游戏版本更新错开日期发布;记录更新前后的内核、驱动、固件版本和主要 sysctl 值;出问题时用旧内核启动确认;关闭缓解的设置(mitigations=off)要权衡安全风险后再决定。
数值参考
内核版本一变,默认行为也会变。例如 Linux 从 6.6 开始把调度器从 CFS 迁移到 EEVDF,连接队列上限(somaxconn)的默认值也从 5.4 起由 128 改为 4,096。CPU 漏洞缓解会增加额外工作,比如从内核返回用户程序时(每次系统调用结束时)以及上下文切换、虚拟机切换时清空 CPU 内部缓冲区,所以每个包都要发起系统调用的网络服务器受影响更大。有些漏洞要彻底防住必须关闭 SMT(把一个核心当两个线程用的功能),而关闭 SMT 后,视工作负载不同,性能可能大幅下降。内核参数 mitigations=off 会关闭所有这些缓解措施、找回性能,但也会暴露在漏洞之下。
监控图上
某一时刻起台阶式上升 · 服务器 tick 耗时、CPU 使用率、相同负载下的延迟
查看位置
把包管理器的更新记录和重启时刻、uname -r 显示的内核版本、ethtool -i 显示的网卡驱动信息,与延迟上升的时刻对照。在相同负载下用 mpstat、pidstat 对比已更新和未更新的服务器,并对比 /sys/devices/system/cpu/vulnerabilities/ 中的缓解状态
确认依据
延迟、CPU 使用率从更新后重启的时刻起台阶式上升并保持,相同负载下只有已更新的服务器偏高。用旧内核、旧驱动启动后恢复
排除依据
已更新和未更新的服务器在相同负载下同样慢,则不是这个原因。同一天也发布了游戏版本更新,且每个玩家的包数、包大小有变化,看“版本更新导致流量特征变化”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
缓解状态可以在 /sys/devices/system/cpu/vulnerabilities/ 下的文件中确认。默认值(mitigations=auto)在保持 SMT 开启的情况下做缓解;设为 auto,nosmt 时,会在有漏洞的 CPU 上关闭 SMT,升级内核后逻辑核心数可能减半。OS 更新和游戏版本更新放在同一天,就很难分清原因,所以要分开发布。
出处 6 条

服务器 conntrack 表耗尽 conntrack table full

ID so-conntrack · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发

Linux 防火墙会把所有连接记在连接跟踪(conntrack)表里,这张表达到上限后,新的数据包会被丢弃。

起因 连接激增、反复建立短连接,使连接记录增多 → 结果 表满后丢弃新连接和部分数据包 → 画面表现 连不上,莫名丢包导致瞬移

症状
连不上/无限加载, 瞬移
因素
丢包
谁会遇到
全服
何时出现
刚登录/维护结束后, 人多的时候
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发
研发团队要做的事
服务器:减少短连接(服务器间调用复用连接)。客户端:连接失败或断开后,逐步拉长重试间隔并随机打散。
运维团队要做的事
调大表的大小(nf_conntrack_max);游戏端口不做跟踪(raw 表的 NOTRACK);设置用量告警。
数值参考
默认上限视服务器内存而定,约为 6 万~26 万条。溢出时内核日志会打出“nf_conntrack: table full, dropping packet”。
监控图上
触顶后走平 · conntrack 条目数(nf_conntrack_count)
查看位置
把 sysctl 的 net.netfilter.nf_conntrack_count(当前条目数)和 nf_conntrack_max 画在同一张图上,在 dmesg 中找“nf_conntrack: table full, dropping packet”
确认依据
nf_conntrack_count 在 max 处走平,从那一刻起内核日志出现 table full
排除依据
条目数离 max 还很远则不是这个原因。AWS 实例本身的连接跟踪上限,看“超出云 PPS 上限”中的 conntrack_allowance_exceeded
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

服务器间连接的临时端口耗尽 Ephemeral port exhaustion (TIME_WAIT)

ID so-ports · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

游戏服务器频繁地与 DB 或其他服务器建立短连接又断开时,断开的连接会在一段时间内占着端口,导致新连接打不开。

起因 每个请求都新建连接再关闭 → 结果 先关闭的一方要占着端口约 60 秒(Linux,TIME_WAIT 状态),可用端口耗尽 → 画面表现 内部请求失败,导致存盘失败、功能出错

症状
吞操作/回档, 连不上/无限加载
因素
丢包
谁会遇到
全服, 仅特定功能
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
复用连接(连接池),不要每个请求都新建连接再关闭。
运维团队要做的事
扩大端口范围(ip_local_port_range);评估出站连接复用 TIME_WAIT(Linux tcp_tw_reuse);监控 TIME_WAIT 数量。
数值参考
Linux 默认端口范围(32768~60999)约 2.8 万个。向同一个目标地址每秒新建连接超过 470 次就会耗尽。Windows 默认端口约 1.6 万个(49152~65535),TIME_WAIT 也更长,耗尽得更快。
监控图上
触顶后走平 · TIME_WAIT socket 数、内部连接失败次数
查看位置
用 ss -tan state time-wait 按目标地址统计 TIME_WAIT socket,在游戏服务器日志里找 connect 失败(EADDRNOTAVAIL)
确认依据
发往同一目标(DB 等)的 TIME_WAIT 在临时端口范围(默认约 2.8 万个)附近走平,connect 以 EADDRNOTAVAIL 失败
排除依据
TIME_WAIT 很少,却只有出站到外部的连接失败,看“云 NAT 网关连接/端口上限”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
Linux 的 TIME_WAIT 60 秒是内核里写死的值。调小名字相近的 tcp_fin_timeout,TIME_WAIT 也不会变短。
出处 5 条

L8 Socket 与协议

14 个原因 · 完整版章节

TCP 队头阻塞 Head-of-line blocking

ID sk-hol · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

TCP 为了保证顺序,在重新收到丢失的那个包之前,不会把后面已经到达的数据包交给游戏。

起因 一个数据包丢失 → 结果 后面的包已经到了,却在接收缓冲区里等着 → 画面表现 先停顿,然后一下子全部放出来,表现为快进

症状
卡住, 快进
因素
丢包, 停顿
谁会遇到
只有我
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:实时位置用 UDP,只有必不可少的内容走可靠传输,拆成多条流。客户端:网络处理改成与服务器相同的方式(UDP、分通道)。
数值参考
丢一个包至少停顿往返时间 + α,连重传也丢了则会停顿几百 ms 到几秒。
监控图上
断流后集中到达 · 每个连接的接收量、重传次数
查看位置
用服务器侧抓包(tcpdump、Wireshark)查看该玩家连接中的重传包及其前后的空档;全服看 nstat -az 中 TcpRetransSegs 的增量
确认依据
停顿区间以一个包的重传开始,重传包到达后,积压的数据被一次性处理(接收量先是 0,然后集中涌入)
排除依据
用 UDP 通信的游戏不适用。没有重传却停顿,看服务器 tick(“Tick 超出预算”)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

TCP RTO 与指数退避 RTO and exponential backoff

ID sk-rto · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

重传每失败一次,等待时间就翻一倍,短暂的线路中断会变成长时间停顿。

起因 线路短暂中断,重传也接连失败 → 结果 到下一次尝试的间隔按 0.3 → 0.6 → 1.2 → 2.4 秒这样翻倍(以 ping 100 ms 为例) → 画面表现 线路只断了 1 秒,游戏却卡住 2 秒以上。断得更久,最终就会掉线

症状
卡住, 掉线
因素
丢包, 停顿
谁会遇到
只有我
何时出现
偶尔随机, 移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:响应心跳,一段时间收不到就主动清理连接(用 TCP_USER_TIMEOUT 让连接更早放弃);凭会话令牌接续会话;使用可靠 UDP。客户端:以较短间隔发送心跳,响应一断就尽快重连,不要等 TCP 重传。
数值参考
Linux 的 RTO(重传等待时间)最小为“ping + 200 ms”,建立连接时从 1 秒起步。按默认设置(tcp_retries2=15),即使重传一直失败,也要约 15 分钟后才放弃连接。
监控图上
断流后集中到达 · 每个连接的 RTO、backoff,RTO 超时次数
查看位置
对停顿的连接用 ss -ti 看 rto(重传等待 ms)和 backoff(连续超时次数),全服看 nstat -az 中 TcpExtTCPTimeouts(重传定时器超时次数)的增量
确认依据
停顿连接的 backoff 在 1 以上,rto 已涨到秒级,且那个时刻 TCPTimeouts 增加
排除依据
重传都以快速重传结束、没有 RTO 超时,停顿就很短。这时看“TCP 队头阻塞”
确认手段
运维工具即可确认(无需游戏代码)
出处 6 条

Nagle 算法 + 延迟 ACK Nagle + delayed ACK (TCP_NODELAY off)

ID sk-nagle · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

把小包攒起来再发的 Nagle 算法,和推迟发送 ACK 的延迟 ACK 叠加在一起,消息每拆开写一次,就会延迟 40~200 ms。

起因 没开 TCP_NODELAY,却把小消息拆成多次写入 → 结果 发送方在等 ACK,接收方却推迟发送 ACK → 画面表现 线路 ping 很低,所有操作却都固定地慢半拍,表现为操作延迟

症状
操作延迟
因素
延迟
谁会遇到
全服, 只有我
何时出现
一直, 做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:开启 TCP_NODELAY;把一个 tick 内的消息攒起来一次写入;关闭接收方延迟 ACK 的办法(Linux 的 TCP_QUICKACK 只能短暂维持,Windows 要逐台 PC 改注册表)游戏无法可靠掌控,不要依赖。客户端:开启 TCP_NODELAY;把一帧内的消息攒起来一次写入。
数值参考
延迟 ACK 在 Linux 上通常为 40 ms(视情况最长 200 ms)。Windows 旧版本为 200 ms,新版本为 40 ms(Windows Server 2019 默认模板为 40 ms)。延迟 ACK 由接收方的操作系统决定,所以服务器开着 Nagle 拆分发送消息时,视接收端 PC 不同,每次可能延迟 40~200 ms。
监控图上
一直偏高 · 操作响应时间(游戏内 RTT)
查看位置
在服务器侧抓包(tcpdump、Wireshark)中查看请求与响应之间的间隔,确认服务器、客户端代码是否开启了 TCP_NODELAY
确认依据
线路 ping 很低,小包之间却反复出现 40 ms(Windows 旧版本为 200 ms)左右的空档,且空档在对方的 ACK 到达后立即结束。开启 TCP_NODELAY 后消失
排除依据
响应间隔与线路 ping 相近则不是这个原因。游戏服务器生成响应慢,看服务器处理(“消息队列积压”)
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

慢客户端导致的阻塞发送 Blocking send on a full socket

ID sk-block-send · 主责 研发团队·服务器开发

某个线路慢的玩家发送缓冲区已满时,如果用阻塞方式(缓冲区腾出空间之前调用不返回的发送)发送,服务器线程就会一直等这一个人。

起因 慢客户端的发送缓冲区已满 → 结果 阻塞发送,服务器线程一直等到缓冲区腾出空间 → 画面表现 这个线程负责的所有人都卡住、慢动作

症状
卡住, 慢动作
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
偶尔随机, 人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
非阻塞发送;给每个客户端的发送队列设上限;丢弃过时的更新。
监控图上
偶发随机尖峰 · 服务器 tick 耗时、每个连接的 Send-Q
查看位置
用 ss -tn 找出 Send-Q(未收到 ACK 或尚未发出的字节)塞满发送缓冲区的连接,在 tick 冲高的瞬间查看游戏服务器的线程 dump(堆栈),看有没有线程停在 send 调用上
确认依据
有 Send-Q 塞满的慢连接时,负责该连接的线程停在 send 上,而且只有同一线程负责的人一起卡住
排除依据
停住的线程在 send 之外(锁、DB 调用)等待,看“锁竞争”“游戏线程中的同步调用”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

慢客户端(slow consumer)处理策略 Slow-consumer policy

ID sk-slow-client · 主责 研发团队·服务器开发

对待发送数据不断堆积的客户端,服务器会丢弃过时的更新或断开连接。

起因 客户端线路跟不上服务器发送的量 → 结果 服务器丢弃过时的更新,超过上限时断开连接 → 画面表现 只有这个人瞬移或掉线

症状
瞬移, 掉线
因素
丢包
谁会遇到
只有我
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
减少发送量(按距离设定更新频率);降低质量继续发送;减少堆在内核里的数据量(Linux TCP_NOTSENT_LOWAT)。
数值参考
发送缓冲区为 256 KB 时,在 30 KB/s 的线路上会积压 8 秒以上的数据。Linux 还可能把这个缓冲区自动扩大到几 MB。
监控图上
仅部分偏高 · 每个连接的 Send-Q、每个客户端丢弃的更新数
查看位置
查看游戏服务器记录的每个客户端的发送队列长度、丢弃的更新数、断开原因,并在服务器上用 ss -tni 一起确认该连接的 Send-Q 和 cwnd
确认依据
只有瞬移或掉线的人的连接 Send-Q 一直是满的,游戏日志里记录了这个人的更新被丢弃,或因发送队列超限而断开
排除依据
Send-Q 是空的却瞬移,就不是服务器发送侧的问题。看这个人的线路丢包(“无线链路丢包”)或画面插值
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

keepalive 默认 2 小时 TCP keepalive defaults

ID sk-keepalive · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

对方没发关闭信号就消失时,TCP 要过很久才能发现。keepalive(确认空闲连接是否还活着的 TCP 功能)默认关闭,开了也要空闲 2 小时才开始确认。

起因 客户端因断电、线路中断,没发关闭信号就消失了 → 结果 服务器认为连接还活着(keepalive 默认 7,200 秒。如果有正在发送的数据,约 15 分钟后才放弃重传) → 画面表现 留下幽灵角色,重新登录时报“已在线”错误

症状
连不上/无限加载, 隐身/幽灵实体
因素
丢包
谁会遇到
只有我
何时出现
挂机一段时间后, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
研发团队要做的事
服务器:响应心跳,一段时间收不到就主动清理连接(调整 TCP_KEEPIDLE、TCP_USER_TIMEOUT);重新登录时凭会话令牌替换旧会话并接续。客户端:以几秒到几十秒的间隔(不超过最短空闲超时的一半)发送游戏层心跳;断开后自动重连。
运维团队要做的事
为代码没有单独设置的 socket 调低内核默认值(tcp_keepalive_time 等)(只对开启了 SO_KEEPALIVE 的 socket 生效)。
数值参考
Linux 默认在空闲 7,200 秒后开始确认,以 75 秒间隔发送 9 次探测,始终没有响应就断开连接,合计约 2 小时 11 分钟。Windows 默认也要空闲 2 小时才开始确认。
监控图上
仅部分偏高 · 每个连接距最后一次接收的时长
查看位置
用 ss -tnoi 看每个连接的 lastrcv(距最后一次接收的 ms)和 keepalive 定时器(timer:(keepalive,…)),与游戏服务器的“已在线”拒绝记录对照
确认依据
存在 lastrcv 为几分钟到几小时的 ESTABLISHED 连接,且该账号重新登录时因“已在线”被拒
排除依据
没有长时间静默的连接却出现“已在线”,看游戏服务器的会话清理代码
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

UDP 数据包的 IP 分片 IP fragmentation of large UDP

ID sk-fragment · 主责 研发团队·服务器开发

超过 MTU(一次能发送的大小)的 UDP 包会在 IP 层分片,只要丢一个分片,整个包就被丢弃。

起因 人多处的快照超过 1,500 字节 → 结果 拆成多个分片发送,丢了任何一个就整个丢弃 → 画面表现 包越大,丢包率就高出好几倍。只在人多的地方瞬移

症状
瞬移
因素
丢包
谁会遇到
特定地点/分线, 特定地区/运营商
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
自己把包拆到 1,200 字节以下;只发变化的部分。
数值参考
在丢包率 2% 的线路上,拆成 4 个分片的包约有 8% 会丢失。还有防火墙或运营商会直接丢弃分片包,这类用户一个大包也收不到。
监控图上
随人数/负载上升 · IP 分片数(IpFragCreates)、快照大小
查看位置
在服务器上看 nstat -az 中 IpFragCreates(发送时生成的分片数)的增量,接收方看 IpReasmFails(重组失败次数)。用游戏服务器日志或抓包确认 UDP 包大小的分布
确认依据
人多的地方 IpFragCreates 增加,存在超过 1,500 字节的 UDP 包,且那时瞬移的反馈增多
排除依据
IpFragCreates 不增加,说明服务器发送侧没有发生分片
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

可靠 UDP 重传配置 Reliable-UDP tuning (KCP, ENet…)

ID sk-reliable-udp · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

在 UDP 之上自己实现的重传规则太保守,恢复就慢;太激进,又会让线路更堵。

起因 重传间隔、次数、窗口大小的设置与线路不匹配 → 结果 恢复迟缓,或重复发送加剧拥塞 → 画面表现 吞技能、快进,拥塞时卡顿更严重

症状
吞操作/回档, 快进
因素
丢包, 延迟
谁会遇到
只有我
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:按实测往返时间重传;按重要程度分通道。客户端:采用与服务器相同的重传、通道设置。
监控图上
偶发随机尖峰 · 可靠 UDP 重传比例、游戏内 RTT
查看位置
在服务器和客户端记录所用库为每个连接维护的统计(重传次数、估算往返时间、重传等待时间),并与同一玩家的实际线路丢包率(用 mtr 测得的值)对比
确认依据
重传比例比实际线路丢包率高出好几倍,是设置太激进;重传等待时间是实测往返时间的好几倍,是设置太保守
排除依据
重传比例与线路丢包率相近、等待时间与往返时间相符,就不是设置问题。看线路丢包本身
确认手段
需要游戏服务器/客户端的日志和指标
出处 1 条

空闲后的慢启动 Slow start after idle

ID sk-slowstart · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

TCP 空闲一段时间后会重新缩小拥塞窗口(一次能发送的量),突然要发大量数据时只能分几次发。

起因 在空闲了一段时间的连接上发送大量数据(例如进入城镇) → 结果 拥塞窗口已经缩小,要分成多个往返发送 → 画面表现 进入后周围的角色和 NPC 要晚几个往返才出现(服务器越远越明显)

症状
操作延迟, 隐身/幽灵实体
因素
延迟
谁会遇到
只有我
何时出现
移动中/切换地图时, 挂机一段时间后
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
减少进入时的数据量(先发最必要的)。
运维团队要做的事
关闭 tcp_slow_start_after_idle(Linux,服务器全局设置)。
数值参考
空闲时间超过 RTO,拥塞窗口就开始缩小;空闲很久的话会降到约 14 KB(10 个包)。这时 100 KB 无法一次发完,要分 3 个往返发送。
监控图上
仅部分偏高 · 进入后的传输时间(RTT 长的玩家)
查看位置
确认 sysctl net.ipv4.tcp_slow_start_after_idle 的值,在空闲后进入区域的瞬间,看该连接 ss -ti 中的 cwnd(拥塞窗口)是否变小
确认依据
设置为 1(默认),空闲后进入的瞬间 cwnd 降到 10 左右,传输被分成多个往返。RTT 越长的玩家出现得越晚,改为 0 后消失
排除依据
cwnd 保持得很大却仍然出现得晚,看服务器侧的进入处理(“进入密集区域时实体生成激增”)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

拥塞控制导致发送量骤降 Congestion control backoff

ID sk-congestion · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

TCP 把丢包当作拥塞信号,把发送速率降低 30~50%。遇到 Wi-Fi 丢包也会同样降速。

起因 要发送的量大时,Wi-Fi 或线路上出现少量丢包 → 结果 TCP 大幅降低发送速率,然后缓慢恢复(Linux、Windows 默认的 CUBIC 降低 30%) → 画面表现 人多的地方更新积压,表现为快进、操作延迟

症状
快进, 操作延迟
因素
延迟, 停顿
谁会遇到
只有我
何时出现
人多的时候
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
减少发送量(AOI 过滤、只发变化部分);分散发送,避免一次性集中发出。
运维团队要做的事
改用 BBR 等拥塞控制算法(tcp_congestion_control)。
监控图上
缓慢上升后骤降 · 每个连接的拥塞窗口(cwnd)、发送速率
查看位置
对更新积压的玩家连接多次执行 ss -ti,看 cwnd、ssthresh 的变化和拥塞控制算法名(cubic、bbr),同时看 Send-Q 是否在堆积
确认依据
丢包后 cwnd 大幅下降再缓慢回升,反复出现;下降期间 Send-Q 堆积,与快进、操作延迟的反馈时间重合
排除依据
cwnd 充足却仍积压,看接收方窗口(“零窗口(看起来像重传的停顿)”)或服务器发送侧
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

RST 强制关闭导致最后的数据丢失 SO_LINGER, abrupt RST

ID sk-linger · 主责 研发团队·服务器开发

服务器仓促断开连接时,最后发出的提示或存盘完成信号会丢失。

起因 服务器以强制关闭(RST)的方式关闭连接。SO_LINGER 设为 0 秒,或者没读完收到的数据就关闭,都会这样 → 结果 还在发送中的踢人原因和最后的数据被丢弃 → 画面表现 莫名其妙地出现“因未知错误断开连接”

症状
掉线
因素
丢包
谁会遇到
只有我
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发
研发团队要做的事
发送原因后先只关闭发送方向(shutdown),把收到的数据一直读到对方关闭,然后再关闭;避免 SO_LINGER 0 秒。
监控图上
偶发随机尖峰 · 以 RST 结束的连接数
查看位置
看 nstat -az 中 TcpExtTCPAbortOnData(还有待发数据时以 RST 关闭,SO_LINGER 0 秒)、TcpExtTCPAbortOnClose(还有未读数据时关闭)的增量,在断开瞬间的服务器侧抓包中看发出的是 FIN 还是 RST
确认依据
“因未知错误断开连接”的反馈时间点上服务器发出了 RST,AbortOnData、AbortOnClose 增加
排除依据
服务器以 FIN 正常关闭却看不到原因,看客户端的关闭处理
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

阻塞 I/O 模型 Blocking I/O model

ID sk-blocking-io · 主责 研发团队·服务器开发

等一个 socket 时线程就干不了别的事,这种结构下人越多,整体就越慢。

起因 每个连接都要等待读写的方式 → 结果 一个连接的延迟蔓延到同一线程的其他连接 → 画面表现 同时在线人数越多,整体越慢,表现为慢动作、操作延迟

症状
慢动作, 操作延迟
因素
停顿
谁会遇到
全服
何时出现
人多的时候, 晚高峰
负责方
主责 研发团队·服务器开发
研发团队要做的事
改为基于 epoll、IOCP、io_uring 的异步 I/O。
监控图上
随人数/负载上升 · 响应时间、线程数
查看位置
用 pidstat -w -t 看游戏服务器的线程数和各线程的自愿上下文切换(cswch/s,等待资源而停住的次数),与随同时在线人数变化的响应时间对比
确认依据
同时在线人数越多,响应时间上升越陡;线程数随连接数一起增加,其中大多数只有自愿切换多、几乎不用 CPU(在等 socket)
排除依据
线程一直在用 CPU、没有等待,是计算过载(“Tick 超出预算”)
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

SO_REUSEPORT 分配不均 SO_REUSEPORT imbalance, stuck worker

ID sk-reuseport · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

多个进程分担同一个端口时,内核会按地址哈希给每个连接定好负责的进程,之后不再改变。负责的进程一旦停住,只有分到它那里的人在等。

起因 网关、登录服务器用 SO_REUSEPORT 起了多个进程 → 结果 即使一个进程因 GC 或过载停住,分到它的新连接和 UDP 包也不会转给其他进程 → 画面表现 只有部分人连不上、卡住。进程数变化的重启期间,部分 UDP 会话会中断

症状
连不上/无限加载, 卡住, 掉线
因素
停顿, 丢包
谁会遇到
只有我, 全服
何时出现
刚登录/维护结束后, 偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
绝不让接收线程停住;实现重启时交接会话的流程。
运维团队要做的事
监控每个进程的连接队列(ss 的 Recv-Q);发布时如果改变进程数,要走会话交接流程。
监控图上
仅部分偏高 · 每个监听 socket 的连接队列(Recv-Q)
查看位置
用 ss -ltnp 看同一端口每个监听 socket 的 Recv-Q(等待 accept 的连接数)和负责进程,对比各进程的处理量
确认依据
同一端口的多个 socket 中只有一个 Recv-Q 持续堆积,且该进程停住或处理量接近 0
排除依据
所有 socket 的 Recv-Q 都均匀堆积,是整体过载(“连接队列(backlog)溢出”)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

Windows UDP socket 的 WSAECONNRESET 错误 WSAECONNRESET on a Windows UDP socket

ID sk-udp-connreset · 主责 研发团队·服务器开发

Windows 服务器向已经离开的客户端发 UDP 时,会收到“端口不可达”(ICMP)通知。这个通知会让下一次接收调用以错误结束;如果服务器代码把这个错误当作 socket 本身坏了来处理,所有使用这个 socket 的人都会受影响。

起因 一直向刚离开的客户端地址发 UDP,收到“端口不可达”(ICMP)通知 → 结果 Windows 让接下来的接收调用以 WSAECONNRESET(10054)错误结束,服务器代码停止接收或关闭 socket → 画面表现 使用这个 socket 的所有人一起卡住、掉线

症状
掉线, 卡住
因素
丢包, 停顿
谁会遇到
全服
何时出现
偶尔随机, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发
研发团队要做的事
用 WSAIoctl 关闭 SIO_UDP_CONNRESET,不再接收这个通知;接收出错时也只记日志、继续接收。
监控图上
连接成批断开 · 连接数、接收错误日志
查看位置
在游戏服务器日志里找 UDP 接收错误码(WSAECONNRESET、10054)以及接收循环停止或关闭 socket 的记录,用服务器侧抓包确认之前是否收到了 ICMP Port Unreachable
确认依据
连接集中断开前记录到 WSAECONNRESET 接收错误,在那之前刚离开的客户端地址发来了 ICMP Port Unreachable
排除依据
Linux 服务器,或代码已关闭 SIO_UDP_CONNRESET,则不适用
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

L9 服务器游戏进程

18 个原因 · 完整版章节

Tick 超出预算 Tick overrun

ID sp-tick-overrun · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

一个 tick 内要做的事超出预算时,服务器的 tick 周期会被拉长,整个区域都会变慢或一卡一卡。

起因 一个 tick(例如 50 ms)要处理的工作超出预算 → 结果 本该每秒计算 20 次的游戏状态只算了 8 次 → 画面表现 整个区域慢动作(视服务器设计也可能是一卡一卡),技能反应慢

症状
慢动作, 操作延迟, 一卡一卡
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
人多的时候, 晚高峰
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
削减昂贵的计算;把 tick 拆到多个线程;分散人数(分线);把 tick 处理时间记为指标。
运维团队要做的事
把 tick 耗时和各核心 CPU 使用率加入监控和告警;考虑单核性能(主频)高的 CPU 或实例。
数值参考
20 tick 服务器的预算为 50 ms,30 tick 为 33 ms,60 tick 为 16.7 ms。为了应对人突然涌入,平时只用预算的一半左右、留出余量比较安全。
监控图上
随人数/负载上升 · 服务器 tick 耗时、各场景/分线人数、游戏线程 CPU
查看位置
把服务器记录的 tick 处理时间(p99)、tick 超时次数与各场景/分线人数放在同一张图上看。没有 tick 指标时,用 pidstat -t 1 看单个游戏线程的 CPU 使用率
确认依据
人数集中的时刻 tick 耗时超出预算(20 tick 为 50 ms),期间游戏线程的 CPU 使用率一直贴近 100%
排除依据
tick 超时而游戏线程 CPU 却很低,是等待类原因(GC 停顿、锁、同步调用)。bcc runqlat 显示的运行队列延迟长,说明线程分不到 CPU,属于 CPU 不足或线程过多
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
tick 延迟时的表现因服务器设计而异。每个 tick 把游戏状态推进固定时长(例如 50 ms)的服务器,游戏时间本身会变慢,表现为慢动作。按实际流逝的时间一次性推进的服务器能保住游戏进度的速度,但数据包变稀、每次移动幅度变大,看起来是一卡一卡、瞬移。无论哪种,操作的反应都会变慢。一个游戏线程负责整台服务器时,整台服务器都会变慢;按区域分线程时,只有那个区域变慢。也有像 EVE Online 这样的游戏,在大规模战斗中故意把游戏时间放慢最多 10 倍(Time Dilation),让计算跟上。
真实案例
CCP Games 2014: EVE Online HED-GP 大规模舰队战中的服务器过载
出处 5 条

视野(AOI)计算激增(N²) Area-of-interest explosion

ID sp-aoi · 主责 研发团队·服务器开发

所有人两两比较谁能看到谁,人数变成 10 倍时,计算量就会变成 100 倍。

起因 所有角色两两比较距离,或者即使按网格划分,一个格子附近也挤了几百人 → 结果 100 人约比较 1 万次,1,000 人约比较 100 万次 → 画面表现 在世界 BOSS、攻城战这类人群聚集的地方,tick 耗时暴增,表现为慢动作、一卡一卡

症状
慢动作, 一卡一卡
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
按网格、区块划分,只比较附近的对象;远处的对象降低更新频率;给一个人能看到的人数设上限。
数值参考
把距离比较和可见/不可见列表更新合计按每一对玩家 0.1 µs(千万分之一秒)算,1,000 人(约 100 万对)时一个 tick 要 100 ms,是 20 tick 预算(50 ms)的两倍。
监控图上
随人数/负载上升 · 服务器 tick 耗时、聚集在一处的人数
查看位置
把各场景/分线人数和 tick 耗时放在同一张图上,并单独测量 tick 中视野计算所用的时间。没有单独测量时,用 perf top -p 看游戏进程中各函数的 CPU 占比
确认依据
聚集在一处的人数翻倍时,tick 耗时增至接近 4 倍,视野、距离计算函数占了大部分 CPU 时间
排除依据
tick 耗时与人数成正比增加,或者发送、序列化函数占比大,看“广播激增”或“序列化/压缩开销”
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

广播激增 Broadcast fan-out (N×N)

ID sp-broadcast · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

把一个人的移动发给所有能看到他的人时,要发的更新数量是聚集人数的平方。

起因 把一个人的变化发给所有能看到他的人 → 结果 1,000 人互相能看到时,每个 tick 有 100 万条更新 → 画面表现 发送队列和带宽饱和,产生延迟和丢包(操作延迟、快进、瞬移)

症状
操作延迟, 瞬移, 快进
因素
延迟, 丢包
谁会遇到
特定地点/分线, 全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
按距离、重要程度降低更新频率(近处的敌人每个 tick 更新,远处的人每秒几次);给发往每个人的数据量设上限,从重要的开始填;把多条更新合并到一个包里;限制显示人数。
运维团队要做的事
把每台服务器的发送带宽、每秒包数与网卡、实例的网络上限对比并告警;大型活动前确认余量。
数值参考
1,000 人 × 1,000 人 × 20 tick = 每秒 2,000 万条。每条 40 字节的话,全服约 6.4 Gbps,每个接收者约 6.4 Mbps。把可见人数限制在 150 人,全服约 1 Gbps,每人约 1 Mbps。
监控图上
随人数/负载上升 · 服务器发送包数/字节数、聚集在一处的人数
查看位置
把 sar -n DEV 1 的 txpck/s、txkB/s(服务器网卡每秒发送的包数、KB)和人数曲线一起看。云实例看 ethtool -S 中的超限计数器(AWS ENA 为 bw_out_allowance_exceeded、pps_allowance_exceeded)
确认依据
聚集人数增加时,发送包数、字节数比人数增长得更陡(接近平方),从触及上限的时刻起,超限计数器或发送丢弃增加
排除依据
发送量不变而只有 tick 耗时增加,看视野计算或游戏逻辑
确认手段
运维工具即可确认(无需游戏代码)
真实案例
CCP Games 2014: EVE Online HED-GP 大规模舰队战中的服务器过载
出处 5 条

单线程区域过载(热点) Single-threaded hot zone

ID sp-hotzone · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

每个区域由一个线程负责的架构下,人一旦聚到一处,只有那一个核心会跑到 100%。

起因 一个线程负责一个区域(分线) → 结果 人聚到一处时只有那个核心饱和,其余核心还有余量 → 画面表现 只有那个区域卡顿,其他区域正常

症状
慢动作, 操作延迟
因素
停顿
谁会遇到
特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
分散到多条分线;区域内部并行化;人数上限。
运维团队要做的事
把各核心 CPU 使用率加入监控和告警(整台服务器的平均值会把单个核心的饱和掩盖掉)。
数值参考
16 核服务器上即使有一个核心 100%,整台服务器的 CPU 使用率看起来也只有约 6%。要看各核心的使用率才能发现。
监控图上
随人数/负载上升 · 各核心 CPU 使用率、各线程 CPU
查看位置
用 mpstat -P ALL 1 看各核心使用率,用 pidstat -t 1 看游戏进程各线程的 CPU,与最忙线程所负责场景的人数对比
确认依据
整台服务器 CPU 不高,只有一个线程(一个核心)贴近 100%,且那时这个线程负责的场景里人很多
排除依据
多个核心都均匀偏高,是整台服务器过载。只有一个核心的 %soft(接收处理)高,看“网卡中断集中在单个核心”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
“其他区域正常”只适用于每个区域单独跑 tick 的情况。如果多个区域线程每个 tick 都要互相等待、再一起进入下一个 tick,最忙的那个区域就会拖慢整台服务器的 tick。
真实案例
CCP Games 2014: EVE Online HED-GP 大规模舰队战中的服务器过载
出处 3 条

锁竞争 Lock contention

ID sp-lock · 主责 研发团队·服务器开发

多个线程为了写同一份数据而等同一把锁时,线程再多,一次也只有一个在执行。

起因 拍卖行、公会仓库这类公共数据被多个线程同时使用 → 结果 拿到锁的线程结束之前,其余线程都在等 → 画面表现 只有特定功能慢,严重时整个 tick 延迟

症状
操作延迟, 卡住
因素
停顿
谁会遇到
仅特定功能, 全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
把锁拆细;减少锁内的工作;采用基于消息的架构(每份数据指定一个负责线程,其他线程只发消息请求)。
数值参考
锁内工作占全部工作的 20% 时,线程再多,吞吐量最多也只有单线程的 5 倍;占 40% 时停在 2.5 倍。
监控图上
随人数/负载上升 · 请求处理时间、各线程 CPU 和上下文切换
查看位置
用 pidstat -w -t 1 看各线程的自愿上下文切换(cswch/s,等待资源而停住的次数),用 bcc offcputime -p 看线程离开 CPU 后在哪里等待(按调用栈统计等待时间)。.NET 看 dotnet-counters 的锁竞争次数(.NET 9 及以后为 dotnet.monitor.lock_contentions,8 及以前为 Monitor Lock Contention Count)
确认依据
负载增加时 CPU 使用率依然很低,处理时间却变长,大部分等待时间集中在获取锁的调用栈上,锁竞争次数也一起上升
排除依据
CPU 已经跑满,是计算量问题(“Tick 超出预算”“单线程区域过载(热点)”)。等待的地方是 DB、文件调用,看“游戏线程中的同步调用”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
多个线程一起修改游戏数据的架构才会出现这个问题。每个区域、功能由一个线程负责、只通过消息交互的架构几乎没有锁,但要当心工作集中到一个线程的问题(单线程区域过载)。游戏线程如果在等慢速存盘操作持有的锁,那个 tick 整个都会停住。
真实案例
Roblox 2021: Roblox 73 小时故障:服务发现(Consul)集群的争用问题
出处 6 条

死锁 Deadlock

ID sp-deadlock · 主责 研发团队·服务器开发

两个线程互相等待对方持有的锁,就会永远停住。

起因 线程 A 拿着锁 1 等锁 2,B 拿着锁 2 等锁 1 → 结果 两者都永远停住,相关线程也一个接一个停住 → 画面表现 整台服务器停住,看门狗重启服务器,所有人掉线

症状
卡住, 掉线
因素
停顿
谁会遇到
全服, 仅特定功能
何时出现
偶尔随机, 人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
锁顺序规则;带超时的锁;看门狗,并留下停住瞬间的线程 dump。
监控图上
连接成批断开 · 连接数、服务器发送量
查看位置
停住期间抓取所有线程的调用栈。JVM 用 jstack(会自动查找并标出死锁),.NET 用 dotnet-stack,原生服务器用 gdb 的 thread apply all bt,或者先用 gcore 生成 core 文件,重启后再分析
确认依据
两个以上的线程停在互相等待对方所持锁的调用栈上,期间进程 CPU 使用率接近 0
排除依据
停住期间有一个线程以 100% CPU 在跑,是死循环。线程们都在等 DB 或外部响应,看同步调用或线程池耗尽
确认手段
运维工具即可确认(无需游戏代码)
出处 6 条

游戏线程中的同步调用 Synchronous DB / file I/O on the game loop

ID sp-sync-call · 主责 研发团队·服务器开发

tick 中途等待 DB 响应或文件写入时,等多久,服务器上整个游戏的运行就停多久。

起因 在 tick 内等待 DB 查询或存盘、写日志、调用外部 API → 结果 DB 耗时 100 ms,tick 也停住 100 ms → 画面表现 每当 DB、磁盘变慢,整个野外就顿一下

症状
卡住, 一卡一卡
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
做特定操作时, 偶尔随机
负责方
主责 研发团队·服务器开发
研发团队要做的事
慢操作(DB 查询和存盘、写日志、调用外部 API)全部交给异步处理,结果在下一个 tick 应用(只加超时的话,等待期间照样停住)。
数值参考
即使同一数据中心内 DB 往返只要 0.5 ms,一个 tick 调用 100 次也要 50 ms,一下就用光 20 tick 的全部预算。
监控图上
偶发随机尖峰 · 服务器 tick 耗时、DB 查询延迟
查看位置
把 tick 耗时曲线和 DB 查询延迟(slow query log 等)、磁盘延迟放在同一时间轴上看。没有 tick 指标时,用 bcc offcputime -p 看游戏线程在哪里等待
确认依据
tick 冲高的时刻与 DB、文件延迟冲高的时刻重合,游戏线程的等待时间集中在接收 DB 响应、写文件的调用栈上
排除依据
DB、磁盘延迟平稳而 tick 冲高,看 GC 停顿或锁竞争
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

消息队列积压 Mailbox / job queue backlog

ID sp-queue · 主责 研发团队·服务器开发

请求进来的速度超过处理速度、在队列里堆积时,排在后面的请求要过几秒才被处理,或者被丢弃。

起因 请求到达的速度超过处理速度 → 结果 队列越来越长,超过上限就丢弃 → 画面表现 技能、交易反应慢或被吞

症状
操作延迟, 吞操作/回档
因素
延迟, 丢包
谁会遇到
特定地点/分线, 仅特定功能
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
监控队列长度;采用优先丢弃过时请求的策略;并行处理。
监控图上
触顶后走平 · 队列长度、最旧消息的滞留时长,每秒处理数
查看位置
服务器记录的各队列长度、最旧消息的滞留时长、每秒进入数、处理数、丢弃数。没有代码指标时,用 ss(或 netstat)看游戏 socket 的 Recv-Q(内核已收下但进程尚未读取的量)
确认依据
进入数超过处理数期间,处理数停在某个值上不再上升,队列长度、滞留时长和丢弃数持续增加
排除依据
队列很短、滞留时长也短,反应却慢,看线路延迟或 tick 本身的延迟
确认手段
需要游戏服务器/客户端的日志和指标
真实案例
CCP Games 2014: EVE Online HED-GP 大规模舰队战中的服务器过载
出处 4 条

定时器集中触发 Synchronized timers

ID sp-timer-burst · 主责 研发团队·服务器开发

所有怪物刷新、所有 buff 到期、整点奖励都挤在同一个 tick 时,那个 tick 的负载就会高出几十倍。

起因 刷新、到期、奖励、自动存盘的定时器对齐到了同一时刻 → 结果 那一个 tick 的工作量是平时的几十倍 → 画面表现 每到固定时刻就顿一下

症状
卡住, 一卡一卡
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
固定周期
负责方
主责 研发团队·服务器开发
研发团队要做的事
把定时器时刻随机错开一点;分到多个 tick 处理。
监控图上
周期性尖峰 · 服务器 tick 耗时
查看位置
收集 tick 耗时冲高的各个时刻,看间隔(整点、每 5 分钟等)。与在同一时刻运行的刷新、buff 到期、奖励、自动存盘定时器列表对照
确认依据
tick 每次都在同一时刻或以相同间隔冲高,且那个时刻有集中触发的游戏定时任务
排除依据
有周期,但与 GC 日志中的停顿时刻或服务器的 cron、备份时刻重合,看“服务器 GC 全局停顿”“定时任务”
确认手段
需要游戏服务器/客户端的日志和指标
出处 1 条

寻路计算激增 Pathfinding storms

ID sp-pathfinding · 主责 研发团队·服务器开发

几百只怪物同时追着玩家计算路径时,会占用大量 CPU。

起因 拉怪、大规模刷新使大量怪物同时追击 → 结果 每只怪物都要做寻路计算 → 画面表现 只有那片刷怪区出现慢动作

症状
慢动作
因素
停顿
谁会遇到
特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
路径缓存;计算次数上限;分到多个 tick。
监控图上
随人数/负载上升 · 服务器 tick 耗时、各场景怪物数量
查看位置
各场景正在追击玩家的怪物数量和 tick 耗时。没有单独统计时,用 perf top -p 看游戏进程中各函数的 CPU 占比
确认依据
拉怪、大规模刷新时 tick 耗时增加,寻路(路径搜索)函数占了 CPU 时间的很大比重
排除依据
怪物少、只有玩家多时 tick 增加,看视野计算或广播
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

序列化/压缩开销 Serialization / compression cost

ID sp-serialize · 主责 研发团队·服务器开发

把要发送的数据转换成字节并压缩也要消耗 CPU,人多时这部分开销会暴增。

起因 每次更新都要把结构体转换成字节并压缩 → 结果 开销与人数的平方成正比增长 → 画面表现 发送变慢,表现为操作延迟

症状
操作延迟
因素
停顿, 延迟
谁会遇到
特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
一个包做好后复用给多人;采用轻量格式。
监控图上
随人数/负载上升 · 服务器 CPU 使用率、生成数据包的线程的 CPU
查看位置
用 perf top -p 对比人少和人多时,游戏进程 CPU 时间中序列化、压缩、加密函数(包括 zlib、LZ4、OpenSSL 等库函数)的占比
确认依据
人越多,序列化、压缩、加密函数的占比越大,生成数据包的线程的 CPU 先饱和
排除依据
这些函数占比很小,看视野计算或游戏逻辑
确认手段
运维工具即可确认(无需游戏代码)
深入了解
如果连接对数据包加密(TLS、DTLS 等),加密和解密也要消耗 CPU。加密按连接分别处理,所以即使一个包复用给多人,加密开销仍按接收人数计算。AES-GCM 这类对称加密快到单核每秒能处理几 GB,平时占比很小;但速度会因一次加密的单位(记录,record)大小而差别很大,像游戏这样小包多的场景,每字节的开销会变大。每个连接一次的握手中,服务器要用证书私钥签名并计算密钥交换(ECDHE)。单核每秒签名约 1,100 次(RSA 2048)到 1.8 万次(ECDSA P-256),密钥交换约 9,000 次,登录集中时会成为负担。
出处 4 条

服务器崩溃 Server process crash

ID sp-crash · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

服务器进程因未处理的错误而崩溃时,这台服务器上的所有人会同时掉线。

起因 指向不存在对象的错误(空引用)、错误的数据、内存不足等致命错误 → 结果 服务器(或场景)进程终止 → 画面表现 所有人同时掉线,上次存盘之后的进度可能回档

症状
掉线, 吞操作/回档
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
偶尔随机, 做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
分析崩溃 dump,修复原因;频繁存盘。
运维团队要做的事
进程自动重启;搭建收集、保存崩溃 dump 的环境;服务器宕机立即告警。
监控图上
连接成批断开 · 连接数、进程重启次数
查看位置
coredumpctl list 中的 core dump 记录(时间、PID、终止信号),以及服务管理器(systemd)的异常退出、重启记录。Windows 服务器看 WER 留下的 dump 文件
确认依据
连接数瞬间掉到接近 0 的时刻,有游戏服务器进程的异常退出和 core dump
排除依据
进程一直活着却掉线,看网络设备或空闲超时。如果是长时间停住后由看门狗重启的记录,看死循环或死锁
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

线程池耗尽 Thread pool starvation

ID sp-threadpool · 主责 研发团队·服务器开发

处理任务的工作线程全被慢操作占住时,新请求只能干等。

起因 工作线程都在等外部 API、DB 响应,被占住 → 结果 新请求没有线程可分配 → 画面表现 登录、商城等特定功能无限加载

症状
连不上/无限加载, 操作延迟, 卡住
因素
停顿
谁会遇到
仅特定功能, 全服
何时出现
人多的时候, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发
研发团队要做的事
给慢调用设超时;按功能拆分线程池;改为异步。
监控图上
触顶后走平 · 线程池线程数、队列长度,请求处理时间
查看位置
.NET 用 dotnet-counters monitor 看线程池线程数和队列长度(.NET 9 及以后为 dotnet.thread_pool.thread.count、dotnet.thread_pool.queue.length,8 及以前为 ThreadPool Thread Count、ThreadPool Queue Length),用 dotnet-stack 确认工作线程在哪里等待。JVM、原生服务器用线程 dump 确认同样的内容
确认依据
CPU 使用率远低于 100%,线程数却持续缓慢增加或顶在上限,队列堆积,大部分工作线程在等同一个外部调用(DB、HTTP)的响应
排除依据
队列是空的仍然慢,说明被调用方本身慢,看“级联故障”或“依赖外部服务”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
如果接收数据包和游戏逻辑共用同一个工作线程池,几个慢操作占满所有工作线程的那一刻,整台服务器的数据包处理都会停住。
真实案例
Riot Games 2021: League of Legends EUW 5 小时故障:一个辅助 DB 导致全服停摆
出处 5 条

死循环/逻辑失控 Infinite loop / runaway logic

ID sp-infinite-loop · 主责 研发团队·服务器开发

bug 导致一个 tick 无法结束时,服务器会停住,然后被看门狗强制重启。

起因 条件写错导致循环不结束,或递归失控 → 结果 tick 结束不了,服务器停住 → 画面表现 卡住后所有人掉线

症状
卡住, 掉线
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
做特定操作时, 偶尔随机
负责方
主责 研发团队·服务器开发
研发团队要做的事
循环次数上限;看门狗;问题输入的复现测试。
监控图上
连接成批断开 · 连接数、各线程 CPU
查看位置
停住期间用 pidstat -t 1 看各线程的 CPU,用 perf top -t(线程 ID)或 gdb 确认以 100% 运行的线程在哪个函数里打转。已经重启的话,看看门狗超时记录(systemd 的 WatchdogSec、Kubernetes 存活探针失败)
确认依据
服务器停住期间,一个游戏线程一直以 100% CPU 运行,调用栈始终在同一个函数、循环里打转
排除依据
停住期间 CPU 接近 0,看死锁或等待外部响应
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

战斗集中在单一目标(世界 BOSS) Hot entity / combat event fan-out

ID sp-hot-entity · 主责 研发团队·服务器开发

几百人同时打一个 BOSS 时,这一只 BOSS 的计算全压在一处,命中信息还要发给所有看得到的人。

起因 几百人不停地对一个 BOSS 施放技能、buff、debuff → 结果 BOSS 的血量、仇恨列表、debuff 计算全压在一处,每次命中都要把伤害数字、特效包发给所有看得到的人 → 画面表现 技能延迟生效,伤害数字成批跳出,只有 BOSS 周围是慢动作

症状
操作延迟, 快进, 慢动作
因素
停顿, 延迟
谁会遇到
特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
他人的伤害数字、特效合并或省略;限制单个目标身上的 debuff 数量;把命中处理分到多个 tick。
数值参考
800 人每人每秒打 2 次,就是每秒 1,600 次命中。要通知看着的 800 人,就是每秒 128 万条消息。
监控图上
随人数/负载上升 · 服务器 tick 耗时、发送消息数
查看位置
把 BOSS 战期间的 tick 耗时、发送包数与 BOSS 周围人数一起看,条件允许的话再看每个目标每秒的事件(命中、buff、debuff)数
确认依据
BOSS 周围人数增加时 tick 耗时和发送量陡增,这一只 BOSS 每秒的事件数比其他目标多几十倍
排除依据
与 BOSS 无关、只要聚在一处就同样变慢,看视野计算或广播激增
确认手段
需要游戏服务器/客户端的日志和指标
出处 1 条

进入密集区域时实体生成激增 Spawn burst when entering a crowd

ID sp-spawn-burst · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

传送到挤满人的城镇时,服务器要把新进入视野的几百人的外观、装备、状态一次性发过来。

起因 通过传送、登录、换线,突然出现在人多的地方 → 结果 一次性生成并发送几百人的全部信息,自己的电脑也要一次性加载 → 画面表现 刚到达时短暂停顿,角色一个个慢慢出现,操作延迟

症状
卡住, 操作延迟, 快进
因素
停顿, 延迟
谁会遇到
只有我, 特定地点/分线
何时出现
移动中/切换地图时, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:按由近到远的顺序分到多个 tick 发送;缓存外观信息。客户端:在加载画面期间预先接收;把收到的角色分到多帧创建。
数值参考
一个人的外观、装备、buff 信息为 300 字节的话,500 人约 150 KB,相当于平时一个 tick 发送量(几 KB)的几十倍在一瞬间涌出。
监控图上
开服/维护后激增 · 每个连接的发送字节数、客户端帧耗时
查看位置
到达人多的地点后几秒内,通过该连接发送的字节数和包数(服务器日志)与客户端帧耗时(网络状态面板、客户端日志)
确认依据
到达后该连接的发送量飙到平时一个 tick 的几十倍再回落,同一瞬间客户端帧耗时也冲高
排除依据
移动到人少的地方也同样停顿,看“场景切换(服务器间迁移)”或客户端加载
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

实体堆积(未清理的物品/召唤物) Entity / timer buildup over uptime

ID sp-entity-buildup · 主责 研发团队·服务器开发

本该消失的地面物品、召唤物、已结束的定时器没有被清理而不断堆积,服务器开得越久,每个 tick 要做的事就越多。

起因 地面物品、召唤物、过期定时器、空队伍信息没有及时删除 → 结果 每个 tick 要遍历的列表一天比一天长 → 画面表现 维护刚结束时正常,过几天后只有那台服务器或那个区域越来越迟钝

症状
慢动作, 一卡一卡, 操作延迟
因素
停顿
谁会遇到
全服, 特定地点/分线
何时出现
开得越久越严重
负责方
主责 研发团队·服务器开发
研发团队要做的事
把各区域的实体数记为指标观察趋势;给每个实体设寿命和数量上限;定期清理。
数值参考
每个 tick 都把所有实体遍历一遍的服务器,实体数翻倍,这部分的 tick 耗时也会翻倍。
监控图上
缓慢上升后骤降 · 各场景实体数、服务器 tick 耗时
查看位置
以比维护周期更长的时间范围(几周)查看各场景、各服务器的实体数(地面物品、召唤物、定时器)和 tick 耗时
确认依据
维护后从低位起步的实体数和 tick 耗时每天上涨,在维护、重启时骤降,如此反复,期间内存充足
排除依据
tick 不变而只有内存持续上涨,看“内存泄漏”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
和内存泄漏一样,服务器开得越久越糟,区别在于内存充足、只有 tick 耗时在增加。实体数曲线随维护周期呈锯齿状,就是这种情况。
出处 2 条

版本更新导致流量特征变化 Patch changes traffic pattern

ID sp-patch-traffic · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维

新内容、特效、同步项增加了数据包的大小和频率,原本正常的服务器在版本更新后开始碰到 MTU、带宽、包数的上限。

起因 版本更新增加了新技能特效、同步项、物品信息,数据包变大或变频繁 → 结果 大包超过 MTU 被分片,增加的流量碰到带宽、云 PPS 上限、发送缓冲区的限制 → 画面表现 版本更新后,人多的地方开始出现瞬移、吞技能、操作延迟。基础设施什么都没改,丢包却增加了

症状
瞬移, 吞操作/回档, 操作延迟
因素
丢包, 延迟
谁会遇到
全服, 特定地点/分线, 特定地区/运营商
何时出现
人多的时候, 晚高峰, 一直
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维
研发团队要做的事
自己把包拆到 1,200 字节以下;新同步项只发变化部分,并按距离、重要程度降低频率;发布前在测试服上把每个玩家每秒的包数、字节数和最大包大小与上一个版本对比;在流量指标中记录构建版本号。
运维团队要做的事
服务器/OS:在监控图上标出发布时间,对比发布前后每个玩家每秒的包数、字节数和平均包大小;为实例超限计数器设告警;必要时换更大的实例。网络:检查防火墙、负载均衡器、DDoS 防护设备的处理上限以及是否拦截分片。
数值参考
UDP 包在 1,200 字节以下比较安全;互联网路径 MTU 通常为 1,500 字节,经过隧道会更小(GRE 隧道为 1,476 字节)。超过路径 MTU 的包会被分片或丢弃,分片包只要丢一个分片就整个丢失。每个玩家每秒的包数增加 20%,全服也会增加 20%,原本就接近上限的实例会马上超限。
监控图上
某一时刻起台阶式上升 · 每个玩家每秒的包数/字节数、平均包大小
查看位置
对比发布前后服务器网卡每秒的包数、字节数(sar -n DEV 的 rxpck/s、txpck/s、rxkB/s、txkB/s,EC2 为 NetworkPacketsOut、NetworkOut)除以同时在线人数后的值。平均包大小 = 字节数 ÷ 包数,大小分布用 Wireshark 的 Packet Lengths 统计分析抓包
确认依据
发布后每个玩家的包数、字节数或平均包大小台阶式上升并保持,同一时刻起服务器生成的分片数(sar -n IP 的 fragcrt/s)或实例超限计数器(AWS ENA 的 pps_allowance_exceeded、bw_out_allowance_exceeded)增加
排除依据
发布前后流量特征相同,只有延迟和丢包增加,看同一时刻的基础设施变更(配置、路由、设备、OS/内核更新)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
收到“更新前好好的”这类反馈时,除了基础设施变更,首先要确认的游戏侧原因就是这一条。即使更新公告里没有网络相关改动,一个新特效或同步项在人多的地方也会按几百人成倍放大。增加的流量实际会碰到哪些上限,请看“UDP 数据包的 IP 分片”“超出云 PPS 上限”“网卡带宽饱和”“内核 socket 缓冲区不足”“中间设备超出处理上限(防火墙/IPS/DDoS 防护)”等条目。本条针对的是把流量推到这些上限的起点在于游戏版本更新的情况,所以在提高上限之前,先削减版本更新带来的流量。如果同一时间还更新了 OS 和内核,就看每个玩家的流量有没有变化,以此与“OS/内核/驱动/固件更新后的性能变化”区分。
出处 7 条

L10 内存

9 个原因 · 完整版章节

服务器 GC 全局停顿 Stop-the-world GC pause

ID mem-gc · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

Java、C# 服务器为回收垃圾对象而暂停所有线程(stop-the-world),这段时间里整个服务器都停住。

起因 堆满,触发 GC → 结果 暂停所有游戏线程后回收(存活数据越多,停得越久) → 画面表现 这台服务器上的所有人同时卡住,随后快进

症状
卡住, 快进
因素
停顿
谁会遇到
全服
何时出现
固定周期, 开得越久越严重
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
用启动参数显式指定停顿短的 GC(ZGC、Shenandoah、调低停顿目标的 G1),减少分配,调整堆大小。
运维团队要做的事
选用内存充足、能给堆留足空间的实例;容器至少分配 2 个 CPU、约 1.8 GB 内存(JDK 26 及以下版本低于这个配置时会默认选用 Serial GC);监控 GC 停顿时间。
数值参考
只回收新对象(新生代)的 Minor GC 需要几到几十 ms。回收整个堆、存活数据达数 GB 的 Full GC 有时会超过 1 秒。ZGC 的停顿几乎与堆大小无关,不到 1 ms;Shenandoah 的停顿也不随堆大小成比例增长,同样很短。
监控图上
周期性尖峰 · 服务器 tick 耗时、GC 停顿时间
查看位置
开启 GC 日志,把停顿的时刻和时长叠加到服务器 tick 耗时曲线上对照。Java 看启动参数 -Xlog:gc*(JDK 8 及以下用 -XX:+PrintGCDetails)输出的 Pause 行,.NET 看 dotnet-counters 的 GC 停顿指标(.NET 9 起为 dotnet.gc.pause.time,8 及以下为 % Time in GC since last GC),Go 看 GODEBUG=gctrace=1 每次 GC 输出的那一行
确认依据
tick 耗时冲高的时刻与 GC 停顿重合,停顿时长与冲高持续时间相近。服务器上所有场景、分线在同一时刻冲高
排除依据
GC 日志里没有长停顿而 tick 仍冲高,则是锁、同步调用、磁盘写入等其他原因。只有一个场景冲高,看“脚本引擎 GC 停顿”(mem-script-gc)或这个场景的负载
确认手段
运维工具即可确认(无需游戏代码)
深入了解
Java G1 的单次停顿目标默认是 200 ms,在 20 tick 服务器上相当于 4 个 tick。给容器分配的 CPU 少于 2 个或内存少于约 1.8 GB 时,JDK 26 及以下版本的 Java 会默认选用单线程回收的 Serial GC,停顿会长得多。C#(.NET)服务器通常会开启服务器 GC 和后台 GC,但存放新对象的第 0、1 代(Gen0/1)回收,以及伴随压缩的 Full GC,仍会暂停所有线程。Go 的停顿通常不到 1 ms,但分配量大时,申请内存的一方必须分担 GC 工作,tick 会因此变慢。无论哪种方式,只要分配快于回收,游戏线程最终都会停住:G1 会退化为 Full GC,ZGC 会让申请内存的线程一直停到回收结束。
真实案例
Riot Games 2021: League of Legends EUW 5 小时故障:一个辅助 DB 导致全服停摆
出处 10 条

脚本引擎 GC 停顿 Scripting VM GC (Lua, etc.)

ID mem-script-gc · 主责 研发团队·服务器开发

即使是 C++ 服务器,如果任务、AI、技能用 Lua 等脚本来跑,脚本引擎执行 GC 期间,对应的场景也会停住。

起因 每个场景的脚本引擎在执行任务、AI、事件时大量创建临时对象 → 结果 脚本引擎的 GC 一次回收太多时,这个场景的 tick 停住 → 画面表现 只在特定场景、特定事件期间周期性地顿一下

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
特定地点/分线
何时出现
人多的时候, 固定周期
负责方
主责 研发团队·服务器开发
研发团队要做的事
设置增量或分代 GC,每个 tick 推进一小步 GC,减少脚本中的临时对象。
数值参考
脚本堆涨到数百 MB 后,一次性集中回收(关闭增量回收,或分代模式下的完整回收)有时要花几十到几百 ms。
监控图上
周期性尖峰 · 各场景 tick 耗时、脚本引擎内存
查看位置
每个 tick 记录各场景的 tick 耗时和这个场景脚本引擎的内存占用(Lua 用 collectgarbage("count")),叠加在同一张图上对照
确认依据
脚本内存骤降的时刻(一次性集中回收)与这个场景的 tick 冲高重合,其他场景正常
排除依据
脚本内存没有变化却冲高,是这个场景的负载或锁。服务器上所有场景同时冲高,看“服务器 GC 全局停顿”(mem-gc)或“swap”(mem-swap)
确认手段
需要游戏服务器/客户端的日志和指标
出处 1 条

内存分配暴增 Allocation storms

ID mem-alloc · 主责 研发团队·服务器开发

活动期间大量创建临时对象,GC 会比平时频繁得多。

起因 物品掉落、战斗日志、活动奖励让临时对象激增 → 结果 GC 频率翻了几倍,还没来得及丢弃的对象晋升到老年代,Full GC 也随之提前 → 画面表现 只在活动期间周期性地顿一下

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
全服, 特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
对象池,缓冲区复用,用 profiler 分析内存分配。
监控图上
随人数/负载上升 · GC 次数、分配速率
查看位置
用 GC 日志(Java -Xlog:gc,Go GODEBUG=gctrace=1)统计每分钟 GC 次数;.NET 看 dotnet-counters 的分配量和 GC 次数(.NET 9 起为 dotnet.gc.heap.total_allocated、dotnet.gc.collections,8 及以下为 Allocation Rate、Gen 0 GC Count)。与同时在线人数、活动时间叠加对照
确认依据
活动开始后,分配速率和 GC 次数比人数增长得更陡,短停顿变得频繁。活动结束后恢复原状
排除依据
GC 次数不变而单次停顿变长,说明存活数据增加了,看“GC 颠簸(堆余量不足)”(mem-gc-thrash)、“内存泄漏”(mem-leak)
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

内存泄漏 Memory leak

ID mem-leak · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

没有释放的内存一点点累积,几天后引发 GC 失控、swap 或进程被强制终止。

起因 已下线角色的数据、事件处理函数没有释放 → 结果 空闲内存在几天内持续减少 → 画面表现 维护结束后正常,越往后越卡,最终服务器宕机

症状
慢动作, 卡住, 掉线
因素
停顿
谁会遇到
全服
何时出现
开得越久越严重, 晚高峰
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
分析堆 dump,长时间压测。
运维团队要做的事
监控各进程内存占用的趋势并设置告警。
监控图上
缓慢爬升 · 进程内存(RSS)、GC 后的堆
查看位置
以天为单位看游戏服务器进程的内存(pidstat -r 的 RSS);带 GC 的服务器看 GC 刚结束时剩余的堆。Java 看 -Xlog:gc 行里 GC 前后占用量中的 GC 后数值,.NET 看 dotnet-counters 的 GC 后堆大小(.NET 9 起为 dotnet.gc.last_collection.heap.size,8 及以下为 GC Heap Size)
确认依据
GC 后剩余的堆(谷底基线)在重启后逐日抬高,凌晨人少时也不回落
排除依据
堆的谷底基线平稳、只有 RSS 上涨,看“内存碎片”(mem-fragment)或原生内存。随人数起伏则是正常占用
确认手段
运维工具即可确认(无需游戏代码)
深入了解
每周例行维护都会重启,泄漏因此被掩盖,很长时间都发现不了。往往在某次维护推迟、或活动带来人数增加时突然暴露。
出处 5 条

GC 颠簸(堆余量不足) GC thrashing (heap nearly full)

ID mem-gc-thrash · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

存活数据接近堆上限时,GC 即使运行也几乎回收不到东西,于是不停地反复执行。

起因 活动人数增加或内存泄漏,让存活数据逼近堆上限 → 结果 GC 只能回收一点点,紧接着又是 Full GC,CPU 大部分时间都花在 GC 上 → 画面表现 全服几分钟内反复出现慢动作、卡住,最后因内存不足而终止

症状
慢动作, 卡住, 掉线
因素
停顿
谁会遇到
全服
何时出现
晚高峰, 人多的时候, 开得越久越严重
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
堆大小要比高峰时的存活数据留足余量(通常 2 倍以上),减少长期引用的数据和泄漏。
运维团队要做的事
为 GC 时间占比设置告警,不要硬撑、尽早重启,选用内存充裕的实例以便调大堆。
数值参考
GC 占用超过 10% 的运行时间,一般就视为危险信号。Java 的部分 GC 如果把 98% 的时间花在 GC 上却几乎回收不到内存,就会抛出内存不足错误。
监控图上
触顶后走平 · GC 后的堆、GC 时间占比
查看位置
看 GC 后剩余的堆离最大堆有多近,以及花在 GC 上的时间占比。Java 看 -Xlog:gc 行里的“GC 后(堆大小)”和 Pause Full 行出现的频率,.NET 看 dotnet-counters(.NET 8 及以下为 % Time in GC since last GC,.NET 9 起看 dotnet.gc.pause.time 的增量),Go 看 GODEBUG=gctrace=1 各行之间的间隔
确认依据
GC 刚结束堆仍接近最大值,Full GC 接连执行,GC 时间占比明显高于平时(通常超过 10%)。这段时间全服 tick 一起变慢
排除依据
GC 后堆还有余量、只是停顿长,属于 GC 类型或配置问题,看“服务器 GC 全局停顿”(mem-gc)
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

swap Swapping

ID mem-swap · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

内存不足时,OS 会把一部分内存换出到磁盘。之后每次用到这部分内存,都要等待比内存慢 1,000 倍以上的磁盘。

起因 内存用量超过物理内存 → 结果 OS 把一部分换出到磁盘,需要时再读回来 → 画面表现 tick 耗时暴涨到数百 ms,这台服务器上的所有玩家都出现慢动作、卡住

症状
慢动作, 卡住
因素
停顿
谁会遇到
全服
何时出现
开得越久越严重, 晚高峰
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
给进程内存占用设上限(堆大小等),排查泄漏。
运维团队要做的事
游戏服务器配置为不使用 swap,靠内存告警应对;关闭 swap 后内存一旦不足进程就会被强制终止(OOM),所以物理内存要比峰值用量留足余量。
数值参考
读内存约 100 ns,从 SSD 读回约 100 µs(1,000 倍),经网络访问的云盘约 1 ms(1 万倍),HDD 约 10 ms(10 万倍)。
监控图上
缓慢爬升 · swap 用量、换入/换出
查看位置
把 vmstat 1 的 si、so 列(每秒从 swap 读入、换出到 swap 的量),/proc/pressure/memory 的 some、full(因等待内存而停顿的时间占比),以及游戏服务器进程 pidstat -r 的 majflt/s(必须从磁盘读入的缺页)与 tick 耗时叠加对照
确认依据
卡顿时刻 si 大于 0,游戏服务器的 majflt/s 和 memory 的 full 值一起上升
排除依据
si、so 为 0,内存压力(PSI)也接近 0,则与 swap 无关。没有 swap 而 majflt/s 和 PSI 上升,说明内存耗尽、正在重新读入代码页,要先腾出内存
确认手段
运维工具即可确认(无需游戏代码)
深入了解
带 GC 的服务器回收时要读遍堆的各个角落,所以哪怕只有一部分堆被换出,一次 GC 也可能拉长到几秒甚至几十秒。关闭 swap 后,不会经历因 swap 变慢的阶段,会直接进入强制终止(OOM),因此必须先留足空闲内存。即使没有 swap,内存接近耗尽时,OS 也会把可执行文件的代码页从内存中丢掉再重新读入,在被强制终止前,整台服务器可能会有一段时间严重变慢。
出处 8 条

缓存未命中 CPU cache misses

ID mem-cache-miss · 主责 研发团队·服务器开发

数据分散在内存各处时,CPU 每次都要到较慢的内存里取数据并等待。

起因 对象通过指针分散在各处,访问顺序杂乱 → 结果 CPU 缓存里没有,每次都从内存读取(慢 100 倍左右) → 画面表现 做同样的事,tick 开销翻几倍,严重时出现慢动作

症状
慢动作
因素
停顿
谁会遇到
全服
何时出现
一直, 人多的时候
负责方
主责 研发团队·服务器开发
研发团队要做的事
把经常一起用的数据连续存放(面向数据设计)。
监控图上
一直偏高 · tick 耗时、CPU 使用率
查看位置
对游戏服务器进程执行 perf stat -d -p PID,测量每周期指令数(insn per cycle)和 L1、LLC 缓存未命中,与 tick 耗时、CPU 使用率一起看
确认依据
CPU 一直很忙,但 insn per cycle 低、LLC 未命中多。改了数据布局的版本在同样人数下 tick 耗时大幅下降,即可确定
排除依据
CPU 使用率低而 tick 慢,是锁、I/O 等待这类在 CPU 之外等待的原因
确认手段
运维工具即可确认(无需游戏代码)
出处 2 条

内存碎片 Heap fragmentation

ID mem-fragment · 主责 研发团队·服务器开发

反复分配和释放会把空闲空间切得很碎,进程占用的内存会远多于实际使用量。

起因 多个线程长时间分配、释放大小不一的内存 → 结果 空闲空间零散分布、无法归还给 OS,占用像泄漏一样持续增长 → 画面表现 运行越久越会因 swap、内存不足而变慢,最后被强制终止

症状
慢动作, 掉线
因素
停顿
谁会遇到
全服
何时出现
开得越久越严重
负责方
主责 研发团队·服务器开发
研发团队要做的事
按大小分级的内存池,抗碎片的分配器(jemalloc、mimalloc 等)。
监控图上
缓慢爬升 · 进程内存(RSS)
查看位置
用同一版本启动两个服务器实例,只对其中一个用环境变量 MALLOC_ARENA_MAX 减少 glibc arena 数量,或换成 jemalloc 等其他分配器,对比几天内 pidstat -r 的 RSS
确认依据
人数、实体数相近,只有改过的那个实例 RSS 不再增长或大幅下降
排除依据
换了分配器照样上涨,是没有释放的内存,看“内存泄漏”(mem-leak)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
曲线形态和泄漏一样,分析堆却找不到泄漏点。Linux 默认分配器(glibc)在线程多的服务器上尤其严重,仅仅换掉分配器,占用就可能大幅下降。
出处 3 条

NUMA 远端内存 Remote NUMA access

ID mem-numa · 主责 运维团队·系统运维

在装有两颗 CPU 的服务器上,使用挂在另一颗 CPU 上的内存,访问会变慢。

起因 线程和内存分别位于不同的 CPU 插槽(socket) → 结果 内存访问变慢(视硬件为 1.5~2 倍) → 画面表现 配置相同,各进程的性能却有差异

症状
慢动作
因素
停顿
谁会遇到
全服
何时出现
一直
负责方
主责 运维团队·系统运维
运维团队要做的事
把进程和内存绑定到同一个插槽(numactl);有两个插槽时,按插槽分开部署游戏服务器进程。
监控图上
仅部分偏高 · 各进程 tick 耗时、各节点内存
查看位置
用 numastat -p PID 看游戏服务器进程的内存位于哪个 NUMA 节点,看 numastat 的 numa_miss、other_node 是否增长,并与该进程所在 CPU 的节点对比
确认依据
只有慢的进程大部分内存位于与其运行 CPU 不同的节点上;用 numactl 把 CPU 和内存绑定到同一节点后重启,差异消失
排除依据
节点分布与快的进程相同却仍然慢,是邻居干扰、CPU 限流、该进程自身负载等其他原因
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

L11 磁盘

9 个原因 · 完整版章节

同步写日志 Synchronous logging

ID dk-sync-log · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

游戏线程每写一行日志都要等磁盘完成,磁盘一忙,游戏逻辑也跟着停住。

起因 在游戏线程里直接把战斗、交易日志写入文件 → 结果 要求确保落盘(fsync),或 OS 的写缓冲(页缓存)达到上限时,磁盘一忙,一次写入就要几十 ms → 画面表现 日志多的战斗中顿一下

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
异步日志(内存缓冲 + 独立线程),减少日志量,不在游戏线程里调用 fsync。
运维团队要做的事
日志轮转和压缩以较低的 I/O 优先级运行,日志与数据放在不同磁盘,监控磁盘写入延迟。
监控图上
偶发随机尖峰 · 服务器 tick 耗时、磁盘写入延迟
查看位置
把 iostat -x 1 的 w_await、aqu-sz 与 tick 耗时叠加对照,用 perf trace -p PID --duration 10 找出游戏服务器中耗时超过 10 ms 的 write、fsync 调用及其所在线程
确认依据
tick 冲高的时刻,游戏线程的 write、fsync 调用耗时几十 ms,同时磁盘写入延迟也飙升。常与日志轮转、压缩的时间重合
排除依据
游戏线程中没有耗时长的系统调用而 tick 仍冲高,是 GC、锁、tick 超出预算等其他原因。只有日志专用线程耗时长,则不影响游戏逻辑
确认手段
运维工具即可确认(无需游戏代码)
深入了解
OS 通常先把写入的数据放进内存(页缓存),稍后再刷到磁盘,所以写一行日志一般会立即完成。停顿出现在这几种时候:用 fsync 要求确保落盘时;积压的写入超过上限、OS 阻塞写调用时;轮转或压缩日志文件时。所以平时一切正常,只在磁盘繁忙的瞬间冲高。
出处 5 条

fsync 风暴 fsync storms

ID dk-fsync · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·数据库运维

要求把数据“真正”写进磁盘(确保落盘)时,视磁盘不同每次要花 0.1 ms 到几十 ms;请求一集中,队列就会变长。

起因 定时存盘、集中下线让确保落盘的写入请求扎堆 → 结果 磁盘队列变长 → 画面表现 每到存盘时间就卡,下线、换线变慢

症状
一卡一卡, 操作延迟
因素
停顿, 延迟
谁会遇到
全服
何时出现
固定周期, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·数据库运维
研发团队要做的事
合并存盘(多次存盘只做一次 fsync),错开存盘时间。
运维团队要做的事
服务器/OS:使用带掉电保护的服务器级 SSD,监控磁盘队列长度和 fsync 延迟。DB 服务器:存盘写到 DB 时,DB 日志盘也用同样的 SSD,监控提交延迟。
数值参考
单次耗时因硬件而异,大致为:服务器级 SSD(带掉电保护)0.1 ms,普通 SSD 1 到几 ms,云盘 1~2 ms,HDD 10 ms 以上。如果一个线程逐个等待,HDD 每秒连 100 次都做不到。
监控图上
周期性尖峰 · 磁盘队列长度、刷盘/写入延迟
查看位置
把 iostat -x 1 的 f/s、f_await(磁盘处理的刷盘次数及耗时)以及 w/s、aqu-sz、w_await 与定时存盘、下线时间叠加对照。旧版 sysstat 把 aqu-sz 显示为 avgqu-sz。云盘看 EBS 的 VolumeQueueLength、VolumeAvgWriteLatency
确认依据
每到存盘时间或集中下线时,刷盘次数和队列长度一起飙升,w_await、f_await 变成平时的几倍。此时存盘、换线变慢
排除依据
队列在与存盘、下线无关的时间飙升,看“备份/压缩/扫描任务”(dk-backup)或“IOPS 上限/队列饱和”(dk-iops)。刷盘次数不变却变慢,看“云盘突发积分耗尽”(dk-burst)
确认手段
运维工具即可确认(无需游戏代码)
出处 6 条

云盘突发积分耗尽 Burst credit depletion

ID dk-burst · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维

部分云盘和小规格实例有突发积分,可以短时间超过基准性能运行。繁忙时段一长、积分耗尽,速度就会突然下降。

起因 长时间以高于基准性能的水平运行 → 结果 突发积分耗尽,性能骤降到基准水平 → 画面表现 每天晚上过了几个小时后开始卡

症状
一卡一卡, 慢动作, 操作延迟
因素
停顿, 延迟
谁会遇到
全服
何时出现
晚高峰, 开得越久越严重
负责方
主责 运维团队·系统运维 · 配合 运维团队·数据库运维
运维团队要做的事
服务器/OS:换成性能有保障的磁盘(gp3、预置 IOPS 型),为积分余额设告警,同时检查实例的磁盘带宽突发上限和 CPU 积分。DB 服务器:包括托管数据库在内,DB 磁盘也换成性能有保障的类型,并为积分余额设告警。
数值参考
AWS gp2 100 GB 磁盘平时 300 IOPS,突发时 3,000 IOPS,积分满时约能撑 30 分钟。gp3 没有积分,始终是 3,000。Azure 的小容量 Premium SSD 也靠积分最多突发 30 分钟。
监控图上
触顶后走平 · IOPS、突发积分余额
查看位置
看 CloudWatch 中 EBS 的 BurstBalance(gp2、st1、sc1),实例的 EBSIOBalance%、EBSByteBalance%(部分可突发的实例),以及突发型实例的 CPUCreditBalance。Azure 看 Data Disk Used Burst IO Credits Percentage 等突发积分使用率指标
确认依据
从余额掉到接近 0 的时刻起,IOPS(VolumeReadOps、VolumeWriteOps)在基准性能处走平,VolumeQueueLength 和卡顿一起增加。在高峰持续几小时之后开始
排除依据
各项余额都充足而 IOPS 走平,是卷或实例的固定上限,看“IOPS 上限/队列饱和”(dk-iops)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
即使磁盘没问题,小规格虚拟机的实例磁盘带宽也有突发上限(每天至少 30 分钟等),同样会出现这种曲线。使用 CPU 积分的低价实例,积分耗尽后也会降到基准性能。
出处 7 条

IOPS 上限/队列饱和 IOPS limit / queue saturation

ID dk-iops · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 运维团队·数据库运维

请求超过磁盘每秒能处理的数量后,队列变长,延迟暴涨。

起因 读写请求接近磁盘的处理能力 → 结果 队列变长(利用率超过 90% 时通常暴涨) → 画面表现 存盘、加载变慢,同步调用时会卡住

症状
操作延迟, 卡住
因素
延迟, 停顿
谁会遇到
全服
何时出现
人多的时候, 晚高峰
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 运维团队·数据库运维
研发团队要做的事
合并请求,常读的数据放缓存,改成异步,不让游戏线程等磁盘。
运维团队要做的事
服务器/OS:换更快的磁盘,确认各实例类型的磁盘带宽和 IOPS 上限,为磁盘利用率和队列设告警,大文件复制放到空闲时段。DB 服务器:DB 磁盘也要为 IOPS、吞吐量利用率设告警,确认 DB 实例规格的磁盘上限。
数值参考
HDD 约 150 IOPS,SATA SSD 数万,NVMe 数十万。云上默认磁盘(AWS gp3)为 3,000 IOPS、每秒 125 MiB;吞吐量上限与 IOPS 分开计算,大文件复制把它占满时,连小的写入也会被拖慢。
监控图上
触顶后走平 · IOPS、磁盘队列长度
查看位置
看 iostat -x 1 的 r/s、w/s,rkB/s、wkB/s,aqu-sz,r_await、w_await。云上看 EBS 的 VolumeReadOps、VolumeWriteOps、VolumeQueueLength,以及表示是否超限的 VolumeIOPSExceededCheck、VolumeThroughputExceededCheck,实例侧看 InstanceEBSIOPSExceededCheck、InstanceEBSThroughputExceededCheck
确认依据
每秒请求数或吞吐量在上限值处走平,aqu-sz 和 await 一起飙升。云上的超限检查指标为 1
排除依据
%util 即使达到 100%,只要 await 低,可能还有余量。在并行处理请求的 SSD、RAID 上,%util 不代表到了上限。没到上限而只有 await 高,是磁盘自身的延迟或 fsync,看“HDD 寻道延迟”(dk-hdd)、“fsync 风暴”(dk-fsync)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
在云上,除了磁盘本身的上限,每种服务器规格(实例类型)还有自己的磁盘带宽和 IOPS 上限。就算挂了昂贵的磁盘,服务器规格小,也会受限于实例上限。
出处 8 条

磁盘写满 Disk full

ID dk-full · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发

日志和 dump 堆积把磁盘写满后,写入会失败;没有做好应对,服务器就会崩溃。

起因 日志、dump、临时文件堆积到 100% → 结果 写入失败。没有错误处理就崩溃,有错误处理则存盘失败 → 画面表现 掉线,进度回档

症状
掉线, 吞操作/回档
因素
停顿
谁会遇到
全服
何时出现
开得越久越严重
负责方
主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发
研发团队要做的事
处理写入失败,用存盘重试和告警代替崩溃,减少不必要的日志和 dump。
运维团队要做的事
服务器/OS:日志轮转,容量告警,日志盘与数据盘分离。DB 服务器:监控 DB 事务日志(WAL、binlog)是否因复制中断、日志备份遗漏而堆积。
监控图上
缓慢爬升 · 磁盘使用率
查看位置
看 df -h 的使用率和 df -i 的 inode 使用率,在服务器和 DB 日志里找 ENOSPC 错误。DB 方面,PostgreSQL 看 pg_replication_slots 中 active 为 false 的槽,MySQL 看 SHOW BINARY LOGS 的文件数和大小,SQL Server 看 sys.databases 的 log_reuse_wait_desc,RDS 看 FreeStorageSpace
确认依据
使用率连续几天稳步上升,到达 100% 的时刻与崩溃、存盘失败的时间重合,日志里有 ENOSPC
排除依据
空间充足而写入失败,是权限、文件大小上限等其他原因
确认手段
运维工具即可确认(无需游戏代码)
深入了解
从库停止复制或漏做日志备份时,DB 的事务日志(WAL、binlog 等)不会被清理,会一直堆积。这块磁盘一满,DB 的所有写入都会停止,存盘和交易会一起失败。
出处 8 条

备份/压缩/扫描任务 Backup / compression / scans

ID dk-backup · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维

凌晨的备份、日志压缩、安全扫描独占磁盘时,游戏服务器的读写会被挤到后面。

起因 定时的备份、压缩任务启动 → 结果 占用了大部分磁盘带宽和 IOPS → 画面表现 每天同一时间卡顿

症状
一卡一卡, 操作延迟
因素
停顿, 延迟
谁会遇到
全服
何时出现
固定周期
负责方
主责 运维团队·系统运维 · 配合 运维团队·数据库运维
运维团队要做的事
服务器/OS:降低备份、压缩、扫描任务的 I/O 优先级,错开执行时间。DB 服务器:在从库上做备份。
监控图上
周期性尖峰 · 磁盘利用率、磁盘等待时间
查看位置
用 sar -d 把过去几天的记录(/var/log/sa 下的按日文件,需要 sadc 用 -S DISK 采集磁盘项)中的 %util、await、aqu-sz 按日期叠加对照;在那个时间点用 pidstat -d 1 找出 kB_rd/s、kB_wr/s 最大的进程,与 cron、systemd 定时器的计划对照
确认依据
每天同一时间 await 和 %util 飙升,此时备份、压缩、扫描进程占了大部分磁盘读写
排除依据
飙升时间每天不同,就不太可能是定时任务。那个时间的 I/O 大部分来自游戏服务器自己,属于存盘、日志问题,看“fsync 风暴”(dk-fsync)、“同步写日志”(dk-sync-log)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

服务器端懒加载 Lazy loading on the server

ID dk-lazy-load · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

服务器第一次收到副本、地图数据请求时才从磁盘读取,那个 tick 期间所有人都会卡住。

起因 有人第一次进入某个副本或区域 → 结果 服务器在游戏线程里从磁盘读数据 → 画面表现 这台服务器上的所有人短暂卡住

症状
卡住
因素
停顿
谁会遇到
特定地点/分线, 全服
何时出现
移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
服务器启动时预加载,异步加载。
运维团队要做的事
刚从快照创建的服务器,在投入服务前预热磁盘(把所有块读一遍),或使用快速快照恢复功能。
监控图上
偶发随机尖峰 · 服务器 tick 耗时、磁盘读取
查看位置
把停顿时刻与游戏服务器日志中副本、区域的首次进入记录对照,看当时游戏服务器的磁盘读取(pidstat -d 的 kB_rd/s),并用 perf trace --duration 找耗时长的 read、open 调用。新拉起的云服务器,把 EBS 的 VolumeAvgReadLatency 与运行已久的服务器对比
确认依据
只在首次进入时停顿,第二次进入同一地方不再停顿。停顿期间游戏线程在等文件读取
排除依据
已加载的区域同样停顿,是 tick 超出预算、GC 等其他原因
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
在云上刚用快照(磁盘副本)创建的服务器,每个第一次读取的块都要从远端存储拉取,比平时慢得多。如果只有弹性伸缩新拉起的服务器首次进入特别慢,就该怀疑这个原因。
出处 5 条

写入 core dump Core dump writing

ID dk-coredump · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

服务器崩溃时要把数 GB 内存写到磁盘,重启有时会因此推迟好几分钟。

起因 服务器崩溃,把全部内存写成文件 → 结果 写入数 GB 期间无法重启 → 画面表现 服务器崩溃导致掉线,之后很长时间连不上

症状
连不上/无限加载
因素
停顿
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
考虑只保存必要内存的小 dump(minidump)方式,修复崩溃原因。
运维团队要做的事
限制 dump 大小(OS 的 core dump 设置),使用更快的磁盘,把重启和 dump 分开(dump 的压缩、上传在重启后另行处理)。
监控图上
连接成批断开 · 连接数、服务器重启时间
查看位置
把崩溃时间、core 文件大小(coredumpctl list、info,或 core_pattern 指向位置的文件)、文件写完的时间、服务重新拉起的时间放在一起,看这段时间 iostat -x 的 wkB/s
确认依据
崩溃后写入数 GB 的 core 文件期间,磁盘写入一直贴近上限,写完之后才开始重启
排除依据
core dump 已关闭或文件很小,重启仍然慢,是地图加载、DB 冷缓存等服务器启动过程的问题,看“冷缓存(刚重启时)”(db-cold-cache)
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

HDD 寻道延迟 HDD seek latency

ID dk-hdd · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发

HDD 的磁头必须在盘片上移动(寻道,seek),读写分散的数据每次要花将近 10 ms。

起因 老旧服务器或低价存储使用 HDD → 结果 每次随机读写约 10 ms → 画面表现 存盘、加载普遍变慢

症状
操作延迟
因素
延迟
谁会遇到
全服
何时出现
一直
负责方
主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发
研发团队要做的事
以顺序写为主的设计。
运维团队要做的事
服务器/OS:换成 SSD(先换随机读写多的存储盘)。DB 服务器:先把随机读写最多的 DB 磁盘换成 SSD。
监控图上
一直偏高 · 磁盘读写延迟(r_await、w_await)
查看位置
用 lsblk -d -o NAME,ROTA 确认是否为机械硬盘(HDD),看 iostat -x 1 的 r/s、w/s 和 r_await、w_await。虚拟机在云平台或存储的规格说明里确认磁盘类型
确认依据
是机械硬盘,每秒请求只有几十到一百多个,r_await、w_await 却总在几 ms 到几十 ms
排除依据
是 SSD 但延迟高,看“IOPS 上限/队列饱和”(dk-iops)或“云盘突发积分耗尽”(dk-burst)
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

L12 数据库

16 个原因 · 完整版章节

缺少索引的查询 Missing index / full table scan

ID db-no-index · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

没有索引时,要找到符合条件的行,就得把整张表读一遍(全表扫描)。

起因 发布新功能时加了一个没有索引的条件查询 → 结果 扫描全部几百万行,一条查询要几百 ms 到几秒 → 画面表现 邮箱、交易记录加载慢,连接被占住,其他请求也跟着等

症状
操作延迟, 连不上/无限加载
因素
延迟, 停顿
谁会遇到
仅特定功能, 全服
何时出现
做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
新查询在发布前审查执行计划,补充索引,修改类语句(UPDATE、DELETE)也要确认走了索引。
运维团队要做的事
监控慢查询日志,找出全表扫描的查询并同步给研发团队,线上加索引采用锁持有时间短的在线方式。
数值参考
有索引时几 ms;没有索引时随数据量成比例变慢,大表上会慢几百到几万倍。
监控图上
某一时刻起台阶式上升 · DB 查询延迟、扫描行数
查看位置
MySQL 看 slow query log(开启 log_queries_not_using_indexes 后,未使用索引的查询也会记录)中的 Rows_examined、Rows_sent,以及 performance_schema events_statements_summary_by_digest 的 SUM_NO_INDEX_USED、SUM_ROWS_EXAMINED,再执行 EXPLAIN。PostgreSQL 看 pg_stat_user_tables 的 seq_scan、seq_tup_read,再执行 EXPLAIN
确认依据
发布后新出现的查询读取的行数(Rows_examined)比返回的行数(Rows_sent)多几千倍,EXPLAIN 显示全表扫描(MySQL type ALL,PostgreSQL Seq Scan)。大表的 seq_tup_read 从发布时间起陡增
排除依据
走了索引仍然慢,是锁等待或执行计划变化,看“热点行锁竞争”(db-hot-row)、“线上表结构变更(DDL)锁”(db-ddl-lock)、“执行计划变化导致查询变慢”(db-plan-flip)。小表的全表扫描可能是正常的
确认手段
运维工具即可确认(无需游戏代码)
深入了解
变慢的不只是读。不走索引的修改语句(UPDATE、DELETE)在某些 DB 上会把扫描过的行都锁住,连无关玩家的存盘也会被挡住。
出处 8 条

热点行锁竞争 Hot row lock contention

ID db-hot-row · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

所有人都要修改同一行(公会仓库、拍卖行热门物品、全服计数器)时,同一时刻只有一个人能拿到锁。

起因 活动、热门物品让修改集中到同一行 → 结果 请求一直等到拿到锁 → 画面表现 交易失败、“请稍后再试”、超时

症状
吞操作/回档, 操作延迟
因素
停顿, 延迟
谁会遇到
仅特定功能
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
拆分行(分片计数器),缩短事务,在内存中汇总后一次性写入。
运维团队要做的事
监控行锁等待时间和次数,找出竞争集中的行并共享结果。
数值参考
一个请求持锁 10 ms,这一行每秒最多只能修改 100 次。如果事务中还夹着与其他服务器的往返通信,能修改的次数会更少。
监控图上
随人数/负载上升 · 行锁等待次数、时长
查看位置
MySQL 看 Innodb_row_lock_waits、Innodb_row_lock_time 的增量和 Innodb_row_lock_current_waits,用 sys.innodb_lock_waits 找出谁在等谁。PostgreSQL 看 pg_stat_activity 中 wait_event_type 为 Lock 的会话、pg_locks 中 granted 为 false 的请求;开启 log_lock_waits(默认关闭)后,等待时间长的锁会记入日志
确认依据
锁等待随活动、人数陡增,等待中的请求大多指向同一张表的同一行(同一个键)
排除依据
等待均匀分散在多张表、多行上,属于磁盘、CPU 饱和问题。某个会话长时间持锁不放,看“长事务”(db-long-tx)
确认手段
运维工具即可确认(无需游戏代码)
出处 7 条

DB 死锁 Database deadlock

ID db-deadlock · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

两个事务(作为一个整体处理的一组 DB 操作)互相等待对方锁住的行时,DB 会强制取消其中一个。

起因 交易 A 按物品→货币的顺序加锁,B 按货币→物品的顺序加锁 → 结果 DB 检测到死锁,回滚其中一方 → 画面表现 交易、制作偶尔失败,物品回退

症状
吞操作/回档, 操作延迟
因素
丢包, 停顿
谁会遇到
仅特定功能
何时出现
人多的时候, 做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
统一加锁顺序,缩短事务,失败时自动重试。
运维团队要做的事
保持死锁检测开启,收集死锁记录并共享;关闭了检测的 MySQL 服务器要调低锁等待超时(默认 50 秒)。
数值参考
检测所需时间:MySQL(InnoDB)几乎即时,PostgreSQL 默认 1 秒,SQL Server 最多约 5 秒。在此期间两个请求都停在原地。如果服务器因并发请求极多而关闭了 MySQL 的死锁检测,就要一直等到锁等待超时(默认 50 秒)。
监控图上
偶发随机尖峰 · 死锁次数、交易失败次数
查看位置
MySQL 看 SHOW ENGINE INNODB STATUS 的 LATEST DETECTED DEADLOCK(最近 1 次),开启 innodb_print_all_deadlocks 后写入错误日志的所有死锁,以及 INFORMATION_SCHEMA.INNODB_METRICS 的 lock_deadlocks。PostgreSQL 看 pg_stat_database 的 deadlocks,SQL Server 看默认开启的 system_health 会话中的 xml_deadlock_report。游戏服务器侧的错误码为 MySQL 1213、PostgreSQL 40P01、SQL Server 1205
确认依据
交易、制作失败的时刻死锁次数增加,记录下的两个事务以相反的顺序锁同一组表
排除依据
死锁次数不变却失败,看锁等待超时(MySQL 错误 1205)或“热点行锁竞争”(db-hot-row)
确认手段
运维工具即可确认(无需游戏代码)
出处 10 条

连接池耗尽 Connection pool exhaustion

ID db-pool · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

与 DB 建立的连接数是固定的,慢查询占住连接后,其余请求只能等待。

起因 慢查询或请求激增,所有连接都在使用中 → 结果 新请求要等到有空闲连接 → 画面表现 登录时无限加载,存盘变慢,超时

症状
连不上/无限加载, 操作延迟
因素
停顿, 延迟
谁会遇到
全服, 仅特定功能
何时出现
刚登录/维护结束后, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
消除慢查询,调整连接池大小和等待超时(不要盲目调大连接池),按功能拆分连接池。
运维团队要做的事
确认 DB 的最大连接数和 CPU、IOPS 余量;扩容或弹性伸缩前,确认服务器数 × 连接池大小不超过最大连接数;把连接等待、锁等待指标加入监控。
数值参考
所需连接数可以按“每秒请求数 × 每个请求占用连接的时间”估算。每秒 2,000 个请求、每个 5 ms,平均有 10 个连接一直在忙。考虑到请求集中的时候,通常配置两三倍。查询慢到 150 ms 时,同样的请求量需要 300 个连接。
监控图上
触顶后走平 · 正在使用的 DB 连接数、连接等待时间
查看位置
在 DB 侧按游戏服务器统计连接状态。MySQL 看 SHOW PROCESSLIST 的 Host、Command(空闲连接为 Sleep)、Time,以及 Threads_connected、Threads_running 和被拒绝的连接数 Connection_errors_max_connections。PostgreSQL 把 pg_stat_activity 按 client_addr、state 分组统计。游戏服务器的连接池库如果输出等待数、等待时间,也一并查看
确认依据
某台游戏服务器的连接数达到连接池上限、全部在执行查询,空闲连接为 0,这段时间登录、存盘都在等待。或者 DB 总连接数达到 max_connections,新连接被拒绝
排除依据
空闲连接充足仍然慢,是查询本身慢或 DB 资源饱和,看“缺少索引的查询”(db-no-index)、“热点行锁竞争”(db-hot-row)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
盲目调大连接池,只会增加 DB 的 CPU 消耗和锁竞争,大家一起变慢。另外,服务器数 × 连接池大小超过 DB 最大连接数时,新扩容或重启的服务器连连接都建不起来。这在弹性伸缩或维护结束后很常见。
出处 6 条

复制延迟 Replication lag

ID db-replica-lag · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

写入走主库、读取走从库时,如果从库同步落后,刚写入的内容就读不到。

起因 写入集中涌向主库,从库落后几秒 → 结果 刚保存的内容,去从库读时还没有 → 画面表现 刚买的物品看不到,交易所价格还是旧值,出现重复发放 bug

症状
吞操作/回档
因素
延迟
谁会遇到
仅特定功能
何时出现
人多的时候, 晚高峰
负责方
主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
研发团队要做的事
刚写入的数据从主库读;检查是否已发放和执行发放,在主库的同一个事务里完成(用唯一键或条件 UPDATE 防止重复)。
运维团队要做的事
复制延迟告警;从库规格不低于主库并启用并行复制;大批量删除拆小分批执行;管控在从库上长时间运行的统计查询。
监控图上
随人数/负载上升 · 复制延迟(秒)
查看位置
MySQL 在从库上看 SHOW REPLICA STATUS 的 Seconds_Behind_Source(8.0.22 之前的版本用 SHOW SLAVE STATUS)。PostgreSQL 看主库 pg_stat_replication 的 write_lag、flush_lag、replay_lag,RDS 看 ReplicaLag
确认依据
玩家反馈“看不到”的时刻延迟在几秒以上,延迟消除后再看就正常。写入激增、大批量删除或在从库上运行长统计查询时,延迟变大
排除依据
延迟接近 0 仍然看不到,属于游戏服务器的缓存或同步问题
确认手段
运维工具即可确认(无需游戏代码)
深入了解
写入不多也可能落后。主库上耗时 10 分钟的一次大批量删除,在从库上重放期间也会让从库落后同样的时间;在从库上长时间运行的统计查询也会拖慢同步。
出处 6 条

检查点/日志刷盘 Checkpoint / log flush stalls

ID db-checkpoint · 主责 运维团队·数据库运维

DB 定期把内存中的变更集中写入磁盘,那一刻查询会变慢。

起因 变更累积后定期写入磁盘 → 结果 那一刻磁盘变忙,查询变慢 → 画面表现 存盘、加载周期性变慢

症状
操作延迟, 一卡一卡
因素
延迟
谁会遇到
全服, 仅特定功能
何时出现
固定周期
负责方
主责 运维团队·数据库运维
运维团队要做的事
把检查点拆细、均匀摊开,事务日志(redo log、WAL)留足容量,使用更快的磁盘。
监控图上
周期性尖峰 · DB 查询延迟、磁盘写入量
查看位置
PostgreSQL 看 log_checkpoints(较新版本默认开启)日志中的检查点时间和写出的缓冲区数,检查点次数(17 起为 pg_stat_checkpointer 的 num_timed、num_requested,16 及以下为 pg_stat_bgwriter 的 checkpoints_timed、checkpoints_req),以及 checkpoint_warning 警告。MySQL 看 SHOW ENGINE INNODB STATUS 的 LOG 部分中 Log sequence number 与 Last checkpoint at 的差值。同时叠加服务器的磁盘写入量和写入延迟
确认依据
查询延迟冲高的时刻与检查点时间重合,此时磁盘写入量和写入延迟飙升。PostgreSQL 中请求触发的检查点(num_requested)远多于定时检查点(num_timed)时,可判断为 WAL 频繁触及 max_wal_size、检查点被提前
排除依据
冲高周期与检查点时间无关,看“备份/压缩/扫描任务”(dk-backup)或“大型批处理任务”(db-batch)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
记录变更的事务日志(MySQL 的 redo log、PostgreSQL 的 WAL)设得太小,每次日志写满,DB 都要仓促地集中做检查点,写入吞吐量会一阵一阵地大幅下降。
出处 7 条

冷缓存(刚重启时) Cold buffer pool after restart

ID db-cold-cache · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

DB 重启后内存缓存是空的,一段时间内所有查询都要从磁盘读取。

起因 维护时重启 DB → 结果 常用数据不在内存里,只能从磁盘读 → 画面表现 维护结束后一段时间内登录、加载很慢

症状
连不上/无限加载, 操作延迟
因素
延迟
谁会遇到
全服
何时出现
刚登录/维护结束后
负责方
主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
研发团队要做的事
分批开服(用登录排队逐步放开登录人数)。
运维团队要做的事
重启后预热缓存(确认缓冲池的保存、恢复设置),从快照恢复的 DB 还要预读磁盘。
监控图上
开服/维护后激增 · 磁盘读取、缓冲区缓存命中率
查看位置
MySQL 看 Innodb_buffer_pool_reads(缓冲池中没有、从磁盘读取的次数)与 Innodb_buffer_pool_read_requests 的比值,以及预热进度 Innodb_buffer_pool_load_status。PostgreSQL 看 pg_stat_database 的 blks_read、blks_hit。同时看 DB 服务器的磁盘读取次数
确认依据
重启后磁盘读取飙升、命中率偏低,随时间推移逐渐恢复,这段时间登录、加载很慢
排除依据
命中率和平时一样,维护结束后仍然慢,看“登录激增与 N+1 查询”(db-login-storm)或“连接池耗尽”(db-pool)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
MySQL 关闭时会保存缓冲池的页列表,启动时在后台重新读入,但填满需要时间。在云上用快照(磁盘副本)恢复的 DB,磁盘本身每个块第一次读取时也很慢,持续时间会更长。
真实案例
Roblox 2021: Roblox 73 小时故障:服务发现(Consul)集群的争用问题
出处 5 条

登录激增与 N+1 查询 Login storm, N+1 queries

ID db-login-storm · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

加载一个角色要分别查询几十次时,数万人同时登录就会变成几百万条查询。

起因 加载角色时分别查询物品、技能、任务 → 结果 维护结束后大量玩家同时登录,查询激增 → 画面表现 登录时无限加载,正在游戏的玩家存盘也被拖慢

症状
连不上/无限加载, 操作延迟
因素
停顿, 延迟
谁会遇到
全服
何时出现
刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
合并成一次查询,登录排队,缓存,检查 ORM 懒加载产生的查询次数。
运维团队要做的事
统计调用次数最多的查询排名并共享,监控维护结束后登录时段的查询数和连接数。
监控图上
开服/维护后激增 · DB 每秒查询数、登录数
查看位置
把维护结束后的登录数与 DB 每秒查询数(MySQL 看 Questions 的增量)叠加对照,算出每次登录的查询数。调用次数靠前的查询,MySQL 用 events_statements_summary_by_digest 的 COUNT_STAR,PostgreSQL 用 pg_stat_statements 的 calls 统计
确认依据
每次登录有几十条查询,排名靠前的是按单个角色 ID 查询的同类短查询。版本更新后每次登录的查询数增加,就从那次更新查起
排除依据
每次登录的查询数不多、但每条查询都慢,看“冷缓存(刚重启时)”(db-cold-cache)或“缺少索引的查询”(db-no-index)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
ORM(代替开发者生成 DB 查询的库)的懒加载,会在开发者不知情的情况下产生这类查询。开发服上只有几个角色,看不出来,到线上大量玩家同时登录时才第一次暴露。
出处 5 条

大型批处理任务 Batch jobs during service

ID db-batch · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

在运营期间跑排行榜统计、批量发邮件、清理旧数据,会占用锁和磁盘。

起因 在运营时段执行大批量任务 → 结果 大范围加锁,占用磁盘和 CPU → 画面表现 特定时段交易、存盘失败,加载变慢

症状
操作延迟, 吞操作/回档
因素
延迟, 停顿
谁会遇到
仅特定功能, 全服
何时出现
固定周期
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
拆小分批执行,统计放到从库上做。
运维团队要做的事
提供统计专用从库,批处理安排在空闲时段,监控锁升级和间隙锁等待。
监控图上
周期性尖峰 · DB 查询延迟、锁等待
查看位置
找出卡顿时刻正在运行的长查询。MySQL 看 slow query log,PostgreSQL 看 pg_stat_activity 的 query_start、query,再与同一时刻的锁等待指标和批处理计划(cron、DB 事件调度器)对照。SQL Server 用 lock_escalation 扩展事件记录锁升级
确认依据
每次都在同一时间有大批量 UPDATE、DELETE、统计查询在跑,期间锁等待和磁盘利用率一起上升
排除依据
那个时刻没有长查询,看“检查点/日志刷盘”(db-checkpoint)或服务器上的“备份/压缩/扫描任务”(dk-backup)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
在 SQL Server 中,一条语句持有的行锁超过约 5,000 个时,会把它们换成表锁(锁升级)。那一刻,使用同一张表的所有请求都会停住。MySQL 在默认设置下按范围条件修改时,也会连行与行之间的间隙一起锁住(间隙锁),阻止插入新行。
出处 4 条

DB 故障切换 Database failover

ID db-failover · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

主库宕机、切换到备库期间无法写入,最后一部分没来得及复制的数据可能会丢失。

起因 主库故障,备库被提升为主库 → 结果 切换期间几秒到几分钟无法写入;如果是异步复制,未复制的数据可能丢失 → 画面表现 短时间内所有存盘失败,物品、经验回档

症状
吞操作/回档, 卡住, 掉线, 连不上/无限加载
因素
停顿, 丢包
谁会遇到
全服
何时出现
偶尔随机
负责方
主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
研发团队要做的事
存盘支持重试;配置为尽快丢弃断开的连接、按新地址重新建立连接(连接池、DNS 缓存);在切换演练时确认重连正常。
运维团队要做的事
采用同步或半同步复制(代价是写入延迟增加),做切换演练,监控切换时间和复制延迟。
数值参考
托管数据库的自动切换通常需要几十秒到 2 分钟。如果是异步复制,可能会丢失最近一段时间的存盘,时长相当于复制延迟(不到 1 秒到几秒)。
监控图上
连接成批断开 · DB 连接数、写入错误数
查看位置
把 DB 侧的故障切换记录(RDS 为事件 RDS-EVENT-0013 切换开始、RDS-EVENT-0049 切换完成,自建 DB 看提升日志)和游戏服务器的 DB 连接数、连接错误数放在同一张图上。如果是异步复制,还要看故障前一刻的复制延迟(RDS ReplicaLag,PostgreSQL pg_stat_replication 的 replay_lag)
确认依据
存盘失败集中在一段时间内,且与故障切换从开始到完成的区间重合。回退的量与故障前一刻的复制延迟相近。切换结束后仍持续报错的游戏服务器,说明还在使用连向旧地址的连接
排除依据
没有切换记录的时刻出现连接中断,属于网络或 DB 过载问题
确认手段
运维工具即可确认(无需游戏代码)
真实案例
Riot Games 2021: League of Legends EUW 5 小时故障:一个辅助 DB 导致全服停摆
出处 7 条

存盘间隔过长导致进度丢失 Periodic save window

ID db-save-interval · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

为减轻负载,几分钟才存一次盘,期间服务器一旦崩溃,进度就会丢失。

起因 角色状态每几分钟保存一次 → 结果 其间服务器崩溃或发生故障 → 画面表现 重新登录后回到几分钟前的状态(回档)

症状
吞操作/回档
因素
丢包
谁会遇到
全服, 特定地点/分线
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
重要事件(交易、获得稀有物品)立即存盘,记录变更日志。
运维团队要做的事
确认 DB 的 IOPS、CPU 余量能承受缩短存盘间隔后增加的写入。
监控图上
连接成批断开 · 连接数、回档反馈数
查看位置
把崩溃、故障时间与反馈回档的角色的最后存盘时间(游戏服务器的存盘日志或 DB 的修改时间列)放在一起对照
确认依据
回退到的时间点与崩溃前最后一次存盘时间一致,丢失的时长短于存盘间隔
排除依据
游戏服务器日志里记录存盘已完成却仍然回退,看“DB 故障切换”(db-failover)造成的数据丢失,或“复制延迟”(db-replica-lag)导致读到从库上的旧值
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

缓存雪崩 Cache stampede / thundering herd

ID db-cache-stampede · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

热门数据的缓存同时过期时,几千个请求会一下子涌向 DB。

起因 存在 Redis 等处的热门数据同时过期 → 结果 想重新生成同一份数据的请求一起涌向 DB → 画面表现 DB 过载,多个功能接连变慢或卡住

症状
操作延迟, 卡住, 连不上/无限加载
因素
停顿, 延迟
谁会遇到
全服
何时出现
固定周期, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
随机打散过期时间;只让一个请求去刷新,其余请求使用旧值。
运维团队要做的事
配置从节点和自动故障切换,避免 Redis 重启或故障时缓存被整体清空;确认 DB 留有缓存清空时也能扛住的余量。
监控图上
周期性尖峰 · 缓存命中率、DB 每秒查询数
查看位置
把 Redis INFO 的 keyspace_hits、keyspace_misses(命中率)、expired_keys、是否重启过(uptime_in_seconds)与 DB 每秒查询数叠加对照,并统计当时 DB 中同一查询同时在跑几个(MySQL SHOW PROCESSLIST,PostgreSQL pg_stat_activity)
确认依据
缓存未命中瞬间飙升的时刻,DB 查询数一起飙升,同时运行的查询大多是读取同一份数据的同一条查询。与热门键的过期周期或 Redis 重启时间重合
排除依据
缓存未命中与平时相同、只有 DB 查询增加,看“登录激增与 N+1 查询”(db-login-storm)或“大型批处理任务”(db-batch)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
Redis 重启或因故障导致缓存整体清空时,也会发生同样的情况。越是依赖缓存、把 DB 配得很小的架构,风险越大。
出处 6 条

长事务 Long-running transaction / MVCC purge lag

ID db-long-tx · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

一个事务长时间不结束,就会一直持有锁,DB 也没法清理(purge)旧版本数据,整体逐渐变慢。

起因 开着事务去等其他服务器的响应,或运营期间在主库上跑长时间的统计查询 → 结果 持有的锁不释放,待清理的旧版本数据不断堆积 → 画面表现 用到这些行的功能超时,几个小时内存盘、查询普遍变慢

症状
操作延迟, 吞操作/回档
因素
延迟, 停顿
谁会遇到
仅特定功能, 全服
何时出现
开得越久越严重, 偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
不要在事务里等待网络调用或用户输入,统计查询放到从库上。
运维团队要做的事
对长事务告警并强制终止,提供统计专用从库,监控 undo log 和死元组的增长。
监控图上
缓慢爬升 · undo log 长度(History list length)、死元组数
查看位置
MySQL 用 INFORMATION_SCHEMA.INNODB_TRX 的 trx_started 找出最老的事务,看 SHOW ENGINE INNODB STATUS 的 TRANSACTIONS 部分中的 History list length(尚未清理的 undo log 量)。PostgreSQL 看 pg_stat_activity 的 xact_start 和 state 为 idle in transaction 的会话,以及 pg_stat_user_tables 的 n_dead_tup
确认依据
存在已持续几分钟到几小时的事务,期间 History list length 或 n_dead_tup 持续上升;结束这个事务后,随着清理(purge、VACUUM)运行而下降
排除依据
没有长事务却普遍变慢,看“检查点/日志刷盘”(db-checkpoint)或磁盘问题
确认手段
运维工具即可确认(无需游戏代码)
深入了解
为了让读的一方能看到修改之前的样子,DB 会保留旧版本(MVCC)。最老的事务结束后这些记录才能删除,所以一个事务开着几个小时,MySQL 会堆积 undo log,PostgreSQL 会堆积 VACUUM 清理不掉的死元组(dead tuple)。SQL Server 则会因事务日志无法收缩而把磁盘占满。
出处 7 条

Redis 慢命令 Redis blocking commands (single-threaded)

ID db-redis-block · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

Redis 一次只处理一条命令,一条慢命令就会挡住后面所有请求。

起因 运营中用 KEYS 全量搜索,整体读取或删除含几百万元素的排行榜、列表 → 结果 这条命令结束前,其他所有请求都在等待(几十 ms 到几秒) → 画面表现 用到会话、排行榜、缓存的功能同时顿一下,登录变慢

症状
卡住, 操作延迟, 连不上/无限加载
因素
停顿, 延迟
谁会遇到
全服, 仅特定功能
何时出现
偶尔随机, 固定周期
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
用 SCAN 代替 KEYS,拆分大 key,删除用 UNLINK(后台删除),打散集中在同一秒的过期时间。
运维团队要做的事
监控慢命令日志(SLOWLOG),在生产服务器上禁用 KEYS 等危险命令,定期排查大 key,关闭 THP 并为 fork 预留内存,RDB、AOF 持久化放到从节点上做。
数值参考
普通命令不到 1 ms。一次处理几百万个元素时,可能要几百 ms 甚至几秒。
监控图上
偶发随机尖峰 · Redis 响应延迟、慢命令数
查看位置
用 SLOWLOG GET 查看超过 slowlog-log-slower-than 的命令;用 CONFIG SET latency-monitor-threshold 开启延迟监控(默认关闭)后,用 LATENCY LATEST、LATENCY DOCTOR 查看 fork、expire-cycle 等各类事件的延迟。再用 INFO 的 latest_fork_usec 和 redis-cli --bigkeys 确认 fork 耗时和大 key
确认依据
停顿时刻的 SLOWLOG 中有 KEYS 或整体操作大 key 的命令,或者 LATENCY 在同一时刻记录了几十 ms 以上的 fork、expire-cycle 事件
排除依据
SLOWLOG、LATENCY 都是空的,只有游戏服务器侧感觉慢,属于网络或游戏服务器内部的等待(SLOWLOG 只统计命令执行时间,不含与客户端之间的收发时间)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
为了生成持久化文件(RDB 快照)或重写 AOF 而复制进程(fork)的瞬间,Redis 也会停顿。在现在的服务器上大约每 1 GB 内存需要 10 ms,30 GB 就是 300 ms 左右。开启大页(THP)时,fork 之后每次写入都要整页复制大页(copy-on-write),停顿和内存占用都会大幅增加,所以通常会关闭 THP 并预留充足内存。同一秒内过期的键非常多时,Redis 也会因为忙着删除而短暂停顿。
出处 7 条

执行计划变化导致查询变慢 Query plan regression (stats, parameter sniffing)

ID db-plan-flip · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

代码没变,DB 却换了处理同一条查询的方式(执行计划),昨天 2 ms 的查询今天就变成几百 ms。

起因 统计信息自动更新、DB 重启、数据分布变化,让 DB 重新制定执行计划 → 结果 选中了不走索引的计划,同一条查询慢了几十到几百倍,连接被占住 → 画面表现 没有任何发布,某个功能的加载却突然变慢,其他请求也跟着等待

症状
操作延迟, 连不上/无限加载
因素
延迟, 停顿
谁会遇到
仅特定功能, 全服
何时出现
偶尔随机
负责方
主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
研发团队要做的事
结果行数随参数值差异很大的查询,拆开写或考虑使用计划提示(hint),把查询设计成一定能走索引。
运维团队要做的事
监控慢查询和执行计划记录,固定好的计划(SQL Server 查询存储等),管理统计信息的更新时间。
监控图上
某一时刻起台阶式上升 · 各查询的平均执行时间
查看位置
定期收集同类查询的平均耗时,观察趋势。MySQL 看 events_statements_summary_by_digest 的 AVG_TIMER_WAIT,PostgreSQL 看 pg_stat_statements 的 mean_exec_time(12 及以下为 mean_time)。变慢前后的执行计划用 EXPLAIN 或 PostgreSQL 的 auto_explain 对比,SQL Server 用查询存储的“回归查询”(Regressed Queries)视图对比
确认依据
在没有发布的时间点,某条查询的平均耗时台阶式上升几十倍,该时间点与统计信息更新、DB 重启重合,执行计划也已改变
排除依据
执行计划没变却变慢,是数据量增长、锁等待或磁盘问题,锁等待看“热点行锁竞争”(db-hot-row)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
SQL Server 会复用按第一次传入的参数值制定的计划(参数嗅探)。按只有几件物品的新角色制定的计划,用在有几万件物品的老角色身上就会大幅变慢,反过来的情况也很常见。重启清掉计划后恢复正常,过后又会再次变差。
出处 7 条

线上表结构变更(DDL)锁 Schema change lock (DDL / metadata lock)

ID db-ddl-lock · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

在服务运行中给表加列或加索引,仅仅因为一个短暂需要的锁,使用这张表的所有请求都可能被迫等待。

起因 通过热修复给线上表加列、加索引 → 结果 表结构变更在等之前开启的长事务,后来的所有请求又在等这个表结构变更 → 画面表现 使用这张表的功能(背包、邮件等)整体卡住并超时

症状
操作延迟, 吞操作/回档, 连不上/无限加载
因素
停顿
谁会遇到
仅特定功能, 全服
何时出现
偶尔随机, 刚登录/维护结束后
负责方
主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
研发团队要做的事
含表结构变更的热修复要与数据库运维协商时间,先发布在没有新列时也能正常运行的代码。
运维团队要做的事
设置较短的锁等待超时,失败就重试;在没有长事务时执行;使用在线变更工具;大表放到维护时间做。
监控图上
某一时刻起台阶式上升 · 锁等待会话数、这张表的查询延迟
查看位置
MySQL 统计 SHOW PROCESSLIST 中 State 为 Waiting for table metadata lock 的会话,用 sys.schema_table_lock_waits 找出阻塞它们的会话(blocking_pid)。PostgreSQL 看 pg_locks 中 granted 为 false 的请求和 AccessExclusiveLock,用 pg_blocking_pids() 找出阻塞它们的会话
确认依据
从开始变更表结构的时刻起,使用这张表的所有查询都堆积在锁等待中,排在最前面的是未结束的事务或表结构变更语句
排除依据
等待只集中在特定行上,同一张表的其他行处理正常,看“热点行锁竞争”(db-hot-row)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
变更表结构时,MySQL 会短暂持有元数据锁,PostgreSQL 会短暂持有最强的表锁。变更本身哪怕一瞬间就完成,只要前面有一个未结束的事务,后面的所有请求都会排队等待。
出处 8 条

L13 服务器架构与运维

13 个原因 · 完整版章节

经由网关/代理 Gateway / proxy hop

ID in-gateway · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发

在客户端和游戏服务器之间加一层中间服务器,每经过一次就多一段处理时间,这台服务器也会成为单点故障。

起因 客户端 ↔ 网关 ↔ 游戏服务器的结构 → 结果 中间服务器增加处理和排队时间,过载时所有人都受影响 → 画面表现 所有人 ping 上升,网关故障时经过它的玩家全部掉线

症状
操作延迟, 掉线
因素
延迟, 停顿
谁会遇到
全服
何时出现
人多的时候, 一直
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发
研发团队要做的事
服务器:网关可以扩到多台的结构;某台网关挂掉后重新连到其他网关,角色状态能直接接上的结构(会话重连)。客户端:网关断开时自动重连。
运维团队要做的事
网关水平扩容(增加台数),按网关监控 CPU、连接数、处理延迟。
数值参考
因为在同一数据中心内,平时每经过一次不到 1 ms。网关过载时会增加到几十到几百 ms。
监控图上
随人数/负载上升 · 网关处理延迟,网关 CPU、连接数
查看位置
网关的 CPU、连接数,网关 socket 的 Recv-Q(ss、netstat),以及经过网关前后的延迟差。经过服务网格的 HTTP、gRPC 调用,把 Istio 标准指标 istio_request_duration_milliseconds 按发送方(reporter=source)和接收方(reporter=destination)分开对比
确认依据
游戏服务器的处理时间不变,只有网关这一段的延迟增加,且此时网关 CPU 打满或 Recv-Q 堆积
排除依据
不经过网关的路径(直连、其他网关)同样慢,则是线路或游戏服务器侧
确认手段
运维工具即可确认(无需游戏代码)
深入了解
使用服务网格(如 Istio)时,每台服务器旁边挂着的 sidecar 代理(Envoy)也会多出一跳。服务之间的请求要依次经过发送方的 sidecar 和接收方的 sidecar,代理上加的日志、指标采集等功能越多,处理时间和等待时间就越长。
真实案例
Riot Games 2020: League of Legends 欧洲、巴西服务器的边缘主机过载
出处 7 条

场景切换(服务器间迁移) Zone / server handoff

ID in-zone-transfer · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

进入其他区域或副本时,要把角色数据交给另一台服务器,这个过程中会出现延迟和失败。

起因 进入副本、跨大陆移动,负责的服务器随之更换 → 结果 存盘 → 传递 → 加载;目标服务器拥挤或没有空闲副本实例时要排队 → 画面表现 加载时间长,进入失败,移动途中掉线

症状
连不上/无限加载, 卡住, 掉线, 拉回
因素
延迟, 停顿
谁会遇到
只有我, 特定地点/分线
何时出现
移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
减少迁移数据,提前预留目标服务器,失败时回到原来的位置。
运维团队要做的事
监控副本、场景服务器的空闲实例余量,高峰前备好足够台数。
监控图上
随人数/负载上升 · 场景切换耗时、失败次数
查看位置
服务器记录的迁移各阶段耗时(存盘、传递、加载)和失败原因,目标服务器的人数和空闲实例数
确认依据
在加载时间长、进入失败的反馈时刻,迁移耗时增加或失败集中出现,且目标服务器拥挤或空闲实例耗尽
排除依据
迁移很快完成,到达后才卡住,看“进入密集区域时实体生成激增”或客户端加载
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
没有加载过程的无缝大世界,跨过服务器边界时负责的服务器也会更换。在边界附近可能会顿一下,或出现拉回现象。
出处 1 条

级联故障 Cascading failure

ID in-cascade · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维

一个服务变慢,调用它的服务器都会因等待响应而被占住,连不相关的功能也会停摆。

起因 DB、认证等某一个服务变慢 → 结果 调用方服务器的线程和连接都在等待响应而被占住,失败请求的重试又增加负载 → 画面表现 看起来无关的功能也全部变慢或停摆

症状
卡住, 操作延迟, 连不上/无限加载
因素
停顿
谁会遇到
全服
何时出现
人多的时候, 偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·网络运维
研发团队要做的事
所有调用设超时,使用熔断器,按功能隔离(舱壁隔离,bulkhead),重试逐步拉长间隔并限制次数,健康检查的响应与繁重任务分开。
运维团队要做的事
负载均衡器的健康检查对失败次数和间隔留出余量,避免把只是暂时变慢的服务器立刻摘掉;限制同时被摘除的服务器数量。
监控图上
触顶后走平 · 各服务的响应时间、错误率,线程、连接的使用数
查看位置
把各服务的响应时间、错误率、重试次数按同一时间轴放在一个界面上,找出最先变慢的地方。在负载均衡器后面时,看目标响应时间(AWS ALB 为 TargetResponseTime)、目标返回的 5xx 数量(HTTPCode_Target_5XX_Count)、因异常被摘除的目标数(UnHealthyHostCount)
确认依据
某个服务的延迟先上升,随后调用它的一方线程、连接使用数顶到上限,错误扩散到其他服务,重试次数和被摘除的目标数同时增加
排除依据
多个服务在同一时刻一起变慢,先查公共资源(DB、网络、宿主机)故障
确认手段
运维工具即可确认(无需游戏代码)
深入了解
健康检查(确认服务是否存活的检查)也会放大级联。繁忙的服务器回应检查慢了,负载均衡器就会把本来正常的服务器摘掉,流量集中到剩下的服务器,下一台服务器也跟着变慢。
真实案例
Riot Games 2020: League of Legends 欧洲、巴西服务器的边缘主机过载
Riot Games 2021: League of Legends EUW 5 小时故障:一个辅助 DB 导致全服停摆
Roblox 2021: Roblox 73 小时故障:服务发现(Consul)集群的争用问题
AWS 2021: AWS us-east-1 内部网络拥塞
AWS 2025: AWS us-east-1 DynamoDB DNS 故障与漫长的恢复
出处 4 条

辅助服务器故障 Auxiliary service outage

ID in-subservice · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

聊天、组队、拍卖行这类与游戏服务器分开运行的服务器出故障时,只有对应的功能不能用。

起因 功能专用服务器变慢或挂掉 → 结果 只有该功能的请求没有响应 → 画面表现 聊天发不出去,组队邀请没反应,交易行无限加载(战斗正常)

症状
吞操作/回档, 连不上/无限加载
因素
停顿, 丢包
谁会遇到
仅特定功能
何时出现
偶尔随机, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
设计成即使失败游戏也能继续,按功能显示状态,不要把多个功能集中到一台中心服务器上。
运维团队要做的事
为每台辅助服务器配置健康检查和告警,做冗余和自动重启。
监控图上
连接成批断开 · 各功能的请求成功率,辅助服务器的连接数、健康检查
查看位置
聊天、组队、拍卖行等各辅助服务器的健康检查、进程状态和连接数,各功能的请求成功率、响应时间。在负载均衡器后面时,看目标组的 UnHealthyHostCount
确认依据
只有负责被反馈功能的服务器出现健康检查失败或连接数骤降,游戏服务器的 tick 和战斗正常
排除依据
多个功能同时停摆,则是统一中转这些功能的中心服务器,或级联故障
确认手段
运维工具即可确认(无需游戏代码)
深入了解
如果组队、公会、私聊、跨服务器移动全由一台中心服务器(世界服务器、管理服务器)中转,这台服务器一变慢,多个功能就会同时停摆。
出处 3 条

发布/重启 Deploy / rolling restart

ID in-deploy · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

为更新而重启服务器时,如果不迁移连接,这台服务器上的玩家都会掉线,关机前的存盘和随后的重连也会一下子挤在一起。

起因 发布热修复,按顺序重启服务器 → 结果 不把连接迁到其他服务器就直接关闭,这台服务器上所有玩家的存盘请求同时涌向 DB → 画面表现 没有预告的掉线,大量重连

症状
掉线, 连不上/无限加载, 操作延迟
因素
停顿
谁会遇到
全服, 特定地点/分线
何时出现
偶尔随机, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
排空功能(drain,只拦新连接,等现有玩家自行离开),把角色迁到其他服务器,关闭前分批存盘,重启后完成缓存加载和 JIT 预热再通知就绪,热重载在单独线程中提前读好数据,在两个 tick 之间一次性替换。
运维团队要做的事
发布工具逐台等排空完成后再重启,重启的服务器确认就绪(预热完成)后再接流量,提前公告发布时间。
数值参考
一台服务器上有 5,000 人时,关闭前几秒内会有 5,000 条存盘请求涌向 DB。
监控图上
连接成批断开 · 各服务器的连接数,DB 写入次数
查看位置
把发布工具的操作记录(各服务器的重启时刻)以竖线(标注)的形式叠加到连接数、断线数、DB 写入、登录请求的监控图上
确认依据
各服务器连接数在重启时刻逐台依次骤降,骤降前 DB 写入冲高,骤降后登录请求冲高
排除依据
断开时刻与发布、重启记录不重合,则是服务器崩溃或网络设备方面的问题
确认手段
运维工具即可确认(无需游戏代码)
深入了解
刚重新启动后的几分钟也会慢。缓存是空的,DB 查询集中涌来;Java、C# 服务器还没完成边运行边优化代码的过程(JIT 预热),同样的工作要花更多时间。不关服务器、重新读取脚本和数据表的方式(热重载)在读取期间 tick 也会停住,造成短暂停顿。
出处 3 条

弹性伸缩延迟 Autoscaling lag

ID in-autoscale · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

人一多就会自动增加服务器,但准备要几分钟,这段时间现有服务器处于过载状态。

起因 活动开始,连接数激增 → 结果 新服务器从启动到就绪要几分钟 → 画面表现 活动开始后的几分钟内出现慢动作、连不上

症状
慢动作, 连不上/无限加载
因素
停顿
谁会遇到
全服
何时出现
人多的时候, 刚登录/维护结束后
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
分线分流(已经在拥挤分线里的人无法迁到新服务器),缩短新服务器的启动和数据加载时间。
运维团队要做的事
活动前提前扩容,准备好已预热的备用服务器,缩容时等剩下的人离开后再关机。
数值参考
检测到负载需要 1 到几分钟(指标按几分钟的平均值来看),启动新服务器、读取游戏数据、填充缓存又要几分钟。
监控图上
开服/维护后激增 · 实例数,CPU 利用率,连接排队
查看位置
把弹性伸缩的活动记录(决定扩容的时刻、新实例开始提供服务的时刻)叠加到 CPU 利用率、连接数的监控图上。AWS 看 Auto Scaling 组指标(需开启才能看到)GroupDesiredCapacity(目标台数)、GroupPendingInstances(准备中)、GroupInServiceInstances(服务中)
确认依据
连接激增后的几分钟里,只有目标台数和准备中的实例增加,现有服务器 CPU 一直顶在上限,从服务中实例增加的时刻起才缓解
排除依据
新实例加入后仍然慢,则是服务器台数以外的原因(DB 等公共资源、级联故障)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
弹性伸缩主要用在登录、网关、副本这类只要让新服务器接新玩家就行的地方。缩容时也会出问题。凌晨人少时缩减服务器,如果不等剩下的人离开就关机,这些人就会掉线。
真实案例
AWS 2021: AWS us-east-1 内部网络拥塞
AWS 2025: AWS us-east-1 DynamoDB DNS 故障与漫长的恢复
出处 4 条

日志/监控过载 Logging / monitoring overhead

ID in-monitoring · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

出故障时日志会激增,同步发送日志的服务器会被日志拖得更慢。

起因 出错时日志、指标的发送量激增 → 结果 日志采集器处理不过来,同步发送的服务器只能等待 → 画面表现 故障时出现的一卡一卡、卡住,因日志而更加严重

症状
一卡一卡, 卡住
因素
停顿
谁会遇到
全服
何时出现
人多的时候, 偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维
研发团队要做的事
异步发送,采样,缓冲区满了就丢弃,相同的错误日志合并发送。
运维团队要做的事
按故障时的激增量规划日志采集器容量,设置采集器积压告警。
监控图上
偶发随机尖峰 · 日志发送量,日志采集器队列
查看位置
把服务器每秒日志行数、字节数和日志采集 agent 的队列、丢弃数与 tick 耗时放在一起看。有停住的线程,用 bcc offcputime -p 看是否在等日志写入、发送
确认依据
tick 冲高时日志量飙升到平时的几十倍,游戏线程的等待时间集中在日志写入、发送的调用栈上
排除依据
日志量与平时相同,或游戏线程没有在日志上等待,日志激增就只是故障的结果,要另外找最先报错的原因
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

服务器之间的时钟差 Clock skew between servers

ID in-clock-skew · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

每台服务器的时钟略有差异时,冷却、buff、活动开始的判定在不同服务器上就会对不上。

起因 时间同步停掉的服务器,时钟与其他服务器相差几百 ms 到几秒 → 结果 在服务器之间传递 buff 结束时刻这类绝对时间,判定就会对不上 → 画面表现 一移动 buff 就消失,或冷却又从头开始

症状
吞操作/回档
因素
延迟
谁会遇到
只有我
何时出现
移动中/切换地图时
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
服务器之间传递剩余时间,不传绝对时间。
运维团队要做的事
监控时间同步(NTP、chrony),设置服务器间时钟差告警。
数值参考
时间同步(NTP、chrony)正常时,同一数据中心的服务器之间通常在几 ms 以内。同步停掉,或虚拟机长时间停住后恢复,就会拉大到几百 ms 到几秒。
监控图上
缓慢爬升 · 各服务器的时钟偏移
查看位置
收集各服务器 chronyc tracking 的 System time(系统时钟与 NTP 时钟之差)、Last offset 和 Ref time(最后一次采用时间源测量值的时刻)并对比
确认依据
问题服务器的偏移比其他服务器大几百 ms 以上,或 Ref time 很久以前就停住了,而判定错乱只发生在进出这台服务器的移动中
排除依据
所有服务器的偏移都在几 ms 以内,则是游戏侧的时间计算,或客户端的时钟同步误差
确认手段
运维工具即可确认(无需游戏代码)
深入了解
单台服务器的时钟一下子向前或向后跳,看服务器操作系统层的“系统时钟跳变(NTP step)”。
出处 4 条

脚本/机器人过多 Bots and macros

ID in-bots · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维

机器人发请求的频率远高于真人,会吃掉服务器的处理能力。

起因 大量不停重复打怪、移动、交易的机器人登录 → 结果 服务器处理量和 DB 负载增加 → 画面表现 特定练级点或全服变慢(慢动作、操作延迟)

症状
慢动作, 操作延迟
因素
停顿
谁会遇到
全服, 特定地点/分线
何时出现
一直, 晚高峰
负责方
主责 研发团队·服务器开发 · 配合 运维团队·网络运维
研发团队要做的事
检测机器人,按账号、角色限制请求频率。
运维团队要做的事
按 IP 限制连接和请求频率(网吧、移动网络是多人共用一个 IP,要留出余量),用防火墙、WAF 封禁机器人网段。
监控图上
仅部分偏高 · 按账号、IP 统计的每秒请求数
查看位置
用游戏服务器日志查看按账号、角色统计的每秒请求数分布和排行。没有代码指标时,看防火墙、WAF 中按 IP 统计的请求数
确认依据
少数账号、IP 以真人不可能达到的频率不停请求,限制它们之后服务器负载明显下降
排除依据
请求在各账号间分布均匀,则是正常的人数增长(tick 超出预算、弹性伸缩延迟)
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

依赖外部服务 External dependencies (auth, billing, platform)

ID in-external · 主责 外部·外部 · 配合 研发团队·服务器开发

平台登录、支付、实名认证这类外部服务变慢或停摆时,流程会卡在那一步。

起因 外部认证、支付服务故障或延迟 → 结果 在该步骤等待响应 → 画面表现 无法登录,支付失败。已经在游戏中的人不受影响

症状
连不上/无限加载, 吞操作/回档
因素
停顿
谁会遇到
全服, 仅特定功能
何时出现
刚登录/维护结束后, 做特定操作时
负责方
主责 外部·外部 · 配合 研发团队·服务器开发
研发团队要做的事
外部调用设超时并给出友好提示,缓存认证结果,支付重试与补偿流程。
外部要做的事
向认证、支付、平台服务商确认故障并请求恢复,告知玩家是外部服务故障。
监控图上
某一时刻起台阶式上升 · 外部调用响应时间、错误率,登录成功数
查看位置
平台登录、支付、实名认证等各外部调用的响应时间、错误率、超时次数,以及服务商的状态页
确认依据
从登录、支付失败集中出现的时刻起,只有某个外部调用的错误、超时上了一个台阶并停在那里,服务商状态页上同一时刻有故障
排除依据
外部调用正常却登录不了,看登录服务器本身(线程池耗尽、DB)或操作系统的连接队列(backlog)
确认手段
需要游戏服务器/客户端的日志和指标
真实案例
Fastly 2021: Fastly CDN 全球大面积报错
AWS 2021: AWS us-east-1 内部网络拥塞
AWS 2025: AWS us-east-1 DynamoDB DNS 故障与漫长的恢复
出处 3 条

匹配/区域分配错误 Wrong region assignment (matchmaking / GeoDNS)

ID in-region-match · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维, 外部·外部

没有分到近的区域(region),被分到远区域的服务器,即使线路正常,也只有这个玩家 ping 一直偏高。

起因 GeoIP 数据错误,VPN,按队友平均 ping 给整个队伍分配,人数不够时扩大到远区域的规则,按 DNS 解析器位置分配 → 结果 明明有近的区域,却连到了海对面区域的服务器 → 画面表现 在多个区域部署服务器的游戏中,只有我(或只有我们队伍)ping 一直偏高,出现操作延迟、拉回、吞技能

症状
操作延迟, 拉回, 吞操作/回档
因素
延迟
谁会遇到
只有我, 特定地区/运营商
何时出现
一直, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维, 外部·外部
研发团队要做的事
服务器:用客户端测得的各区域 ping 来分配,不依赖 GeoIP;扩大到远区域的规则要设 ping 上限;队伍除了看平均值,也要看 ping 最高的队友;把分配的区域和当时的 ping 记入日志。客户端:用 UDP 测各区域 ping,随匹配请求一起发送;在界面上显示所连区域和 ping;提供手动选择区域的选项。
运维团队要做的事
如果用 DNS 选区域,确认权威 DNS 支持 EDNS Client Subnet(玩家使用的解析器不发送时,会按解析器位置分配);定期更新 GeoIP 数据库;给各区域服务器的接入日志标上 GeoIP 国家和 ASN,找出连到远区域的国家和运营商。
外部要做的事
引导玩家关掉 VPN、游戏加速器后重新连接;引导使用公司 DNS、海外 DNS 的玩家改用运营商 DNS 试试;请 GeoIP 服务商更正错误位置。
数值参考
首尔玩家没分到东京而分到美国西部区域时,ping 从约 30 ms 升到约 130 ms。GeoIP 在国家级别准确率约 99.8%,城市级别即使在美国,落在 50 km 以内的比例也只有约 66%;使用 VPN 时,查到的会是 VPN 服务器的位置。
监控图上
仅部分偏高 · 各玩家的 RTT(ping),分配区域的分布
查看位置
给各区域服务器接入记录(负载均衡器访问日志、VPC 流日志)中的客户端 IP 标上 GeoIP 国家和 ASN,按国家、运营商统计连到了哪个区域。单个玩家时,对比该玩家实际连接的区域和到近区域测得的 ping(由玩家测,或从该区域服务器对玩家 IP 跑 mtr)
确认依据
RTT 高的玩家、国家没有连近区域,连到了远区域;到近区域测得的 ping 很低
排除依据
已正确分到近区域 ping 仍高,则是路由绕行,或该玩家的线路、Wi-Fi
确认手段
运维工具即可确认(无需游戏代码)
深入了解
用 DNS 选区域的方式(基于地理位置或延迟的 DNS)看不到玩家本人的地址,只能根据玩家所用 DNS 解析器的地址推测位置。解析器不支持转发部分玩家地址的 EDNS Client Subnet 时,使用公司 DNS 或远处 DNS 的玩家会按解析器所在地分配。匹配系统也可能按队友 ping 的平均值判断队伍,或者等待时间一长就放宽 ping 标准,分到远区域。AWS GameLift Servers 中队伍 ping 的默认标准也是平均值,并举了把 ping 上限从 50 ms 放宽到 100 ms、200 ms 的配置例子。开着 VPN 的玩家,经过中转服务器增加的延迟(“经由 VPN/游戏加速器”)可能与远区域分配叠加,可以通过关掉 VPN 重新连接后分配的区域是否变化来区分。附近根本没有区域、只能连远区域的情况,看“传播延迟(物理距离)”。
出处 8 条

TLS 证书过期/配置错误 TLS certificate expiry / misconfiguration

ID in-cert · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·客户端开发

登录、API、补丁服务器的证书过期或缺少中间证书时,从那一刻起新建连接的客户端 TLS 连接都会失败。

起因 证书有效期已过,服务器发送时漏掉中间证书,或玩家设备的日期时间不对 → 结果 客户端证书校验失败,断开 TLS 连接 → 画面表现 登录、补丁阶段连不上/无限加载,只有商城这类 HTTPS 功能失败。已经在线的人大多不受影响

症状
连不上/无限加载, 吞操作/回档
因素
停顿
谁会遇到
全服, 仅特定功能, 只有我
何时出现
刚登录/维护结束后, 做特定操作时
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·客户端开发
研发团队要做的事
把证书错误记为与其他连接失败区分开的错误码并给出提示;日期错误时提示开启设备的自动日期时间;使用证书固定(pinning)时一并放入备用密钥,并与运维团队对齐证书更换计划。
运维团队要做的事
网络:在负载均衡器、CDN 上终止 TLS 时,监控托管证书的自动续期状态并对剩余天数告警(ACM 为 DaysToExpiry),保留验证用 DNS 记录。服务器/OS:在服务器上终止 TLS 时,自动化续期并在续期后重新加载配置,配置包含中间证书的完整证书链,从外部定期检查登录、API、补丁各地址的剩余有效期并告警。
数值参考
Let’s Encrypt 证书有效期 90 天,建议每 60 天续期;AWS Certificate Manager 会在到期前 45 天检查经 DNS 验证的证书并自动续期。自动续期悄无声息地失败时,恰好在到期时刻新连接会一下子全部被挡住。
监控图上
连接成批断开 · 登录成功数,TLS 握手错误数
查看位置
用 openssl s_client -connect HOST:443 -showcerts 查看服务器实际发送的证书列表,用 openssl x509 -noout -enddate 确认各证书的到期日(notAfter)。在负载均衡器上终止 TLS 时,看 TLS 协商错误数(AWS ALB、NLB 为 ClientTLSNegotiationErrorCount)和登录成功数
确认依据
到期日已过,或服务器发送的列表中缺少中间证书,错误开始增加的时刻与到期时刻或换证书的时刻重合
排除依据
证书列表和到期日正常却只有部分玩家失败,看这些玩家设备的日期时间,或旧版 OS 的根证书列表
确认手段
运维工具即可确认(无需游戏代码)
深入了解
缺少中间证书的配置,用 PC 浏览器打开可能看起来一切正常。浏览器会记住从其他网站拿到的中间证书来补上缺口,Android 应用这类没有这种记忆的客户端就会失败。证书有效期也在缩短。Let’s Encrypt 计划把默认有效期在 2027 年缩短为 64 天、2028 年缩短为 45 天,固定每 60 天续期的配置,遇到 64 天的证书只剩四天余量,遇到 45 天的证书则会过期。AWS Certificate Manager 对导入(import)的证书也不会自动续期,删掉验证用 DNS 记录也会续期失败。登录被挡住的样子与“DNS 故障/延迟”相似,但证书问题是在找到服务器地址之后、在 TLS 握手阶段失败,开始时刻与到期时刻或换证书的时刻重合。
出处 12 条

登录排队上限/重连保留不足 Login queue cap / no reconnect grace

ID in-login-queue · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

上线、维护结束后连接集中涌入时,登录排队达到上限,开始拒绝新的排队;正在排队的玩家只要短暂断开一下就会丢掉位置,回到队尾。

起因 想登录的人多于登录服务器一次能接纳的人数,于是设置排队;队伍太长时,为了保护服务器而拒绝新的排队 → 结果 队伍越长,等待时间越久,这期间 Wi-Fi、移动网络只要短暂断一下,就会丢掉排队位置 → 画面表现 连不上/无限加载,排队中报错并退出游戏,又要从队尾重新排

症状
连不上/无限加载, 掉线
因素
停顿
谁会遇到
全服, 只有我
何时出现
刚登录/维护结束后, 晚高峰
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
研发团队要做的事
服务器:把排队上限设在登录服务器实际能处理的量上;为排队中断开的玩家保留位置一段时间(重连保留);显示排队序号和预计等待时间;把队列长度、拒绝次数、排队中断开次数记为指标。客户端:排队中断开时不退出游戏,自动重连回原来的位置;重试间隔用指数退避加抖动打散。
运维团队要做的事
服务器/OS:上线前做压测,测出登录、大厅服务器的处理上限;上线时准备好能随时加上的备用机器;把排队指标与连接尝试次数放在同一张监控图上看。
数值参考
2021 年 FINAL FANTASY XIV 资料片上线时,每个逻辑数据中心的排队人数超过 17,000 人就拒绝新的排队(Error 2002)。排队中断开时,大厅服务器会等待几十秒到 1 分钟,在此期间重新连上就从队列中原来的位置继续排。
监控图上
触顶后走平 · 登录排队长度,因达到上限而拒绝的次数,排队中断开次数
查看位置
把登录、大厅服务器记录的队列长度、平均等待时间、因达到上限而拒绝的次数、排队中断开次数,与连接尝试次数放在同一张监控图上看
确认依据
上线、维护结束后队列长度触顶走平期间拒绝次数增加,排队中断开集中在 Wi-Fi、移动网络玩家身上
排除依据
队列很短但登录慢,看 DB(db-login-storm)或操作系统的连接队列(so-backlog)
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
登录集中导致 DB 变慢的情况,看“登录激增与 N+1 查询”;操作系统的连接队列溢出,看“连接队列(backlog)溢出”。这里讲的是游戏有意设置的登录排队在设计上的问题。排队上限是保护登录服务器的安全机制,不能去掉。尽早拒绝超出的请求,才能持续处理能处理的请求。关键在于减少拒绝和断开给玩家带来的损失;队伍越长,错误越集中到 Wi-Fi、移动网络这类线路不稳定的玩家身上。
真实案例
Square Enix 2021: FINAL FANTASY XIV 资料片上线时的拥挤与登录排队错误
出处 2 条

同步设计

16 个原因 · 完整版章节

收到服务器响应才播放表现(请求-响应方式) Request-response (no client-side feedback)

ID sy-request-response · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

按下按钮后,在服务器回复之前既没有动画也没有声音。ping 值直接就是反应速度。

起因 技能、移动、拾取都等服务器确认后才播放 → 结果 从按下那一刻起,在往返时间加 tick 等待的这段时间里毫无反应 → 画面表现 ping 150 ms 时,所有动作都慢 0.2 秒

症状
操作延迟
因素
延迟
谁会遇到
只有我
何时出现
一直, 做特定操作时
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:动画、声音、特效在按下时立即开始(预表现),只有结果(伤害、奖励)在服务器确认后显示;移动、普通攻击先预测并立即呈现;收到服务器的位置校正后,从该位置起重新应用尚未被确认的输入。服务器:用收到的输入自行计算移动,只有与客户端预测位置的差距超过阈值时才发送校正值。
数值参考
反应时间 ≈ ping + tick 间隔的一半 + 一帧。20 tick、ping 150 ms 时约 190 ms。
监控图上
一直偏高 · 从输入到表现开始的耗时,RTT(ping)
查看位置
在开发版本的客户端日志中记录按键时刻、第一个动画或声音开始的时刻、服务器响应到达的时刻,与游戏内 RTT 并排对照。用引擎的网络模拟(Unreal NetEmulation.PktLag)或测试服务器上 Linux 的 tc netem 加入延迟,改变 ping 反复测量
确认依据
表现开始总是与服务器响应到达在同一时刻,从输入到表现的耗时等于 RTT + tick 等待,并且随加入的延迟同步增加
排除依据
表现在按下时立即开始,只有伤害数字这类结果较晚,是正常设计。ping 很低时也慢出一个 tick 间隔以上,则是双重 tick 等待或客户端帧的问题
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
回合制、卡牌、放置类这类不需要快速反应的游戏,这种方式最简单也最安全。问题出在有实时操作的游戏里,连移动、普通攻击都这样做的时候。
出处 5 条

顺序往返多的协议(chatty) Chatty protocol / sequential round trips

ID sy-chatty · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

一次操作需要依次与服务器往返多次时,ping 就要乘以这个次数。

起因 打开商店 → 请求列表 → 确认价格 → 购买 → 刷新背包,每一步都单独请求 → 结果 收到上一个请求的回复后才发下一个请求 → 画面表现 ping 150 ms 时买一次东西要将近 1 秒。加载时间格外长

症状
操作延迟, 连不上/无限加载
因素
延迟
谁会遇到
仅特定功能, 只有我
何时出现
做特定操作时, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:修改协议,把多个步骤合并成一次请求和响应(例如在购买响应里一并带上更新后的背包)。客户端:提前获取需要的数据,UI 不等待结果。
数值参考
耗时 ≈ 往返次数 ×(ping + 服务器处理 + tick 等待)。5 次往返、ping 150 ms 时约 0.85~1 秒。
监控图上
一直偏高 · 各功能完成时间,一次操作的往返次数
查看位置
在服务器侧抓包(Wireshark),用测试账号做一次商店购买、登录这类操作,数一数请求与响应交替往返了几次,以及间隔多长。有服务器请求日志时,按会话 ID 归并,看请求数和每个请求的到达、响应时刻
确认依据
一个操作中,请求等上一个响应回来后才依次往返多次,完成时间大约是往返次数 × RTT,ping 越高的地区玩家,同一功能按比例越慢
排除依据
往返只有一两次,却有某个响应耗时很长,则是服务器处理或 DB 方面的原因。与 ping 无关、所有玩家同样慢,看服务器负载
确认手段
运维工具即可确认(无需游戏代码)
出处 1 条

技能不支持预输入 No input/spell queue

ID sy-no-queue · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

必须等服务器确认上一个技能结束后才能按下一个技能时,每次衔接中间都会插入一段往返时间。

起因 只有在“上一个技能确认后”才接受下一个技能的输入 → 结果 每两个技能之间都空出一段与 ping 相当的时间 → 画面表现 每次衔接之间都出现空当,ping 越高 DPS 越低

症状
操作延迟, 吞操作/回档
因素
延迟
谁会遇到
只有我, 仅特定功能
何时出现
做特定操作时
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:预输入时间窗,冷却结束前一段时间(例如 0.3~0.4 秒)内的输入也接受,并立即发给服务器。服务器:稍早到达的输入不要拒绝,在冷却结束的那一刻执行。
数值参考
冷却 1 秒的技能衔接中,ping 150 ms 时每两个技能之间至少空出 0.15 秒,同样时间内放出的技能减少 13% 以上。
监控图上
一直偏高 · 技能之间的空当,RTT(ping)
查看位置
在服务器日志中按角色记录技能冷却结束时刻、下一个技能请求到达时刻、执行时刻,把其间的空当与玩家 RTT 对比
确认依据
从冷却结束到下一个技能执行之间总是空着大约一个 RTT,ping 越高的玩家空当越长,同样时间内用出的技能越少
排除依据
空当与 ping 无关、保持固定,是公共冷却或动画时长的设计。空当只是偶尔猛然变大,看抖动、丢包或 tick 超出预算
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
例如《魔兽世界》设有预输入时间窗,并允许玩家在设置中调整。时间窗比往返时间长时,衔接之间几乎感觉不到 ping。
出处 1 条

被 ping 吃掉的短判定窗口 Timing window too short for latency + reaction

ID sy-short-window · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

闪避、弹反、格挡这类需要反应的时间很短时,ping 会把这段时间吃掉,出现躲不开的攻击。

起因 BOSS 攻击预警 0.5 秒、弹反判定 0.2 秒这样的短判定窗口 → 结果 预警看到得晚(下行延迟 + 插值),自己的输入也到得晚(上行延迟 + tick 等待) → 画面表现 明明躲开了却被打中,弹反被吞

症状
吞操作/回档, 操作延迟
因素
延迟
谁会遇到
只有我, 仅特定功能
何时出现
做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
研发团队要做的事
服务器:按服务器时间预约攻击预警并提前下发,把判定窗口延长相当于 ping 的时长(延迟补偿)。客户端:收到的预警按预约的服务器时间播放。
运维团队要做的事
在玩家多的地区附近部署服务器(地区服务器),从根本上降低 ping。
数值参考
ping 150 ms、插值 100 ms 时,预警显示到自己画面上约需 0.18 秒,自己的输入到达服务器约需 0.1 秒。再加上人的反应时间 0.25 秒,0.5 秒的预警几乎不可能躲开。
监控图上
仅部分偏高 · 闪避、弹反失败率(按 ping 区间)
查看位置
在服务器日志中记录判定窗口的开始、结束时刻,玩家输入到达服务器的时刻,以及该玩家的 RTT,把失败率按 ping 区间(例如每 50 ms 一档)分开看
确认依据
ping 越高的区间失败率明显越高,失败的输入在判定窗口结束后不久(RTT 加插值时间以内)才到达
排除依据
失败率与 ping 区间无关、大致相同,是机制难度问题。输入在判定窗口内到达却仍判失败,看判定代码或服务器校验
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

没有延迟补偿的判定 Server-now hit validation

ID sy-no-lagcomp · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

服务器只按“当前服务器上的位置”判定命中时,自己看到的画面就会与判定结果对不上。

起因 自己画面上的对手是约 0.2 秒前的位置(ping 150 ms、插值 100 ms 时) → 结果 服务器按当前位置判定,自己瞄准的地方对手已经不在了 → 画面表现 明明打中了却判定未命中。打移动目标必须打提前量

症状
吞操作/回档
因素
延迟
谁会遇到
只有我
何时出现
做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:回溯到攻击者当时看到的时间点再判定(延迟补偿),或改为锁定目标的方式。客户端:攻击时一并发送自己当时看到的时间点(正在插值的服务器时间)。
监控图上
仅部分偏高 · 移动目标命中率(按 ping 区间)
查看位置
在服务器判定日志中同时记录攻击时刻、攻击者画面上的目标位置(客户端发来的值)、判定所用的服务器端目标位置、攻击者 RTT。在开发版本中把服务器判定所用的位置叠画到客户端画面上,一眼就能看出来
确认依据
未命中的判定中,两个位置的差约等于目标速度 ×(攻击者 RTT + 插值时间),ping 越高,只有移动目标的命中率下降
排除依据
静止目标也打不中,是判定框、碰撞检测的问题。做了回溯仍然对不上,看客户端是否把插值时间错误地告诉了服务器
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

延迟补偿过度 Excessive lag compensation

ID sy-lagcomp-overreach · 主责 研发团队·服务器开发

按攻击者的视角回溯得太远时,被打的一方明明已经躲好了,还是会被打中。

起因 为照顾 ping 高的攻击者,服务器大幅回溯后判定 → 结果 在被打者的画面上,自己早已躲到掩体后 → 画面表现 “躲到墙后还被打中”,ping 高的人反而占优

症状
吞操作/回档
因素
延迟
谁会遇到
只有我, 特定地区/运营商
何时出现
做特定操作时
负责方
主责 研发团队·服务器开发
研发团队要做的事
设置回溯上限(例如 200~250 ms),ping 比这更高的攻击者只回溯到上限,剩下的部分由他自己打提前量。
监控图上
仅部分偏高 · 每次命中的回溯时长(按攻击者 ping)
查看位置
在服务器判定日志中为每次命中记录回溯时长、攻击者 RTT、被打者进入掩体的服务器时间。在开发版本中把回溯后的判定框画到画面上(Source 引擎用 sv_showlagcompensation)
确认依据
“躲到墙后还被打中”反馈对应的命中集中在回溯时长长的攻击者身上,回溯时长没有上限,随攻击者 ping 一路增加
排除依据
回溯时长很短的命中也出现墙后中弹,是判定框、碰撞检测的问题。被打者 ping 高时,是他的移动很晚才到达服务器造成的
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
回溯判定以“开枪者优先”为原则。也有人提出“被打者优先”的例外:被打的一方在自己画面上已经进入安全位置时就不回溯。
出处 3 条

客户端权威 Client-authoritative results

ID sy-client-auth · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

各自决定自己的结果,自己的画面很流畅,但结果会与别人的画面对不上,也容易被外挂利用。

起因 位置、命中由客户端决定,服务器只负责转发 → 结果 两个人都说自己先打中了对方,服务器无法验证 → 画面表现 对手瞬移、穿墙,“我打中了却没算”

症状
瞬移, 吞操作/回档
因素
延迟
谁会遇到
全服
何时出现
一直
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:重要结果(命中等)由服务器自己校验,移动做速度和距离检查。客户端:收到服务器拒绝或校正的结果后,回退到该值。
监控图上
一直偏高 · 不可能的移动速度、互相矛盾的命中报告数
查看位置
在服务器上原样记录客户端报告的位置和命中,用连续的位置报告计算移动速度,统计超过最大速度的报告,以及两人互称先打中对方的报告
确认依据
服务器不加校验就把报告转发给其他客户端,不可能的速度或互相矛盾的命中报告与版本更新、地区无关,持续出现
排除依据
服务器自己计算或校验结果时,就不是这个原因。这时的瞬移看丢包或插值缓冲
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

帧同步中等待最慢的玩家 Lockstep waits for the slowest peer

ID sy-lockstep · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

所有人一起计算同一个逻辑帧的结构下,只要有一个人的输入晚到,所有人都得等。

起因 每个逻辑帧都要集齐所有玩家的输入才能计算 → 结果 某个人的输入因抖动、丢包而晚到 → 画面表现 所有人同时顿一下,严重时弹出“正在等待玩家”窗口

症状
卡住, 一卡一卡, 操作延迟
因素
抖动, 丢包, 停顿
谁会遇到
特定地点/分线
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:按 ping 自动调整输入延迟,只把落后的人暂时踢出同步,其余人不等他继续游戏。客户端:使用设定的输入延迟;没有中转服务器的 P2P,输入延迟调整和落后玩家的处理也由主机客户端负责。
数值参考
输入延迟设得比“输入到达对方的时间 + 抖动”短,卡住就会更频繁。到达时间在双方直连时约为 ping 的一半,经过中转服务器时约为两人 ping 之和的一半。
监控图上
偶发随机尖峰 · 逻辑帧等待时间,各玩家输入到达延迟
查看位置
每个逻辑帧记录各玩家输入到达的时刻,以及逻辑帧停下来等待的时间,查看停住的逻辑帧在等谁的输入。有中转服务器时,也可以在服务器侧抓包,按各玩家输入包的到达间隔来看
确认依据
每个停住的逻辑帧,都是同一个人的输入晚于输入延迟到达,且那一刻这个人的抖动、丢包冲高
排除依据
输入都按时到了却仍停住,是最慢那台 PC 的计算时间或服务器处理的问题。没有停顿、只是两边画面的结果不同,属于计算结果不一致(desync),看“指令同步的路径计算不一致”
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

回滚网络代码预测失败 Rollback misprediction

ID sy-rollback · 主责 研发团队·客户端开发

先预测对手的输入并显示出来,猜错了就回滚重新计算。ping 越高,回滚的幅度越大。

起因 对手改变了输入(与预测不同) → 结果 实际输入晚到约半个 ping,就要回滚这么长时间重新计算 → 画面表现 对手的动作跳过几帧或突然改变

症状
瞬移
因素
延迟, 抖动
谁会遇到
只有我
何时出现
做特定操作时, 偶尔随机
负责方
主责 研发团队·客户端开发
研发团队要做的事
混入 1~3 帧输入延迟以减小回滚幅度,设置回滚上限。
数值参考
ping 100 ms(单向 50 ms)时,60 FPS 下约回滚 3 帧。设置 2 帧输入延迟后减少到 1 帧。
监控图上
偶发随机尖峰 · 回滚帧数,RTT(ping)
查看位置
客户端在每次回滚时记录回滚的帧数、当时的 RTT、输入延迟设置、回滚和重新计算的耗时
确认依据
反馈对手动作跳变的时刻回滚帧数大,平均回滚幅度约为(单向延迟 − 输入延迟)÷ 帧时长,ping 越高越大
排除依据
回滚幅度很小却出现一卡一卡,是重新计算超过一帧时长的性能问题。回滚之后两边画面的结果仍然不同,是计算结果不一致(desync)
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

没有时间戳、一到就播放 Events played on arrival (no timestamps)

ID sy-no-timestamp · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

服务器事件不附带发生时刻、一收到就播放时,网络抖动会原封不动地让表现时机忽快忽慢。

起因 “开始攻击”“播放特效”事件一到就执行 → 结果 每个数据包到达时间不同,间隔忽长忽短 → 画面表现 连续攻击动作时快时慢,BOSS 技能时机每次都不一样

症状
一卡一卡, 快进
因素
抖动
谁会遇到
只有我
何时出现
一直
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:按事件附带的时间播放(定时事件、插值缓冲)。服务器:给事件附上发生时刻(服务器时间)再发送。
监控图上
偶发随机尖峰 · 事件播放间隔,数据包到达间隔
查看位置
把服务器日志中的事件发生时刻与客户端日志中的到达、播放时刻按事件编号对齐,比较间隔。在开发版本中加入抖动(tc netem 的抖动值、Unreal 网络模拟的最小/最大延迟)来复现
确认依据
服务器上的发生间隔均匀,播放间隔却原样跟着到达间隔忽长忽短
排除依据
到达间隔均匀,播放却忽快忽慢,是客户端帧的问题(帧耗时尖峰)。服务器上的发生间隔就已经在波动,则是 tick 超出预算
确认手段
需要游戏服务器/客户端的日志和指标
出处 5 条

双重 tick 等待 Double tick quantization

ID sy-double-tick · 主责 研发团队·服务器开发

请求攒到下一个 tick 才处理,结果又在再下一个 tick 才发出,tick 间隔就被叠加了两次。

起因 收到的请求在下一个 tick 处理 → 结果 处理结果也攒到下一个发送 tick 统一发出 → 画面表现 线路 ping 很低,反应却稳定地慢约 1.5 倍 tick 间隔。10 tick 服务器平均 0.15 秒,最差 0.2 秒

症状
操作延迟
因素
延迟
谁会遇到
全服
何时出现
一直
负责方
主责 研发团队·服务器开发
研发团队要做的事
在处理的那个 tick 里直接发出响应,提高 tick 率,重要响应立即发送。
数值参考
10 tick 服务器一个 tick 是 100 ms,光 tick 等待就要多出平均 150 ms、最差 200 ms。只等一次,平均是 50 ms。
监控图上
一直偏高 · 从请求到达到发出响应的时间
查看位置
在服务器侧抓包,测试账号多次做同一动作(例如使用物品)时,测量请求包到达时刻与对应响应包发出时刻的间隔。有服务器日志时,看请求到达时刻、处理所在的 tick 编号、发出响应的时刻
确认依据
在服务器内部花的时间平均约为 tick 间隔的 1.5 倍,最长约 2 倍,与 RTT 无关、保持稳定
排除依据
服务器内部时间在平均 tick 间隔的一半上下,说明 tick 等待只有一次。比 tick 间隔长且忽长忽短,看 tick 超出预算
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

过于严格的服务器校验 Over-strict server validation

ID sy-strict-check · 主责 研发团队·服务器开发

服务器对移动速度、冷却、射程检查得过于严格时,连因抖动扎堆到达的正常输入也会被拒绝。

起因 “一个 tick 内可移动的距离”“冷却容差 0 ms”这类严格标准 → 结果 因抖动,两条指令挤在同一个 tick 到达,被判为违规 → 画面表现 拉回,冷却好了技能却被拒绝

症状
拉回, 吞操作/回档
因素
抖动
谁会遇到
只有我
何时出现
偶尔随机, 移动中/切换地图时
负责方
主责 研发团队·服务器开发
研发团队要做的事
改用累积额度(令牌桶)方式检查,按 ping 和抖动留出余量。
监控图上
偶发随机尖峰 · 服务器校验拒绝、位置校正次数
查看位置
在服务器日志中为每次校验拒绝、位置校正记录原因、该玩家在那个 tick 到达的指令数、与上一条指令的到达间隔
确认依据
拒绝、校正集中在一个 tick 内挤进 2 条以上指令的时刻,而按几秒合计的移动量、使用次数都在规则之内
排除依据
按几秒合计仍超出规则,可能是真的超速或作弊。拒绝集中在特定运营商和晚间时段,看“集中在特定运营商玩家身上的校验误判”
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

主机(房主)结构 Listen server / host advantage

ID sy-host · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

由某个玩家的电脑充当服务器时,这个人的线路和电脑性能决定了所有人的体验。

起因 房主的电脑充当服务器(P2P、Listen Server) → 结果 房主线路或电脑慢,就会波及所有人;房主自己的 ping 为 0 → 画面表现 只有房主占优,房主一退出所有人都卡住或掉线

症状
一卡一卡, 卡住, 掉线
因素
延迟, 停顿
谁会遇到
特定地点/分线
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
研发团队要做的事
服务器:改为由专用服务器负责判定;在此之前,匹配时挑线路、电脑好的人当房主。客户端:支持主机迁移,匹配时测量与其他参与者之间的 ping、上行速度、电脑性能并上报。
运维团队要做的事
为专用服务器准备服务器设备和实例,部署在玩家多的地区附近。
监控图上
仅部分偏高 · 按房主统计的卡顿反馈、掉线次数
查看位置
在对局日志中记录房主(主机)的上行速度、各参与者到房主的 RTT、房主电脑的帧耗时、房主退出的时刻,把卡顿和掉线反馈按房主归类来看。玩家也可以用同一批人只换房主再打一局来确认
确认依据
卡顿、掉线集中在特定房主的房间,该房主上行速度低或帧耗时长时,所有参与者一起变差;没有主机迁移时,房主退出的那一刻所有人都掉线
排除依据
与房主无关、只有同一地区的参与者差,是线路或路径的问题。采用专用服务器结构时,不是这个原因
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

预表现后被服务器拒绝 Client-side feedback rejected by server

ID sy-optimistic-reject · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

在自己画面上先显示出来的命中、技能,服务器事后不认可时,明明看到的结果就当没发生过。

起因 命中特效、技能动作在服务器确认前先播放(预表现) → 结果 服务器重新核对射程、目标位置、冷却、资源后拒绝 → 画面表现 血溅出来了却没有伤害,技能动作放出去了却没效果,只有冷却在转

症状
吞操作/回档, 拉回
因素
延迟
谁会遇到
只有我, 仅特定功能
何时出现
做特定操作时
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:只有伤害数字、死亡、奖励这类需要确定的部分按服务器结果显示;常见的拒绝原因先在本地检查;被拒绝时退回冷却和资源,并显示原因。服务器:射程、目标位置检查按 ping 留出余量;拒绝响应中附上原因;按技能收集拒绝率指标。
数值参考
拒绝响应要在按下后再过 ping + tick 等待的时间才会到。ping 150 ms 时,约 0.2 秒里玩家都以为“打中了”。
监控图上
仅部分偏高 · 各技能的服务器拒绝率(按 ping 区间)
查看位置
在服务器上收集各技能的拒绝率和拒绝原因(射程、目标位置、冷却、资源),按玩家 RTT 区间拆分。在客户端记录预表现过的动作被拒绝的次数
确认依据
拒绝集中在特定技能以及射程、目标位置类原因上,ping 越高拒绝率越高
排除依据
拒绝原因是冷却、资源且与 ping 无关,看客户端和服务器的数据值(冷却、消耗)是否不同。没有拒绝、表现却在服务器响应之后才开始,是“收到服务器响应才播放表现(请求-响应方式)”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
预表现是掩盖 ping 的最好方法。不过客户端和服务器用来判断的信息(对手位置、剩余资源)差别越大,被拒绝就越频繁。按技能收集拒绝率指标,就容易找到判定对不上的地方。
出处 2 条

指令同步的路径计算不一致 Command sync with divergent pathing

ID sy-path-mismatch · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

只互传“去这里”,路径由双方各自计算时,计算稍有差异,角色或怪物就会走上另一条路,然后被拽回原位。

起因 点击移动、怪物追击时只发送目的地,路径由客户端另行计算 → 结果 地形数据差异、与其他角色碰撞、计算顺序不同,导致走了和服务器不同的路径 → 画面表现 怪物穿墙走着走着突然被挪走,点击移动的角色像打滑一样转向

症状
瞬移, 拉回
因素
延迟
谁会遇到
特定地点/分线, 只有我
何时出现
移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:把路径的中间点(路点)一并发送,定期对齐位置。客户端:偏差平滑收敛,使用与服务器相同的地形数据。
监控图上
偶发随机尖峰 · 各实体的位置校正次数、距离
查看位置
记录每个实体在服务器下发位置与客户端计算位置之间的差,把发生校正的坐标标到地图上。把双方的路径结果或位置汇总成校验和,定期对比,就能找出开始出现偏差的时间点
确认依据
校正集中在特定地形(门槛、狭窄通道、斜坡)或拥挤处,网络指标正常的玩家也在同一位置反复出现
排除依据
与地点无关、只在丢包或抖动冲高的瞬间校正,是线路问题。同一只怪物在多个人的画面上一起跳动,看是不是“怪物控制权在慢的客户端上”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
点击移动、Tab 锁定的游戏对 ping 不敏感,原因之一就是这种方式。代价是无法保证双方结果一致,所以一定要有定期对齐位置的机制。浮点运算的结果可能因 CPU 种类、编译器及其优化设置(包括 Debug 与 Release 版本的差异)而略有不同。在帧同步、回滚这类只互传输入、假定双方计算结果完全相同的结构中,这些微小差异会累积,可能导致两边画面的游戏状态分叉(desync)。
出处 6 条

快照发送频率低 Low snapshot / update rate

ID sy-low-send-rate · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

服务器每秒只发几次位置更新(快照)时,插值缓冲就得相应拉长,看到的其他角色也就是更久以前的状态。

起因 为节省流量,位置更新每秒只发 5~10 次 → 结果 要画得流畅,缓冲需设为包间隔的 2 倍(200~400 ms);设得短,只要漏掉一个包就会卡住 → 画面表现 对手转向看起来很晚,与判定对不上。缓冲短时一卡一卡,丢包时瞬移

症状
一卡一卡, 瞬移, 吞操作/回档
因素
延迟, 丢包
谁会遇到
全服
何时出现
一直
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:近处或战斗中的目标多发,远处目标少发;只发变化的部分(增量压缩),减小单次数据量,提高发送频率。客户端:按包间隔自动调整插值缓冲长度。
数值参考
每秒 10 次时包间隔 100 ms,缓冲 200 ms。再加上 ping 150 ms 的单向延迟 75 ms,看到的对手约是 0.3 秒前的状态。
监控图上
一直偏高 · 各客户端的包到达间隔,插值缓冲长度
查看位置
在服务器侧抓包,只过滤发往某个玩家的流,用 Wireshark 的 I/O Graphs 看每秒包数和间隔。有游戏侧日志时,一并看各实体的更新间隔和客户端插值缓冲余量(距离下一个快照到达还剩的时间)
确认依据
位置更新一直很稀,每秒 5~10 次(间隔 100~200 ms),插值缓冲设得超过 200 ms,或缓冲余量经常降到 0
排除依据
更新发得很密,只有到达间隔在波动,看抖动、丢包。人多时只有远处实体收得稀,看“按连接分配的发送预算/优先级”
确认手段
运维工具即可确认(无需游戏代码)
出处 4 条

只有部分人遇到的问题

24 个原因 · 完整版章节

网络差的玩家在别人画面上快进移动 Laggy player seen by others (bursty inputs)

ID pt-slow-burst · 主责 研发团队·服务器开发 · 配合 外部·外部

线路差的人,输入会忽多忽少地扎堆到达服务器。服务器每个 tick 收到多少就应用多少时,在别人眼里,这个角色会顿一下,然后一下子走出好几步。

起因 网络差的人的移动指令,有的 tick 到 0 条,有的 tick 一次到 2~3 条 → 结果 服务器在收到的那个 tick 一次性全部应用,该角色的位置呈台阶式变化 → 画面表现 在别人画面上只有这个角色顿一下,然后一下子快进移动。其他都正常

症状
快进, 瞬移
因素
抖动, 丢包
谁会遇到
只有某个角色看起来异常, 特定地区/运营商
何时出现
一直, 移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 外部·外部
研发团队要做的事
用每个玩家的输入缓冲把指令均匀分摊应用,按输入序列号以原本的间隔应用;只加长别人画面上的插值缓冲是不够的(因为服务器上的位置记录本身就是台阶状)。
外部要做的事
引导网络差的玩家改用有线连接,检查 Wi-Fi 和路由器。
数值参考
抖动 80 ms 时,在 20 tick(50 ms)服务器上,每个 tick 的指令数会在 0~3 条之间波动。
监控图上
仅部分偏高 · 各玩家每 tick 应用的指令数,各玩家的抖动
查看位置
在服务器侧抓包,只过滤被反馈玩家发出的包,按 tick 间隔(例如 50 ms)统计每次到达几个,与其他玩家对比。有服务器日志时,按玩家看每个 tick 应用的移动指令数和输入序列号
确认依据
只有被反馈玩家的包在每个 tick 0 个与 2~3 个之间来回、扎堆到达,该玩家的抖动、丢包高,其他玩家的包到达均匀。该玩家换成有线后情况减轻
排除依据
多个角色同时快进移动,是服务器 tick 延迟或观看者一侧的线路问题。到达和应用都均匀,却只有这个角色看起来在跳,是观看者一侧的插值、显示问题
确认手段
运维工具即可确认(无需游戏代码)
深入了解
在服务器权威结构中,这是正常行为。一个网络差的人的卡顿,在别人眼里只表现为“这个人动作怪异”,不会影响其他人的操作或怪物的移动。不过与这个人直接交互的事情(交易、队伍机制、PvP 判定)会一起变慢。
出处 3 条

到达即处理的服务器造成的快进 Event-driven processing of bursty inputs

ID pt-event-server · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

在包一到就立即处理并广播的服务器上,网络差的人扎堆到达的动作会被接连立即执行。

起因 网络差的人的技能、移动请求扎堆到达 → 结果 服务器一收到就按顺序执行,并立即通知所有人 → 画面表现 在别人眼里,这个人一瞬间放出好几个技能,或像快进一样移动

症状
快进
因素
抖动
谁会遇到
只有某个角色看起来异常
何时出现
做特定操作时, 一直
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:按动作附带的输入时间的间隔执行(时间只在允许范围内采信);或者不拒绝扎堆到达的动作,按最小间隔(公共冷却)拉开后依次执行;不要只按到达时间做冷却检查(会吞掉正常输入)。客户端:给动作附上输入时间再发送。
监控图上
仅部分偏高 · 各玩家的动作执行间隔
查看位置
在服务器日志中按玩家记录动作的到达时刻、执行时刻、客户端附带的输入时间(如果有),比较执行间隔和输入间隔。同时在服务器侧抓包,看该玩家包的到达间隔
确认依据
输入间隔正常,服务器到达、执行间隔却挤在几 ms 之内,挤在一起的时刻与其他人反馈快进的时刻重合
排除依据
输入时间的间隔本身就挤在一起,是客户端或脚本方面的问题。服务器执行间隔均匀,只在别人画面上看起来挤在一起,是观看者的线路问题
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

每个玩家的输入缓冲大小 Per-player server input buffer (jitter buffer)

ID pt-input-buffer · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

服务器为每个人先攒一点输入,每个 tick 取出一个使用时,别人看起来很流畅,但本人动作在服务器上确定的时间也会相应推迟。

起因 服务器把网络差的人的输入攒在缓冲里,每个 tick 应用一个 → 结果 缓冲小,就经常被取空,该角色原地站住,或由服务器按最后一个输入推测移动;缓冲大,本人的输入就确定得晚 → 画面表现 缓冲小,别人看到他顿一下;缓冲大,本人的技能结果出得晚(操作延迟)

症状
一卡一卡, 操作延迟
因素
抖动
谁会遇到
只有某个角色看起来异常, 只有我
何时出现
一直
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:按每个人的线路状况自动调整缓冲大小,积压时一次取两个来追赶,指示缓冲经常被取空的玩家的客户端提前发送输入。客户端:按服务器指示把输入稍微提前发送(客户端时间调整)。
数值参考
各游戏不同,通常是 1~3 个 tick 的量。VALORANT 在 128 tick 服务器上把服务器缓冲保持得更短,平均半帧(约 4 ms)。常见做法是自适应,只给抖动大的人加大缓冲。
监控图上
仅部分偏高 · 各玩家的输入缓冲长度、取空次数
查看位置
在服务器上按玩家记录每个 tick 输入缓冲中剩余的输入数、缓冲取空后按最后一个输入推测填补的次数、从输入到达到应用的耗时
确认依据
缓冲小的人取空次数多,此时在别人画面上短暂停顿;缓冲大的人,从输入到应用的耗时增加了缓冲长度那么多
排除依据
缓冲几乎没被取空,别人画面上却看到一卡一卡,是观看者一侧的插值问题。缓冲很短操作延迟仍然大,是 RTT 本身或双重 tick 等待
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

集中在特定运营商玩家身上的校验误判 Anti-cheat / movement validation false positives on bad ISPs

ID pt-isp-validation · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维

使用抖动大的线路的人,输入会扎堆到达,经常触发服务器的速度、冷却检查。

起因 特定运营商、地区的线路晚上抖动变大 → 结果 服务器把扎堆到达的正常输入判为超速、冷却违规 → 画面表现 只有这家运营商的玩家出现拉回、技能被拒,严重时被服务器踢出而掉线

症状
拉回, 吞操作/回档, 掉线
因素
抖动
谁会遇到
特定地区/运营商, 只有我
何时出现
晚高峰, 移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·网络运维
研发团队要做的事
检查改用跨几秒的累积额度,参考线路状况(ping、抖动)放宽标准,强制断开前加一个警告阶段,用每个玩家的输入缓冲把扎堆到达的输入均匀分到每个 tick,从源头减少误判。
运维团队要做的事
按时段查看各运营商的丢包率、抖动分布并同步给研发团队,检查该运营商段的路径(双向 mtr),必要时调整路由或向运营商升级处理。
监控图上
特定时段偏高 · 各运营商(ASN)的校验拒绝、强制断开次数,各运营商的抖动
查看位置
给服务器的校验拒绝、校正、强制断开日志标上接入 IP 所属运营商(ASN)和时刻,按运营商、时段统计。运维团队在同一时段向该运营商方向跑双向 mtr,看抖动和丢包
确认依据
拒绝、强制断开集中在特定运营商,晚上增多,同一时段该运营商的抖动也高,而按几秒合计的移动量在规则之内
排除依据
与运营商无关、只有特定账号反复出现,可能是真的作弊。所有运营商一起增加,是服务器 tick 积压、指令被集中应用的服务器侧原因(tick 超出预算)
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

一个网络差的队友与 BOSS 机制 One laggy member in a synchronized mechanic

ID pt-raid-member · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

在需要所有人在规定时刻一起反应的团本机制中,一个网络差的人反应晚了,就会让整个队伍失败。

起因 “所有人同时散开”“一个人按按钮”这类团队机制 → 结果 网络差的人预警看到得晚,输入也到得晚 → 画面表现 因为这一个人而团灭,其他队友觉得“都怪那个卡的人”

症状
吞操作/回档, 操作延迟
因素
延迟
谁会遇到
特定地点/分线, 只有某个角色看起来异常
何时出现
人多的时候, 做特定操作时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:机制判定窗口按 ping 留出余量,预警按服务器时间提前下发,设计上避免一个人失败就导致团灭。客户端:收到的预警按服务器时间播放。
监控图上
仅部分偏高 · 导致机制失败的各玩家 RTT
查看位置
在服务器机制日志中记录导致失败的玩家、他的输入到达时刻、判定窗口、他的 RTT 和丢包
确认依据
导致团灭的输入大多来自同一个人,此人的 RTT 明显高于队伍平均值,输入在判定窗口结束后不久才到达
排除依据
失败在队友之间分布均匀,是判定窗口本身太短的问题,看“被 ping 吃掉的短判定窗口”。网络差的人的输入在判定窗口内到达却仍然失败,看服务器判定代码
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

怪物控制权在慢的客户端上 Monster movement delegated to a player client

ID pt-mob-control · 主责 研发团队·服务器开发

有的游戏为减轻服务器负载,把怪物的移动计算交给附近某个玩家的客户端。这个人的线路差时,这只怪物在所有人的画面上都会动得很怪。

起因 服务器把怪物的移动计算交给最近(或最先到)的玩家的客户端 → 结果 负责的人上报结果晚到或扎堆到达服务器 → 画面表现 只有这只怪物在周围所有人的画面上顿一下然后瞬移。负责的人自己的画面上正常

症状
瞬移, 一卡一卡, 快进
因素
抖动, 丢包
谁会遇到
只有某个角色看起来异常, 特定地点/分线
何时出现
一直, 偶尔随机
负责方
主责 研发团队·服务器开发
研发团队要做的事
把控制权交给线路好的人(按 ping、丢包),上报中断时服务器立即收回,BOSS 这类重要怪物由服务器直接计算。
监控图上
仅部分偏高 · 各怪物的位置上报间隔(按持有控制权的客户端)
查看位置
在服务器上为每只怪物记录持有控制权的客户端,以及该客户端的上报间隔、RTT、丢包。也可以在服务器侧抓包,看该客户端所发包的到达间隔
确认依据
动得怪的怪物控制权都在同一个人手里,此人上报间隔忽长忽短或中断,把控制权交给别人后立即恢复正常
排除依据
服务器直接计算的怪物也同样跳,是服务器 tick 延迟或观看者一侧的线路问题。换了控制权仍然跳,看“指令同步的路径计算不一致”
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
负责的人自己觉得一切正常,所以反馈只会是“怪物不对劲”。除了一个人以外所有人都看到同一只怪物动得怪,就先确认这只怪物的控制权在谁手里。
出处 2 条

特定角色数据过于庞大 One character with oversized data (inventory, mail, buffs)

ID pt-heavy-char · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

物品、邮件堆了几千个,或者好友列表、黑名单、buff 特别多的角色,登录、存盘、向周围广播的数据量是别人的好几倍。与线路无关,只有用这个角色时才慢。

起因 长期培养的角色或活动奖励在背包、邮箱里堆了几千个 → 结果 每次登录、切换区域、存盘都要读写对应数量的 DB 数据,要发给周围的装备、buff 信息也很大 → 画面表现 只有这个角色进入时加载很久,打开背包、邮件时顿一下。如果服务器在游戏线程里等待存盘完成,周围的人也会跟着出现短暂停顿

症状
连不上/无限加载, 操作延迟, 卡住
因素
停顿
谁会遇到
只有我, 仅特定功能
何时出现
刚登录/维护结束后, 做特定操作时, 移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
研发团队要做的事
设置背包、邮件的保存上限并自动清理旧邮件,只分批加载需要的部分,存盘只写变化的部分,并放在游戏线程之外执行。
运维团队要做的事
在慢查询日志中找出同一角色反复出现的慢查询并转给研发团队,提供物品、邮件行数最多的角色排行。
数值参考
如果一个物品是 DB 里的一行,有 5,000 个物品的角色每次登录都要读 5,000 行,是普通角色的几十倍。
监控图上
仅部分偏高 · 各角色的登录、存盘耗时,各角色的 DB 查询行数
查看位置
在 DB 慢查询日志(MySQL slow query log、PostgreSQL log_min_duration_statement)中找出同一角色 ID 反复出现的慢查询、慢存盘,并列出物品、邮件表中按角色统计的行数排行
确认依据
慢查询集中在少数几个角色 ID 上,这些角色的物品、邮件行数是平均值的几十倍,换别的电脑、线路登录也同样慢
排除依据
同一账号的其他角色或其他玩家也一起慢,是 DB 设备或锁方面的问题。该角色在别的电脑上正常,是玩家环境问题
确认手段
运维工具即可确认(无需游戏代码)
深入了解
用同一个角色在别的电脑、别的线路上登录同样慢,同一账号的其他角色却正常,就要怀疑角色数据。这正是反馈中一定要写角色名的原因。
出处 3 条

分线/副本/位面不同 Different channel / instance / phase

ID pt-phase · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

两个角色在不同的分线或副本里,或者处在按任务进度显示不同 NPC 的不同“位面”时,看到的就是不同的世界。

起因 第二个角色被分到了别的分线,或任务阶段不同 → 结果 服务器不给这个角色发送该 NPC(正常) → 画面表现 只有一边没有 NPC。看起来像 bug,其实是按设计运行

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
一直, 刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:把分线、位面信息发给客户端,在 QA 检查清单中加入“确认两个角色的分线和任务阶段”。客户端:在界面上显示分线和位面。
监控图上
仅部分偏高 · 各客户端周围的实体数,分线/位面
查看位置
在游戏画面上对比两个角色的分线编号和相关任务的进度阶段,调成同一分线、同一阶段后再看。有服务器实体发送日志时,确认没有把该 NPC 发给这个角色的原因(分线、位面)
确认依据
两个角色的分线或任务阶段不同,调成相同后 NPC 就能看到
排除依据
分线、阶段都相同,却仍然只有一边没有,看“加载中到达的出现通知被丢弃”“刚进入时集中到达的出现信息丢失”“视野注册顺序错乱”
确认手段
需在玩家侧环境确认
深入了解
也要确认任务进度是按账号保存还是按角色保存。如果是同一账号的两个角色,一方的进度可能改变另一方的位面。
出处 2 条

加载中到达的出现通知被丢弃 Spawn messages dropped before the client is ready

ID pt-loading-drop · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

刚进入场景,服务器就发来周围 NPC 的出现通知,而客户端还在加载地图,就把这些通知丢掉了。

起因 服务器在完成进入处理后立即发送周围实体的出现通知 → 结果 客户端正在加载,消息处理函数还没注册,于是丢弃通知 → 画面表现 服务器认为已经发过了,不会再发。在离开视野再回来之前,NPC 一直看不到

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
刚登录/维护结束后, 移动中/切换地图时
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:加载完成后发送“准备就绪”,或者把加载中收到的包先存起来再处理。服务器:收到“准备就绪”后再发送周围信息。
数值参考
同一台电脑上两个客户端同时加载,或者正在加载的一方是后台窗口时,CPU 和磁盘被分摊,处理也受限,这一方的加载可能慢上好几倍。服务器的进入处理变快后,同样的 bug 也会暴露出来。
监控图上
仅部分偏高 · 各客户端加载时间,加载中丢弃的消息数
查看位置
把客户端在加载中收到并丢弃的消息数量、类型以及加载完成时刻,与服务器发送出现通知的时刻对比。在同一台电脑上同时加载两个客户端,或把正在加载的一方放到后台窗口,容易复现
确认依据
看不到的 NPC,服务器发过出现通知,到达时刻在加载完成之前,那段时间丢弃的消息数增加。只发生在加载较慢的那个客户端上
排除依据
出现通知在加载完成后才到达却仍看不到,看“基准快照丢失”或“实体 ID 重用混淆”。服务器根本没发送这个 NPC 的通知,看“视野注册顺序错乱”或“分线/副本/位面不同”
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

视野注册顺序错乱 Interest-management race on enter/leave

ID pt-aoi-race · 主责 研发团队·服务器开发

角色注册到视野网格的时刻与 NPC 跨网格移动的时刻重合时,这个 NPC 的出现通知可能会漏掉。

起因 进入、换线、传送的处理与 NPC 移动在同一时刻重合 → 结果 计算“新进入视野的实体”时漏掉了这个 NPC → 画面表现 只有特定几只 NPC 看不到,或已经离开的 NPC 还留在原地

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
移动中/切换地图时, 偶尔随机
负责方
主责 研发团队·服务器开发
研发团队要做的事
视野更新在一个线程里按同一顺序处理,定期把整个“可见列表”重新对齐一遍。
监控图上
偶发随机尖峰 · 服务器可见列表与客户端实体列表的差异数
查看位置
在服务器上把视野网格注册、实体跨格子移动、出现/离开通知的发送连同 tick 编号一起记录,定期对比服务器的“可见列表”与客户端持有的列表
确认依据
漏掉的 NPC 恰好在该角色进入、传送处理的同一个 tick 跨了格子,且没有针对该 NPC 的出现通知发送记录
排除依据
出现通知发了,客户端却没收到或丢弃了,是传递环节的问题(“刚进入时集中到达的出现信息丢失”“加载中到达的出现通知被丢弃”)。总是同一只 NPC 缺失,看“分线/副本/位面不同”或“显示选项不同”
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

基准快照丢失 Lost baseline for delta compression

ID pt-baseline · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

在服务器只发“与上次相比变化的部分”的方式下,丢了最初那一份完整信息(基准),之后的变化量就没法应用。

起因 实体的完整信息(基准)包丢失,或在处理前被丢弃 → 结果 客户端没有可以应用后续变化量的对象,于是忽略 → 画面表现 这个实体看不到,或过了很久突然出现

症状
隐身/幽灵实体, 瞬移
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:基准在收到确认(ACK)之前必须重传,变化量只针对客户端已确认收到的基准生成。客户端:基准真正应用之后再发送确认(ACK),收到未知实体的变化量时向服务器重新请求。
监控图上
偶发随机尖峰 · 收到未知实体变化量的次数
查看位置
把客户端因没有基准而丢弃变化量的次数和实体 ID,与服务器发送该实体基准的时刻、收到 ACK 的时刻对照。在开发环境中加入丢包(tc netem 的 loss、Unreal 网络模拟的丢包比例)来复现
确认依据
对看不到的实体,服务器发过基准但没收到 ACK,却一直只发变化量,客户端把这些变化量丢弃了
排除依据
基准已收到 ACK 并在客户端应用,却仍然看不到,看“离开通知丢失(幽灵实体)”或“实体 ID 重用混淆”
确认手段
需要游戏服务器/客户端的日志和指标
出处 4 条

离开通知丢失(幽灵实体) Missed despawn (ghost entity)

ID pt-ghost · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

反过来,漏掉“已消失”的通知时,已经死亡或离开的 NPC、玩家只会留在自己的画面上。

起因 死亡、离开、离开视野的通知丢失或顺序错乱 → 结果 客户端认为这个实体还在 → 画面表现 怎么打都没反应的怪物,早已离开的玩家还站在那里

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
偶尔随机, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:定期发送“当前可见列表”。客户端:删除列表里没有的实体,本该移动的实体长时间没有更新时将其隐藏。
监控图上
偶发随机尖峰 · 只存在于客户端的实体数
查看位置
对比服务器发送的“当前可见列表”与客户端持有的实体列表,统计只存在于客户端的实体,并按实体 ID 对照离开通知的发送、接收日志
确认依据
幽灵实体的离开通知服务器发过,客户端却没有接收记录;或离开通知比出现通知先到,顺序颠倒
排除依据
服务器的可见列表里也还留着这个实体,是服务器侧漏了清理实体。刚有新实体以同一 ID 出现,看“实体 ID 重用混淆”
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

刚进入时集中到达的出现信息丢失 Initial spawn burst lost (unreliable channel, receive buffer, fragmentation)

ID pt-spawn-burst · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

进入场景的那一刻,服务器会一次性发出周围几十到几百个实体的出现信息。如果用不可靠(unreliable)通道发送,或者客户端在加载中读不了 socket、接收缓冲区溢出,就会丢掉一部分,而且不会再来。

起因 刚进入时出现信息在极短时间内集中到达 → 结果 正在加载的客户端读 socket 读得晚,OS 接收缓冲区溢出;或者大的 UDP 包被分片,只丢一个分片整个包就没了。不可靠通道也不会重发 → 画面表现 只有加载慢的那个客户端缺了几只 NPC。离开视野再回来就能看到

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
刚登录/维护结束后, 移动中/切换地图时
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:出现/离开通知一定要用保证重传的可靠通道,初始信息分批发送。客户端:在与加载分开的线程里接收,加大接收缓冲区。
数值参考
PC 的 UDP 接收缓冲区默认值因 OS 而异,大多为几十到几百 KB。进入人多的城镇时,出现信息量超过这个值,只要因加载短暂读不了 socket 就会溢出。
监控图上
开服/维护后激增 · 刚进入时的接收量,出现通知缺失数
查看位置
对比服务器在进入后发出的出现通知数与客户端收到的数量,并查看是用哪个通道(可靠、不可靠)发送的。在服务器侧抓包,看进入后发给该玩家的数据量和分片包(Wireshark 过滤器 ip.flags.mf == 1 || ip.frag_offset > 0)
确认依据
收到的数量比发出的少,缺的集中在进入后扎堆的那一段,且用的是不可靠通道或大包被分片。在加载慢的那个客户端上更常见
排除依据
发出和收到的数量相同却看不到,是收到后丢弃(“加载中到达的出现通知被丢弃”)或视野计算的问题。与是否刚进入无关、随时都会缺,是线路丢包
确认手段
需要游戏服务器/客户端的日志和指标
出处 4 条

实体 ID 重用混淆 Entity ID reused without a generation counter

ID pt-id-reuse · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

死掉的 NPC 重新出现时,如果服务器重用同一个实体 ID,期间漏掉离开通知的客户端会把新 NPC 误认成旧 NPC。

起因 NPC 死亡后以同一个实体 ID 重新出现 → 结果 漏掉离开通知的客户端认为是“已知实体”,忽略出现通知,或保留死亡状态不变 → 画面表现 只有一边画面上没有 NPC,或 NPC 倒地不起,有时还显示成别的 NPC 的样子

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端, 只有我
何时出现
偶尔随机, 人多的时候
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:给实体 ID 加上代数编号(generation),以区分重用。客户端:收到已知 ID 的出现通知时,删除原有实体后重新创建。
监控图上
偶发随机尖峰 · 已知 ID 的出现通知数
查看位置
在服务器上记录各实体 ID 的创建、删除时刻(如有代数编号,一并记录),统计客户端以已知 ID 收到出现通知的次数,以及视野更新时被当作“没有变化”处理的删除后重新生成
确认依据
看不到或倒地显示的 NPC,其 ID 与刚死亡的 NPC 相同,期间该客户端没收到离开通知,或服务器离开、出现通知都没发
排除依据
ID 带有代数编号且比较时也用到了,就不是这个原因。ID 没有被重用却仍看不到,是出现通知丢失方面的问题
确认手段
需要游戏服务器/客户端的日志和指标
深入了解
服务器侧也会发生。只按 ID 比较视野内实体列表时,在两次视野更新之间死亡并以同一 ID 重新生成的 NPC 会被当作“没有变化”,离开、出现通知都不发。每个人的视野更新时机错开时,只有恰好赶上那一刻的客户端会遇到。
出处 2 条

固定 UDP 端口冲突 Two clients bound to the same local UDP port

ID pt-port-collision · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

客户端被做成使用固定的本地端口时,同一台电脑上的第二个客户端要么用不了这个端口,要么和第一个客户端分着收包。

起因 两个客户端要打开同一个本地 UDP 端口(用重用选项强行共用) → 结果 OS 只把收到的包交给其中一个 socket,或不保证由哪一个接收。路由器和服务器也把两个客户端看成同一个地址 → 画面表现 一边收不到世界数据包,看不到 NPC 和其他玩家,或者掉线

症状
隐身/幽灵实体, 掉线, 连不上/无限加载
因素
丢包
谁会遇到
双开时只有一个客户端
何时出现
刚登录/维护结束后, 一直
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:本地端口由 OS 自动选择(bind 0 号端口)。服务器:用每个连接签发的会话令牌区分连接。
监控图上
仅部分偏高 · 各客户端收到的包数
查看位置
在玩家电脑上开着两个客户端,在命令提示符中用 netstat -ano -p udp 查看每个游戏进程(PID)打开的本地 UDP 端口。在服务器侧确认两个会话是否来自同一公网 IP、同一端口
确认依据
两个游戏进程绑定在同一个本地端口上,或在服务器上两个会话显示为同一 IP、端口。只开一个就正常
排除依据
两个客户端用的是不同的本地端口,一边仍然异常,看“按 IP/设备区分会话的 bug”或“多开限制”
确认手段
需在玩家侧环境确认
出处 3 条

按 IP/设备区分会话的 bug Session keyed by IP or machine ID

ID pt-session-key · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

服务器或中间服务器按 IP 或设备 ID 区分连接时,会把同一台电脑(同一公网 IP)上的两个客户端认成同一个人。

起因 会话表按 IP 或 IP+设备 ID 建立 → 结果 第二个客户端的信息覆盖或混进第一个会话 → 画面表现 一边看不到 NPC,另一边掉线或收到别人的信息

症状
隐身/幽灵实体, 掉线
因素
丢包
谁会遇到
双开时只有一个客户端, 同一家庭, 特定地区/运营商
何时出现
刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:服务器和中间服务器都按每个连接唯一的会话令牌区分;同一个家里(路由器 NAT 后面)的多个人,以及运营商把一个 IP 分给多个用户的移动线路(CGNAT)用户,也会遇到同样的问题,一定要修。客户端:每个启动的客户端使用各自领取的会话令牌。
监控图上
仅部分偏高 · 同一公网 IP 的并发会话数,会话被覆盖次数
查看位置
在服务器、中间服务器日志中记录查找会话时用的键、会话令牌、客户端 IP 和端口,查看同一 IP 第二次连接进来的那一刻原有会话是否被改。在同一台电脑上依次启动两个客户端即可复现
确认依据
第二个客户端连上的那一刻,第一个会话的地址或角色信息被改掉;同一路由器或移动线路(CGNAT)后面的其他玩家也出现同样的掉线
排除依据
同一 IP 的两个会话用不同的令牌各自保持,就不是这个原因。两个进程用的是同一个本地端口,看“固定 UDP 端口冲突”
确认手段
需要游戏服务器/客户端的日志和指标
出处 1 条

多开限制 Multi-client restriction policy

ID pt-multiclient · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

安全模块或服务器策略限制一台电脑上开多个客户端时,第二个客户端会无法启动或连接,或者先开的那个掉线。有些游戏只禁用额外客户端的部分功能。

起因 安全模块检测到重复运行,或服务器限制同一设备的额外连接 → 结果 拒绝第二次启动或连接,或断开其中一个。少数情况下只屏蔽额外客户端的部分功能 → 画面表现 连不上,或其中一个掉线。只屏蔽功能的游戏中,只有一边看不到 NPC、商店

症状
连不上/无限加载, 隐身/幽灵实体, 掉线
因素
丢包
谁会遇到
双开时只有一个客户端
何时出现
刚登录/维护结束后
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:要限制就给出明确的提示信息,为 QA 在安全模块中设置例外。服务器:同一设备连接限制也要为 QA 设置例外。
监控图上
仅部分偏高 · 按原因统计的连接拒绝、断开次数(重复登录)
查看位置
查看启动第二个客户端时弹出的消息,以及先开那个客户端的断开消息。确认服务器的连接拒绝、强制断开日志中是否有重复登录、同一设备之类的原因代码
确认依据
第二次启动或连接的那一刻弹出拒绝消息,或先开的一方以重复登录为由被断开;只开一个客户端就没有问题
排除依据
没有拒绝、断开原因,两边都连上了,却只有一边看不到 NPC,看“固定 UDP 端口冲突”“按 IP/设备区分会话的 bug”或加载、显示方面的原因
确认手段
需在玩家侧环境确认
出处 1 条

后台窗口处理受限 Background window throttling

ID pt-background · 主责 研发团队·客户端开发 · 配合 外部·外部

处于后台窗口的客户端,游戏、引擎、OS 都会减少它的帧和处理量。收到的包不能及时处理,就会积压或溢出。

起因 游戏选项或显卡驱动的后台帧率限制(例如 NVIDIA 驱动可在每秒 20~200 帧之间指定),省电,引擎的后台暂停设置。OS 也会优先把 CPU、GPU 分给前台窗口 → 结果 每帧处理的包数减少,队列堆积,接收缓冲区溢出后包被丢弃 → 画面表现 窗口切到前台时一下子全冒出来,或者有的 NPC 始终看不到

症状
隐身/幽灵实体, 快进, 掉线
因素
停顿, 丢包
谁会遇到
双开时只有一个客户端
何时出现
挂机一段时间后, 一直
负责方
主责 研发团队·客户端开发 · 配合 外部·外部
研发团队要做的事
网络接收放在与游戏循环分开的线程里持续运行,后台也保证最低处理量,打开引擎的后台运行设置(Unity 为 runInBackground)。
外部要做的事
引导玩家关闭显卡驱动的后台帧率限制和电脑的省电模式。
数值参考
Unity 的 runInBackground 设置关闭时,窗口一失去焦点,游戏循环就会停住。如果只在这个循环里接收数据,这段时间里包完全得不到处理。
监控图上
断流后集中到达 · 客户端帧间隔,每帧处理的包数
查看位置
在同一台电脑上把一个窗口放前台、另一个放后台,互换角色对比。用 PresentMon 测两个进程的帧间隔,有游戏侧日志时看窗口焦点状态和每帧处理的包数
确认依据
只有在后台窗口时帧间隔大幅增加(驱动限制时会在与设定帧率对应的间隔处走平)或处理停住,切换窗口后问题也转移到另一个客户端
排除依据
前台窗口同样出现,就不是后台限制造成的。与窗口位置无关、总是同一个客户端异常,看“显示选项不同”或“客户端版本/数据不一致”
确认手段
需在玩家侧环境确认
出处 4 条

缓存/资源文件并发访问冲突 Shared cache / asset file lock conflicts

ID pt-asset-lock · 主责 研发团队·客户端开发

两个客户端同时写同一个缓存文件夹或锁住文件时,其中一个会加载不了 NPC 模型、纹理。

起因 两个客户端同时写同一安装目录下的缓存、补丁文件 → 结果 文件加锁失败,或读到写了一半的文件,导致加载失败 → 画面表现 有名字牌却没有角色模型,或 NPC 是透明的

症状
隐身/幽灵实体
因素
停顿
谁会遇到
双开时只有一个客户端
何时出现
刚登录/维护结束后, 移动中/切换地图时
负责方
主责 研发团队·客户端开发
研发团队要做的事
每个客户端使用单独的缓存文件夹,文件加锁失败时重试,加载失败时至少显示默认模型。
监控图上
仅部分偏高 · 各客户端资源加载失败次数
查看位置
在玩家电脑上用 Process Monitor 只过滤游戏安装、缓存目录路径,查看两个游戏进程打开、写入文件的结果。有客户端日志时,查找资源加载失败和文件打开错误码(ERROR_SHARING_VIOLATION)
确认依据
看不到的模型,打开文件时以共享冲突、加锁失败告终,同一时刻另一个客户端正在写这个文件。只开一个或把安装、缓存目录分开就消失
排除依据
只开一个客户端也看不到同一个模型,是文件损坏或“客户端版本/数据不一致”。文件正常打开了却画不出来,看“内存/显存不足导致流式加载失败”
确认手段
需在玩家侧环境确认
出处 2 条

内存/显存不足导致流式加载失败 Memory / VRAM exhaustion

ID pt-vram · 主责 研发团队·客户端开发 · 配合 外部·外部

两个客户端共用显存时,新需要的模型、纹理没有地方加载,有一部分就画不出来。

起因 两个客户端共用显存和内存。OS 有时还会先削减后台窗口的显存配额 → 结果 引擎无法加载新的模型、纹理,或反复卸载再加载 → 画面表现 NPC 出现得晚、模糊或看不到,一卡一卡

症状
隐身/幽灵实体, 一卡一卡
因素
停顿
谁会遇到
双开时只有一个客户端
何时出现
移动中/切换地图时, 人多的时候
负责方
主责 研发团队·客户端开发 · 配合 外部·外部
研发团队要做的事
按内存预算自动调整画质,加载失败时显示替代模型。
外部要做的事
引导同时开两个客户端的玩家降低画质或使用低配模式,告知推荐的显存、内存配置。
监控图上
触顶后走平 · 各进程专用 GPU 内存使用量
查看位置
在玩家电脑的任务管理器“详细信息”选项卡中添加“专用 GPU 内存”列,把两个客户端的用量之和与显卡显存容量对比。在游戏侧记录 DXGI 的 QueryVideoMemoryInfo 返回的预算(Budget)和当前用量(CurrentUsage)
确认依据
两个客户端用量之和在显存容量附近走平,当前用量超过预算的时刻模型、纹理加载失败集中出现。降低画质或只开一个就消失
排除依据
显存有余量却仍看不到,看“缓存/资源文件并发访问冲突”或“显示选项不同”
确认手段
需在玩家侧环境确认
出处 3 条

显示选项不同 Different display settings

ID pt-display-option · 主责 研发团队·客户端开发

显示人数限制、隐藏 NPC 名字牌或模型、低配模式这类选项在两个客户端上不同时,看到的东西就不一样。

起因 只有一个客户端开了“周围角色显示数量限制”或低配模式 → 结果 不绘制远处或优先级低的 NPC(正常) → 画面表现 只有一边没有 NPC

症状
隐身/幽灵实体
因素
停顿
谁会遇到
双开时只有一个客户端, 只有我
何时出现
人多的时候, 一直
负责方
主责 研发团队·客户端开发
研发团队要做的事
让玩家能看出是被选项隐藏的实体,按客户端分开设置文件,避免互相混用。
监控图上
仅部分偏高 · 各客户端画面上绘制的实体数
查看位置
并排对比两个客户端的显示人数限制、名字牌、模型隐藏、低配模式设置,把一边调成与另一边完全相同再看。也要确认两个客户端是否共用一个设置文件、互相覆盖
确认依据
设置调成一致后两边画面就一样了,原先看不到的 NPC 是显示人数限制以外的远处实体或优先级低的实体
排除依据
设置完全一致仍然只有一边没有,看“分线/副本/位面不同”或出现通知丢失方面的问题
确认手段
需在玩家侧环境确认
出处 1 条

客户端版本/数据不一致 Client version / data table mismatch

ID pt-version · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

第二个客户端来自另一个安装目录,或补丁没打完时,它不认识服务器发来的新 NPC ID,就会悄悄忽略。

起因 装在其他文件夹里的客户端,或在打补丁过程中启动的客户端 → 结果 收到不认识的 NPC ID、模型 ID 就跳过 → 画面表现 只有新加的 NPC 在一边看不到

症状
隐身/幽灵实体
因素
丢包
谁会遇到
双开时只有一个客户端
何时出现
刚登录/维护结束后
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
研发团队要做的事
客户端:连接时发送数据版本,收到不认识的 ID 时记日志并显示替代内容。服务器:连接时检查数据版本,不一致就拒绝连接并提示更新。
监控图上
仅部分偏高 · 各客户端版本收到未知 ID 的次数
查看位置
对比两个客户端的可执行文件路径,以及画面、日志中显示的客户端版本和数据版本。在游戏侧记录连接时发送的数据版本,以及收到不认识的 NPC、模型 ID 而跳过的次数
确认依据
两个客户端的版本或安装目录不同,看不到的 NPC 是最近版本更新新增的,在已打完补丁的客户端里能看到
排除依据
版本和安装目录都相同,却仍只有一边没有,看“分线/副本/位面不同”或加载、传递方面的原因
确认手段
需在玩家侧环境确认
出处 1 条

按连接分配的发送预算/优先级 Per-connection bandwidth budget and priority

ID pt-priority · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

服务器给每个连接的发送量设上限、按由近及远的顺序发送时,上限定得低的一方收到远处 NPC 会很晚,甚至收不到。

起因 人多的地方,服务器在每个连接的发送量上限内按重要程度依次发送 → 结果 带宽估算偏低的连接(例如因为是后台窗口而确认回得晚的一方),排在后面的实体会被一直往后推 → 画面表现 远处的 NPC 只在一边显示得晚或看不到

症状
隐身/幽灵实体, 操作延迟
因素
延迟
谁会遇到
双开时只有一个客户端, 特定地点/分线
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
研发团队要做的事
服务器:被推迟的实体随时间推移提高优先级(防止饿死,starvation),保证最低更新频率。客户端:在后台也要按时发送确认,避免带宽估算被压低。
监控图上
随人数/负载上升 · 各连接被推迟的实体数,各连接的发送量
查看位置
在服务器上为每个连接记录每个 tick 的发送字节数、发送上限(估算带宽)、没能发出而推迟的实体数,以及各实体距上次发送经过的时间。Unreal 可以在 Networking Insights 中查看各连接的包大小及其中包含的复制实体
确认依据
看不到的 NPC 是这个连接上被推迟了很久的实体,该连接的上限比其他连接低,人越多被推迟的实体越多
排除依据
没有被推迟的实体,那只 NPC 也按时发出了,看发送之后的环节(接收缓冲区、加载、显示选项)。所有连接都顶在上限上,是全服发送量或视野设计的问题
确认手段
需要游戏服务器/客户端的日志和指标
出处 3 条

时钟估算误差导致实体被搁置 Clock estimate error holds or discards entities

ID pt-clock-hold · 主责 研发团队·客户端开发

客户端估算的服务器时间不准时,刚到达的实体信息会被当成“还在未来”而搁置,或被当成“太旧”而丢弃。

起因 某一个客户端对服务器时间的估算偏差很大(在加载中测量、从省电状态恢复) → 结果 插值基准时间与实体信息的时间对不上 → 画面表现 实体出现得晚,或看起来停在原地不动

症状
隐身/幽灵实体, 一卡一卡
因素
延迟
谁会遇到
双开时只有一个客户端
何时出现
挂机一段时间后, 刚登录/维护结束后
负责方
主责 研发团队·客户端开发
研发团队要做的事
定期重新做时间同步,偏差大时立即重置,不使用加载中或刚从省电状态恢复时测得的值。
监控图上
仅部分偏高 · 各客户端的服务器时间估算误差
查看位置
在客户端记录估算的服务器时间、RTT、重新做时间同步的时刻,以及搁置或丢弃实体信息的次数。在刚加载完或刚从省电状态恢复时尝试复现
确认依据
只有出问题的客户端估算误差超过重置阈值(Unity 为 hardResetThresholdSec,默认 0.2 秒),有把实体信息当成未来而搁置、或当成过去而丢弃的记录,重新做时间同步后立即恢复正常
排除依据
估算误差很小却出现得晚,看“按连接分配的发送预算/优先级”或加载方面的原因
确认手段
需要游戏服务器/客户端的日志和指标
出处 2 条

TCP 重传的根本原因

20 个原因 · 完整版章节

无线链路丢包 Wi-Fi / cellular link loss

ID rt-wireless · 主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发

Wi-Fi 和移动网络会在无线链路上重传几次,仍然失败就丢弃数据包。被丢弃的包要等过好一阵,才由 TCP 重新发送。

起因 信号弱或干扰严重,无线链路上的传输接连失败 → 结果 超过无线设备的重试上限(通常几次到十几次)就丢弃数据包 → 画面表现 卡住的时长等于 TCP 重传的等待时间,后面的包在接收缓冲区里等着,之后表现为快进

症状
卡住, 快进, 瞬移
因素
丢包, 抖动
谁会遇到
只有我, 同一家庭
何时出现
偶尔随机, 移动中/切换地图时
负责方
主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发
研发团队要做的事
服务器:开启 TCP_NODELAY(Nagle 开着时,RACK 没有后续数据包可用来判断丢包);重传阻塞期间要发的状态更新不要堆积,只发最新的(用 TCP_NOTSENT_LOWAT 限制堆在内核里的量)。客户端:开启 TCP_NODELAY(自己输入方向的丢包由客户端操作系统恢复);丢包集中或 ping 飙升时,在画面上显示网络状态。
运维团队要做的事
用 RACK-TLP 加快丢包恢复(服务器无法阻止无线丢包,能做的只有加快恢复);确认新版 Linux 的默认值 net.ipv4.tcp_recovery=1(RACK)、net.ipv4.tcp_early_retrans=3(TLP)没有被改掉。
外部要做的事
引导玩家改用有线连接,使用 5 GHz、6 GHz 频段,调整路由器位置或信道。
数值参考
无线丢包率 1% 意味着每 100 个游戏数据包丢 1 个。每秒收 10 个包,大约每 10 秒就会顿一下。没有 RACK-TLP 时,每次都要停顿一个 RTO(ping + 200 ms 以上)。
监控图上
仅部分偏高 · 每个连接的重传率、每个连接的 RTT(ping)
查看位置
在玩家 PC 上分别向路由器(网关)地址和游戏服务器 ping 几百次,比较丢包和延迟波动幅度;换成有线或移动数据后再测一次。服务器上用 ss -ti 看该玩家连接的 retrans 和 rtt(平均值/偏差)
确认依据
ping 路由器时就已出现丢包或忽高忽低的延迟,换成有线后消失。从服务器看,只有该玩家连接的 retrans 和 RTT 偏差大
排除依据
到路由器为止都正常,丢包从更远处开始,看运营商和路径(“瓶颈队列溢出”“路径变更/ECMP 故障路径”)。同一运营商的多名玩家同时变差,先查运营商链路
确认手段
需在玩家侧环境确认
深入了解
无线设备的重试会产生抖动(到达间隔的波动),每次重试增加几 ms;只有超过重试上限才变成丢包。所以无线质量越差,症状就按“抖动 → 偶尔卡住 → 频繁卡住”的顺序加重。在路由器(AP)之间切换的瞬间(漫游),可能连续几十 ms 到几秒都在丢包。移动网络在基站链路上会做大量重传,所以比起丢包,更常表现为几百 ms 的延迟飙升。
真实案例
Square Enix 2021: FINAL FANTASY XIV 资料片上线时的拥挤与登录排队错误
出处 8 条

瓶颈队列溢出(拥塞丢包) Tail drop at a congested bottleneck

ID rt-queue-drop · 主责 运维团队·网络运维 · 配合 外部·外部, 研发团队·客户端开发

路由器、运营商之间的互联链路、数据中心线路这类最窄处的队列一旦满了,新来的数据包就会被丢弃。

起因 视频、下载和其他用户的流量把瓶颈链路占满 → 结果 队列满的期间,新到的数据包接连被丢弃(tail drop)。没被丢弃的包也要在满队列的末尾排队 → 画面表现 多个包同时丢失,长时间卡住后表现为快进,晚上多发

症状
卡住, 快进, 拉回
因素
丢包, 延迟
谁会遇到
同一家庭, 特定地区/运营商, 全服
何时出现
晚高峰, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 外部·外部, 研发团队·客户端开发
研发团队要做的事
丢包集中或 ping 飙升时,在画面上显示网络状态(提示可能是同一线路上有大流量传输)。
运维团队要做的事
保证数据中心线路有余量;检查我方线路和交换机端口的队列丢弃(output drops)计数器;运营商链路拥塞时,改走其他线路或对等互联绕行。
外部要做的事
引导玩家在路由器上启用 SQM(fq_codel、CAKE)和 ECN(让发送方在队列溢出前降速);要求运营商给瓶颈链路扩容。
数值参考
队列溢出的那一刻,几十 ms 内进来的包会有相当一部分同时丢失。连续丢包时,重传的包也容易再丢,所以经常拖到 RTO。
监控图上
特定时段偏高 · 重传率、RTT(ping)
查看位置
把服务器重传率(每 1 分钟运行一次 nstat,取 TcpRetransSegs ÷ TcpOutSegs 的增量)和每个连接的 RTT 按地区、运营商、时段拆开看,同时看我方线路和交换机端口的出方向丢弃(ifOutDiscards)。在高峰和空闲时段分别对问题地区跑 mtr 并比较
确认依据
只在晚高峰重传率上升,丢包之前 RTT 先上升(队列在填满)。mtr 中只在高峰时段从某一跳起直到终点丢包和延迟一起增加
排除依据
丢包前 RTT 没有上升,看“流量监管丢弃超额流量”。不分时段、丢包率一直差不多,看“物理层错误”或“路径变更/ECMP 故障路径”
确认手段
运维工具即可确认(无需游戏代码)
真实案例
Riot Games 2015: 绕远路的 League of Legends 流量与 Riot Direct
出处 9 条

突发发送导致浅缓冲区溢出 Sender bursts overflow shallow buffers

ID rt-burst · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维

服务器每个 tick 在一瞬间集中发出几千人份的更新,交换机的小缓冲区或云平台的瞬时上限不到 1 ms 就会溢出,一部分包被丢弃。

起因 tick 开始的瞬间,把发给所有人的数据包一次性发出 → 结果 多台服务器流量汇聚的交换机端口缓冲区(每个端口几百 KB 到几 MB)或云实例的上限瞬间被冲破(平均利用率很低) → 画面表现 多名玩家同时瞬移或顿一下,看平均值指标找不到原因

症状
瞬移, 卡住, 快进
因素
丢包
谁会遇到
特定地点/分线, 全服
何时出现
人多的时候
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维
研发团队要做的事
把一个 tick 的发送分散到整个 tick 内(几千个连接在 tick 开始时扎堆,靠按连接的平滑发送很难解决);错开各服务器的 tick 起始时刻;发送大数据的连接用 SO_MAX_PACING_RATE 设置速率上限。
运维团队要做的事
服务器/OS:给整台服务器的发送速率设上限(服务器操作系统的流量整形器,Linux tc);单个连接的突发发送用平滑发送(Linux fq 队列、BBR)摊平。网络:换用大缓冲交换机;以较短间隔检查交换机端口的出方向丢弃计数器(看平均利用率发现不了)。
数值参考
10 Gbps 端口 1 ms 能发送的量约为 1.25 MB。多台服务器的 tick 撞在一起、涌向同一个端口时,缓冲区瞬间就满了。
监控图上
随人数/负载上升 · 交换机端口出方向丢弃数、重传率
查看位置
以几秒的间隔采集服务器所连交换机端口及其上一级端口的出方向丢弃(ifOutDiscards);云上看 ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded。用 bcc tcpretrans 采集同一时刻的重传并对照
确认依据
分钟级平均利用率很低,出方向丢弃或 allowance 超限却在增加,且随同时在线人数和同一地点的聚集人数增大。重传在该服务器的多个连接上同一时刻发生,并不集中在特定玩家 IP 网段(运营商、地区)
排除依据
同一端口的 CRC、入方向错误也一起增加,看“物理层错误”。接收端服务器的网卡丢弃计数器或 softnet dropped 增加,看“接收端服务器主机丢包”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
平滑发送(pacing)按连接分别起作用。几千个连接在 tick 开始时各发一两个包形成的扎堆,按连接的平滑发送很难化解,需要游戏服务器自己错开发送时间点。反过来,单个连接发送大数据时,网卡会把几十 KB 的数据切成数据包大小连续发出(TSO),这种突发靠平滑发送就能很好地摊开。
出处 7 条

流量监管丢弃超额流量 Traffic policing

ID rt-policer · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发

运营商套餐限速、云实例上限和 DDoS 防护设备,有时会把超过规定速率的数据包直接丢弃,不放进队列。

起因 瞬时发送量超过允许速率或允许突发量 → 结果 超出的数据包不排队,直接丢弃(流量监管) → 画面表现 每逢突发量大的瞬间就丢好几个包,卡住后表现为快进;平均速率看起来低于上限

症状
卡住, 快进, 瞬移
因素
丢包
谁会遇到
全服, 特定地区/运营商, 只有我
何时出现
人多的时候, 晚高峰
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
研发团队要做的事
把每个 tick 集中发出的量分散到 tick 内发送,让瞬时发送量低于允许突发量;碰到每秒包数上限时,把一个 tick 的消息合并到一个数据包里。
运维团队要做的事
网络:检查设备的流量监管超限计数器;用流量整形替换流量监管;调大允许突发量。服务器/OS:检查云平台的超限指标(AWS 为 ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded);升级实例规格;在服务器上做平滑发送(Linux fq 队列)。
数值参考
流量整形(放进队列延后发送)会增加延迟,流量监管(直接丢弃)会增加丢包。TCP 游戏连接丢一次包就可能停顿几百 ms,所以只是短暂超出上限时,通常流量监管的影响更大。
监控图上
触顶后走平 · 短间隔采样的发送量,流量监管和 allowance 超限计数器
查看位置
看配置了流量监管的设备上的超限(exceed)和丢弃计数器;云上看 ethtool -S 的 bw_out_allowance_exceeded、pps_allowance_exceeded。对发生丢包的连接,用 ss -ti 的 rtt 或抓包查看丢包前一刻的 RTT
确认依据
超限计数器增加,短间隔采样的发送量在某个值处像被削平一样走平。丢包前 RTT 没有上升,只在突发量大的瞬间多个包同时丢失
排除依据
丢包前 RTT 先上升,属于队列溢出(“瓶颈队列溢出”“突发发送导致浅缓冲区溢出”)。超限计数器不变则是其他原因
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

物理层错误(线缆/光模块/连接器不良) Bit errors: bad cable, optics, dirty fiber

ID rt-physical · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 外部·外部

线缆损坏、光纤连接器积灰、光模块老化都会产生误码,损坏的数据包会被设备悄无声息地丢弃。

起因 线缆、光模块或连接器不良,导致比特翻转 → 结果 设备丢弃校验和(CRC)不对的数据包 → 画面表现 只有经过这条路径的玩家反复顿一下再快进,与时段无关

症状
卡住, 快进, 瞬移
因素
丢包
谁会遇到
特定地点/分线, 同一家庭
何时出现
一直
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维, 外部·外部
运维团队要做的事
CRC 错误记在出错方向的接收端,所以两端都要查。网络:检查设备端口的 CRC 和入方向错误计数器;检查光功率(交换机上的光模块信息);清洁光纤连接器;更换线缆或光模块。服务器/OS:检查服务器 ethtool -S 的 rx_crc_errors(各驱动的名称略有不同);检查光功率(ethtool -m);更换服务器侧线缆或网卡。
外部要做的事
问题在玩家家中时,引导玩家更换网线或路由器;在运营商线路上时,要求运营商检修线路。
数值参考
即使只有 0.1% 的丢包,也是每 1,000 个游戏数据包丢一次。经过这条路径的玩家有几十人时,每隔几秒就会有人顿一下。包越大,越容易碰上误码。
监控图上
仅部分偏高 · 各端口的 CRC 错误数,各服务器、各端口的重传率
查看位置
看链路两端的 CRC 计数器。服务器看 ethtool -S 的 rx_crc_errors 或 ip -s -s link 的 crc,交换机看端口的 FCS 错误(dot3StatsFCSErrors)和入方向错误(ifInErrors)。光链路用 ethtool -m 和交换机的光模块信息查看接收光功率
确认依据
某个端口的 CRC 错误不分时段持续增加,只有经过该端口的服务器和连接重传率高。接收光功率比同类其他链路低
排除依据
CRC 不变,只有出方向丢弃增加,属于队列溢出(“突发发送导致浅缓冲区溢出”“瓶颈队列溢出”)。一端的晚期冲突和另一端的 CRC 同时增加,看“双工不匹配”
确认手段
运维工具即可确认(无需游戏代码)
出处 3 条

双工不匹配 Duplex mismatch

ID rt-duplex · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

一端用自动协商,另一端把速率和双工写死,就会有一端以半双工工作,每当负载上来就因冲突丢包。

起因 只有一端设备固定了速率和双工 → 结果 一端以全双工、另一端以半双工工作,产生冲突和晚期冲突(late collision) → 画面表现 平时正常,流量一上来,经过这台设备的所有人都先卡住再快进

症状
卡住, 快进
因素
丢包
谁会遇到
全服, 特定地点/分线
何时出现
人多的时候, 晚高峰
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维
运维团队要做的事
两端都设为自动协商,或两端都固定为相同的值。网络:在交换机端口状态里确认速率和双工;在端口计数器里确认半双工一端的晚期冲突、全双工一端的 CRC 错误和超短帧(runt)是否增加。服务器/OS:用 ethtool 确认速率和双工。
数值参考
1 Gbps 电口(铜缆)必须自动协商,10 Gbps 及以上根本没有半双工。所以现在主要出现在 100 Mbps 及以下的老设备、管理口和部分线路对接处。
监控图上
随人数/负载上升 · 端口晚期冲突数和 CRC 错误数,重传率
查看位置
看链路两端实际的速率和双工。服务器只带接口名运行 ethtool,交换机看端口状态或 SNMP 的 dot3StatsDuplexStatus。同时看晚期冲突(服务器 tx_window_errors,交换机 dot3StatsLateCollisions)和 CRC 错误
确认依据
一端显示半双工,另一端显示全双工。每当流量增加,半双工一端的晚期冲突和全双工一端的 CRC 错误一起增加
排除依据
两端速率和双工一致,只有 CRC 增加,看“物理层错误”。10 Gbps 及以上的链路没有半双工,可排除这个原因
确认手段
运维工具即可确认(无需游戏代码)
出处 6 条

接收端服务器主机丢包 Receiver host drops (ring, softirq, CPU)

ID rt-host-drop · 主责 运维团队·系统运维

数据包已经到了服务器,却因网卡的环形缓冲区(暂存到达数据包的缓冲区)溢出,或内核中负责接收处理的 CPU 核心饱和而被丢弃。

起因 在线人数激增、中断集中在一个核心、虚拟机 CPU 窃取时间、虚拟交换机过载 → 结果 在环形缓冲区(rx_missed_errors 等,名称因驱动而异)或内核接收队列(softnet dropped)处被丢弃 → 画面表现 人一多,全服同时出现输入生效慢、顿一下的情况

症状
操作延迟, 卡住, 快进, 瞬移
因素
丢包, 停顿
谁会遇到
全服
何时出现
人多的时候
负责方
主责 运维团队·系统运维
运维团队要做的事
把 ethtool -S 的 rx_missed_errors 这类丢弃计数器和 /proc/net/softnet_stat 的 dropped 加入监控;调大环形缓冲区(ethtool -G);用 RSS 把中断分散到多个核心;把游戏线程与接收处理核心分开;保证 CPU 余量;虚拟机要检查 CPU 窃取时间和虚拟交换机负载。
数值参考
服务器在接收时丢掉的包由客户端重发。所以服务器的重传指标上不太看得出来,会先体现在 ethtool -S 的 rx_missed_errors 这类丢弃计数器(名称因驱动而异)和 /proc/net/softnet_stat 的 dropped 上。
监控图上
触顶后走平 · 各核心 softirq 占用率,网卡丢弃计数器
查看位置
看 ethtool -S 的丢弃计数器(rx_missed_errors 等,mlx5 为 rx_out_of_buffer、rx_discards_phy)、ip -s -s link 的 missed、/proc/net/softnet_stat 的第 2 列(dropped)和第 3 列(time_squeeze),并用 mpstat -P ALL 看各核心的 %soft(软中断处理)。虚拟机还要看 %steal
确认依据
人多的时刻丢弃计数器或 softnet dropped 增加,负责接收处理的核心 %soft 接近 100% 后再也上不去。该服务器所有连接的输入同时变慢
排除依据
服务器的丢弃计数器不变,重传集中在特定地区或运营商的连接上,就是路径上的丢包。服务器发出的包在路径上丢失时,服务器 nstat 的 TcpRetransSegs 会增加,这些计数器则不变
确认手段
运维工具即可确认(无需游戏代码)
出处 10 条

防火墙/连接跟踪丢包 Stateful firewall / conntrack drops

ID rt-stateful-fw · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发

防火墙或 Linux 连接跟踪(conntrack,把经过的连接记录到表里的功能)在表满了,或判断连接状态不对时,会丢弃数据包。

起因 连接跟踪表已满(table full),或去程和回程路径不同,只有一个方向经过防火墙(非对称路径) → 结果 防火墙把数据包当成“未知连接”或“序列号超出窗口范围”的包丢弃 → 画面表现 表满时新连接进不来;路径不对称时,只有走这条路径的人在反复重传后掉线

症状
卡住, 掉线, 连不上/无限加载
因素
丢包
谁会遇到
全服, 特定地区/运营商
何时出现
人多的时候, 刚登录/维护结束后, 偶尔随机
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发
研发团队要做的事
服务器:为应对表被占满的情况,用登录排队系统调控集中涌入的连接;复用连接,避免反复建立短连接(包括服务器间调用);心跳中断的连接主动清理。客户端:连接失败或断开后,逐步拉长重试间隔并随机打散(避免表满时大家又同时涌回来)。
运维团队要做的事
网络:调大防火墙的连接跟踪表;把游戏端口排除在连接跟踪之外;调整路由,让去程和回程经过同一台防火墙;检查防火墙的 TCP 窗口检查设置。服务器/OS:调大 Linux 的表(nf_conntrack_max);把游戏端口排除在连接跟踪之外(NOTRACK);检查 TCP 窗口检查设置(nf_conntrack_tcp_be_liberal);AWS 上还要看 conntrack_allowance_exceeded。
数值参考
Linux conntrack 的默认上限(nf_conntrack_max)随内存不同,在几万到几十万条之间。当前条目数(nf_conntrack_count)达到上限时,日志里会出现“nf_conntrack: table full, dropping packet”。
监控图上
触顶后走平 · conntrack 条目数(nf_conntrack_count),新连接失败数
查看位置
Linux 服务器看 nf_conntrack_count 和 nf_conntrack_max、dmesg 中的“nf_conntrack: table full, dropping packet”、/proc/net/stat/nf_conntrack 的 drop 和 invalid(每个核心一行,十六进制)。防火墙看会话表使用量和丢弃日志,AWS 看 ethtool -S 的 conntrack_allowance_exceeded
确认依据
条目数顶到上限后走平,同一时刻 table full 日志和 drop 增加,或 conntrack_allowance_exceeded 增加。非对称路径时,上限还有余量,但特定路径上的连接 invalid 和防火墙丢弃日志增加
排除依据
条目数离上限很远,invalid 和丢弃日志也不变,则是其他原因。表有余量,防火墙的 CPU 或每秒包数却已打满,看“中间设备超出处理上限”
确认手段
运维工具即可确认(无需游戏代码)
出处 5 条

中间设备超出处理上限(防火墙/IPS/DDoS 防护) Inline appliance PPS / CPU overload

ID rt-appliance-pps · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

防火墙、入侵防御设备(IPS)、DDoS 防护设备会逐个检查经过的数据包。一旦超出检测能力,处理不过来的包就会被丢弃。

起因 高峰时段或活动期间,体积小的游戏数据包每秒涌入几十万个以上,或检测规则太重 → 结果 设备的 CPU 或每秒包数上限被打满,在设备上丢弃。误判时正常数据包也会被拦截 → 画面表现 这台设备后面的所有服务器同时出现卡住、瞬移,只在人多时加重

症状
卡住, 快进, 瞬移, 掉线
因素
丢包, 延迟
谁会遇到
全服, 特定地区/运营商
何时出现
晚高峰, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发
研发团队要做的事
把游戏流量模式(端口、包大小、每秒包数)同步给运维团队;一个 tick 内要发的小消息攒起来一次发出,减少包数。
运维团队要做的事
把设备的 CPU、每秒包数、丢弃计数器与游戏指标放在一起看;按小包规划设备容量;让游戏端口跳过重度检测;按游戏流量模式调整 DDoS 防护规则。
数值参考
设备规格里的“10 Gbps”多半是按 1,500 字节的大包标的。游戏数据包只有 100 字节左右,同样的带宽下包数要多 10 倍以上,所以线路看着很空闲,每秒包数上限却先被打满。
监控图上
触顶后走平 · 设备每秒包数和 CPU 利用率,设备丢弃数
查看位置
看设备的 CPU、每秒包数和丢弃计数器,并按相同间隔比较设备前后交换机端口的包数。与同时在线人数、服务器重传率叠加在同一张图上看
确认依据
高峰或活动时,设备的每秒包数或 CPU 停在某个值上不去,出设备的包比进设备的少,同一时刻其后所有服务器的重传率一起上升
排除依据
设备前后包数一致,设备也没有丢弃,则是其他原因。服务器的网卡丢弃计数器或 softnet dropped 增加,看“接收端服务器主机丢包”
确认手段
运维工具即可确认(无需游戏代码)
出处 1 条

MTU 黑洞(只有大包反复丢失) PMTU black hole

ID rt-mtu · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发

中间链路能通过的包变小了,“包太大”的通知(ICMP)却被拦截,大包无论重发多少次都会丢失。

起因 VPN 或隧道段的最大包长变小,包过大通知被防火墙拦截 → 结果 发送方不知道原因,一直重传同一个大包,RTO 每次翻倍 → 画面表现 平时正常,一到打开背包、人多的地方、进场加载这类要传大数据的时刻,连后面跟着的小包也全部卡住,最终掉线或无限加载

症状
卡住, 掉线, 连不上/无限加载
因素
丢包
谁会遇到
特定地区/运营商, 只有我
何时出现
做特定操作时, 刚登录/维护结束后
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
研发团队要做的事
要在服务器侧直接调小,就设置 socket 的最大报文段长度(TCP_MAXSEG);只在游戏代码里把消息切小防不住(TCP 会把待发数据重新按 MSS 大小打包)。
运维团队要做的事
网络:在边界设备上做 MSS 调整(clamping);在防火墙和云网络 ACL 中放行包过大 ICMP(类型 3 代码 4,fragmentation needed)。服务器/OS:设置路径 MTU;确认服务器防火墙和云安全组也没有拦截包过大 ICMP;最后一道保险是 Linux tcp_mtu_probing=1。
数值参考
通常为 1,500 字节,经过隧道后约 1,400。同一个包重传 5~6 次,卡住的时间就会超过 10 秒。
监控图上
仅部分偏高 · 每个连接的 RTO、backoff,各地区、各运营商的掉线数
查看位置
用服务器侧抓包或 bcc tcpretrans -s(显示序列号)查看问题连接的重传,用 ss -ti 看该连接的 mss、pmtu、backoff。从服务器向该玩家地址分别发小 ping 和带 DF 标志的 1,500 字节 ping(ping -M do -s 1472),比较结果
确认依据
装满 MSS 的包以同一序列号反复重传,间隔每次翻倍,比它小的包能正常往来。收不到包过大 ICMP(Wireshark 过滤条件 icmp.type == 3 and icmp.code == 4);小 ping 有响应,只有带 DF 标志的大 ping 没有响应
排除依据
小包也一起丢失,就是与包大小无关的丢包(“瓶颈队列溢出”“路径变更/ECMP 故障路径”)。收到包过大 ICMP 且 ss -ti 的 pmtu 变小,说明路径 MTU 发现工作正常
确认手段
运维工具即可确认(无需游戏代码)
深入了解
tcp_mtu_probing=1 要等重传超时持续几秒(相当于 tcp_retries1=3)之后,才判定为黑洞并把 MSS 降到 1,024 字节。这段时间画面是卡住的,所以它只能当最后一道保险,优先做能提前预防的 MSS 调整。
出处 15 条

连接中途 NAT/负载均衡器映射过期 NAT / load balancer mapping expired mid-connection

ID rt-mapping · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维, 运维团队·系统运维

中间设备删掉空闲连接的映射(记录该连接应转发到哪里的条目)后,之后发出的包就送不到了。要么反复重传后掉线,要么设备回一个拒绝连接的 RST,立刻断开。

起因 一段时间内没有数据包往来的连接(暂离、大厅) → 结果 路由器 NAT、运营商 CGNAT、防火墙、负载均衡器、云安全组删除空闲映射 → 画面表现 再次操作的瞬间开始连续重传,然后掉线,或者直接掉线

症状
掉线, 卡住
因素
丢包
谁会遇到
只有我, 特定地区/运营商
何时出现
挂机一段时间后
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维, 运维团队·系统运维
研发团队要做的事
客户端:以不超过最短空闲超时一半的间隔发送心跳(玩家路由器和运营商 CGNAT 的映射只有靠从内往外发的包才能可靠刷新,超时时间我方也改不了,所以由客户端发送);断开后自动重连。服务器:响应心跳,一段时间收不到就主动清理连接(缩短 TCP keepalive 间隔(TCP_KEEPIDLE 等 socket 选项),用 TCP_USER_TIMEOUT 尽早发现);凭会话令牌接续会话。
运维团队要做的事
网络:汇总路径上各防火墙、负载均衡器的空闲超时,同步给研发团队;必要时调大我方防火墙、负载均衡器的超时。服务器/OS:确认云安全组的连接跟踪时间,同步给研发团队。
数值参考
各设备保留 TCP 映射的时间从几分钟到几小时不等。云安全组配置为跟踪连接时,AWS Nitro v6 实例类型默认 350 秒后删除跟踪条目(其他类型为 5 天,看“云安全组连接跟踪过期”)。Linux TCP keepalive 默认“空闲 2 小时才探测”,比大多数设备都晚。
监控图上
连接成批断开 · 断开次数、断开前的空闲时长
查看位置
用服务器侧抓包查看断开连接的最后几分钟;存活的连接用 ss -ti 的 lastsnd、lastrcv(距最后一次发送、接收经过的 ms)看空闲时间。同时看 nstat 的 TcpExtTCPAbortOnTimeout(因定时器到期而放弃连接的次数)
确认依据
每个断开的连接,断开前的空闲时间都超过了某个相近的值(路径上设备的空闲超时,例如 AWS Nitro v6 实例安全组的 350 秒);空闲后从第一个包起就收不到 ACK,只有重传,最后放弃,或立即收到 RST
排除依据
与空闲时间无关、游戏过程中也会断开,则是其他原因(“路径变更/ECMP 故障路径”“防火墙/连接跟踪丢包”)。心跳间隔不超过最短空闲超时一半的连接,可排除这个原因
确认手段
运维工具即可确认(无需游戏代码)
出处 8 条

路径变更/ECMP 故障路径 Route change / bad ECMP member

ID rt-path · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部

互联网路由切换的几秒内,或者被分配到多条 ECMP 路径中某条故障路径的连接上,会出现丢包。

起因 BGP 路由重新计算,或多条路径(ECMP、LAG)中某一条的设备或线路故障 → 结果 路径切换期间暂时丢包,或只有走这条路径的连接持续丢包 → 画面表现 突然卡住几秒后快进,或者“重连就好了”(被分到了别的路径)

症状
卡住, 快进, 瞬移
因素
丢包
谁会遇到
特定地区/运营商
何时出现
偶尔随机
负责方
主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部
研发团队要做的事
记录每个连接的重传统计(TCP_INFO),以便提取受影响玩家的 IP、端口和时间点;连接停顿几秒时不要马上断开。
运维团队要做的事
按地区、运营商监控重传率;确认重连后路径是否改变;接入多家运营商线路;排查我方设备 ECMP、LAG 路径中的故障链路;测路径时也用和游戏相同的 TCP 端口(mtr --tcp --port。路径由地址和端口决定,普通 ping 可能走另一条路径,结果显示一切正常)。
外部要做的事
向运营商报告故障路径,附上用相同 TCP 端口测得的路径结果和重连前后的对比。
监控图上
某一时刻起台阶式上升 · RTT(ping),各地区、各运营商的重传率
查看位置
用 bcc tcpretrans -c 按连接汇总重传,提取受影响玩家的地址和端口;从服务器到玩家、从玩家到服务器,都用和游戏相同的 TCP 端口跑 mtr(mtr -T -P PORT)并比较。重连前后的结果也要比较
确认依据
以某一时刻为界,某地区或运营商的 RTT 像台阶一样跳变,同时出现几秒的集中丢包;或者同一运营商内只有部分连接(地址、端口组合)持续重传,重连后好转。有时普通 ping 一切正常,只有 TCP mtr 能看到丢包
排除依据
该运营商的所有连接在晚高峰一起变差,看“瓶颈队列溢出”。只有一名玩家差,且 ping 路由器就已丢包,看“无线链路丢包”
确认手段
运维工具即可确认(无需游戏代码)
真实案例
Cloudflare 2020: Cloudflare 骨干网配置错误导致部分城市流量丢失
出处 5 条

延迟飙升导致的虚假重传 Spurious RTO from delay spikes

ID rt-spurious-delay · 主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·客户端开发

数据包只是一时到得特别晚,并没有丢。但只要这段延迟超过 RTO,发送方就会判定为丢包并重传。

起因 缓冲区膨胀、Wi-Fi 省电、移动网络无线状态切换、虚拟机挂起,使瞬时延迟达到几百 ms → 结果 RTO 先到期并重传,原包随后也到达(接收方收到重复数据) → 画面表现 卡住和快进是延迟飙升本身造成的。虚假重传几乎不会让卡住变长,只会推高重传指标,被误认为丢包

症状
卡住, 快进, 操作延迟
因素
延迟, 抖动
谁会遇到
只有我, 全服
何时出现
偶尔随机, 挂机一段时间后
负责方
主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·客户端开发
研发团队要做的事
Android 10 及以上的客户端在游戏中申请低延迟 Wi-Fi 模式(WIFI_MODE_FULL_LOW_LATENCY Wi-Fi 锁,仅在屏幕亮着且游戏在前台时生效),减少省电造成的延迟飙升。
运维团队要做的事
避免使用突发性能型实例;不要把 RTO 最小值调得太低;保持 F-RTO 和时间戳开启(tcp_frto、tcp_timestamps);重传指标要和 nstat 的 TCPSpuriousRTOs、TCPDSACKRecv 一起看,以免误判为丢包。
外部要做的事
引导玩家在路由器上启用 SQM、关闭 Wi-Fi 省电,从源头减少延迟飙升。
数值参考
Linux 会用 F-RTO 检测虚假 RTO,有时还会撤销已做的降速。可以用 nstat 的 TCPSpuriousRTOs(判定为虚假 RTO 的次数)和 TCPDSACKRecv(接收方告知“已经收到过”的次数)确认。
监控图上
偶发随机尖峰 · RTT(ping),虚假 RTO 次数
查看位置
每 1 分钟运行一次 nstat,同时看 TcpExtTCPTimeouts(RTO 超时)、TcpExtTCPSpuriousRTOs、TcpExtTCPDSACKRecv、TcpExtTCPLostRetransmit 的增量。有抓包时用 Wireshark 过滤条件 tcp.analysis.spurious_retransmission
确认依据
RTO 增加时 TcpExtTCPSpuriousRTOs 或 TcpExtTCPDSACKRecv 也一起增加,同一时刻 RTT 跳到几百 ms。接收方抓包中原包和重传包都已到达
排除依据
TcpExtTCPSpuriousRTOs 和 DSACK 不变,TcpExtTCPLostRetransmit(重传的包又丢了)却增加,就是真实丢包。RTT 没有跳变,只有 DSACK 一直偏多,看“乱序导致的虚假快速重传”
确认手段
运维工具即可确认(无需游戏代码)
出处 11 条

乱序导致的虚假快速重传 Reordering triggers spurious fast retransmit

ID rt-reorder · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

数据包经过多条路径或聚合链路时顺序被打乱,接收方会用重复 ACK 通知“有包缺失”,发送方就把好好的包又发一遍。

起因 按包分配路径的设备、按包分摊的 LAG(链路聚合)、路径切换的瞬间,都会打乱顺序 → 结果 后面的包先到,累积 3 个重复 ACK → 快速重传 → 画面表现 稀疏往来的游戏包几乎不受影响。人多处的大块状态更新和更新包下载会变慢,偶尔一卡一卡

症状
一卡一卡, 操作延迟
因素
抖动
谁会遇到
特定地区/运营商, 全服
何时出现
一直, 人多的时候
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维
运维团队要做的事
网络:把按包分流改成按连接分流(ECMP、LAG 按地址和端口做哈希)。服务器/OS:使用 RACK(基于时间判断丢包,不怕乱序;通过 DSACK 检测到虚假重传时会自动放宽乱序容忍范围);查看 Linux 为每个连接自动估算的乱序程度(ss -ti 的 reordering 值,初始值为 tcp_reordering=3)。
监控图上
一直偏高 · 乱序检测次数,DSACK 接收数
查看位置
看 nstat 的 TcpExtTCPSACKReorder、TcpExtTCPTSReorder(检测到乱序的次数)和 TcpExtTCPDSACKRecv;按连接看 ss -ti 的 reordering(不为 3 时才显示)和 reord_seen。抓包中用 Wireshark 过滤条件 tcp.analysis.out_of_order
确认依据
乱序计数器和 DSACK 不分时段持续上升,经过特定路径或设备的连接 reordering 值大于 3。接收方抓包中后面的包先到,前面的包随后也到达
排除依据
乱序计数器不变,TcpExtTCPLostRetransmit 增加,就是真实丢包。只在 RTT 跳变的瞬间 DSACK 增加,看“延迟飙升导致的虚假重传”
确认手段
运维工具即可确认(无需游戏代码)
出处 9 条

ACK 延迟或丢失(上行饱和) ACK path congestion on asymmetric links

ID rt-ack-path · 主责 外部·外部 · 配合 研发团队·客户端开发

数据已经顺利到达,但表示“已收到”的 ACK 在塞满的上行队列里被耽搁或丢掉,发送方就会判定为丢包并重传。

起因 家里在上传视频或做云备份,上行被占满 → 结果 ACK 在路由器队列里被耽搁几百 ms,或因队列溢出被丢弃 → 画面表现 服务器发来的游戏包大多能按时到。堆在同一上行队列里的自己的输入被耽搁,出现操作延迟、拉回,偶尔有虚假重传

症状
操作延迟, 拉回
因素
延迟, 丢包
谁会遇到
同一家庭
何时出现
偶尔随机, 晚高峰
负责方
主责 外部·外部 · 配合 研发团队·客户端开发
研发团队要做的事
ping 飙升时在画面上显示网络状态,并弹出“检查正在上传的程序”的提示。
外部要做的事
引导玩家用路由器 SQM 缩短上行队列,优先处理小包(ACK),给视频上传、云备份限速。
数值参考
后面的 ACK 能替前面的 ACK 完成确认,所以丢几个通常没关系。真正的问题是在队列里被耽搁。
监控图上
仅部分偏高 · 每个连接的 RTT(ping)
查看位置
在玩家 PC 上分别于开启上传(视频上传、云备份)和关闭上传时 ping 游戏服务器,比较两者结果。服务器上用 ss -ti 看该玩家连接的 rtt
确认依据
只在上传期间 ping 升到几百 ms,出现操作延迟和拉回,停止上传后很快恢复。从服务器看,那段时间该连接的 rtt 也一起上升
排除依据
与上传无关也出现丢包和延迟,看“无线链路丢包”或路径方面的原因。只有服务器到玩家方向慢,且与上传无关,看“瓶颈队列溢出”
确认手段
需在玩家侧环境确认
出处 4 条

RTO 设置与环境不匹配 RTO min too low or too high

ID rt-rto-setting · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

RTO 最小值调得太低,稍微晚一点就会产生虚假重传;默认值(200 ms)对游戏来说又太长,每丢一次包都要停顿很久。

起因 为数据中心场景把 RTO 最小值调得很低,或者在公网链路上原样使用默认值 → 结果 太低时瞬间的延迟也会引发大量重传,太高时每次丢包都要等很久 → 画面表现 用默认值时,丢一次包就卡住几百 ms 再快进;调得太低,卡住会缩短,但虚假重传激增,浪费线路

症状
卡住, 快进, 操作延迟
因素
延迟
谁会遇到
全服
何时出现
一直
负责方
主责 运维团队·系统运维 · 配合 研发团队·服务器开发
研发团队要做的事
Linux 6.15 及以上时,考虑对游戏连接用 TCP_RTO_MAX_MS 降低 RTO 上限(放弃连接所需的时间也会变短,所以要同时用 TCP_USER_TIMEOUT 设定断线判定时间);只对服务器间的内部连接用 socket 选项 TCP_RTO_MIN_US(6.15 及以上)降低 RTO 最小值;考虑用 socket 选项 TCP_THIN_LINEAR_TIMEOUTS,只让游戏连接的连续 RTO 不再翻倍。
运维团队要做的事
只对服务器间的内部连接按路由降低 rto_min;公网链路保持默认值,用 RACK-TLP 和 thin stream 设置(tcp_thin_linear_timeouts)弥补。
数值参考
Linux RTO = 往返时间 + max(200 ms, RTT 偏差×4)。每失败一次翻倍,最大 120 秒。Linux 6.15 及以上可以用 TCP_RTO_MAX_MS 把这个上限最低降到 1 秒。
监控图上
一直偏高 · 每个连接的 RTO,虚假 RTO 次数
查看位置
看服务器的 RTO 最小值设置(ip route show 的 rto_min,Linux 6.11 及以上还有 sysctl net.ipv4.tcp_rto_min_us)和 ss -ti 的 rto、rtt,并看 nstat 中 TcpExtTCPSpuriousRTOs 的增量
确认依据
在调低了最小值的服务器上,公网连接的 rto 紧贴 rtt,TcpExtTCPSpuriousRTOs 大量增加。保持默认值时,游戏连接的 rto 比 rtt 大 200 ms 以上,每丢一次包就停顿这么久
排除依据
rto 符合默认算法(rtt + 200 ms 左右),虚假 RTO 也少,停顿却特别长,看连续丢包或恢复机制方面(“thin stream 恢复慢”“中间设备剥离 TCP 选项”)
确认手段
运维工具即可确认(无需游戏代码)
出处 12 条

thin stream 恢复慢 Thin streams fall back to RTO

ID rt-thin · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发

像游戏这样稀疏地发小包时,还没等到“后续 3 个包”,RTO 就先到期了。同样的丢包,停顿时间比大流量传输长得多。

起因 包间隔在 100 ms 左右,已发出未确认的数据包(in-flight)只有寥寥几个 → 结果 攒够 3 个重复 ACK 要 300 ms 以上,RTO(ping + 200 ms)先触发;连续丢包则每次翻倍 → 画面表现 丢一次包卡住 0.3 秒左右,重传的包也丢了就卡住将近 1 秒,然后快进

症状
卡住, 快进
因素
丢包, 停顿
谁会遇到
只有我, 全服
何时出现
偶尔随机
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发
研发团队要做的事
服务器:开启 TCP_NODELAY(Nagle 开着时,RACK 没有后续数据包可用来判断);实时数据包改用基于 UDP 的自有重传。客户端:开启 TCP_NODELAY;实时数据包与服务器用同样的方式(UDP)。
运维团队要做的事
使用 RACK-TLP(新版 Linux 默认);用 tcp_thin_linear_timeouts 让连续 RTO 不再翻倍。
数值参考
包间隔 100 ms、ping 60 ms 时,到快速重传约需 360 ms(等后面 3 个包到达且其确认返回),RTO 约为 260 ms。用 RACK 时,下一个包的确认在约 160 ms 返回时就会立即重传。包间隔超过 200 ms 时,RACK 也不比 RTO 快。
监控图上
断流后集中到达 · 每个连接的接收量,RTO 超时次数
查看位置
比较 nstat 中 TcpExtTCPTimeouts(RTO 超时)、TcpExtTCPFastRetrans(快速重传)、TcpExtTCPLossProbes、TcpExtTCPLossProbeRecovery(TLP)的增量,用 ss -ti 看游戏连接的 rto、backoff。同时确认服务器的 net.ipv4.tcp_recovery、tcp_early_retrans、tcp_sack 取值
确认依据
重传中 RTO 超时多于快速重传,游戏连接经常出现 backoff 大于 0(正在经历 RTO)。停顿期间接收量为 0,恢复后集中涌入
排除依据
同一服务器上的大流量传输也同样长时间停顿,就是与连接形态无关的丢包问题。集中在缺少 SACK 和时间戳的连接上,看“中间设备剥离 TCP 选项”
确认手段
运维工具即可确认(无需游戏代码)
深入了解
Linux 以前还有一个面向 thin stream、收到 1 个重复 ACK 就重传的选项(tcp_thin_dupack),2017 年已删除,现在由 RACK 承担这个作用。Nagle 开着(TCP_NODELAY 关闭)时,等待丢失包的确认期间也不发新包,RACK 没有后续数据包可用来判断,只能等到 RTO。
出处 11 条

中间设备剥离 TCP 选项 Middlebox strips TCP options

ID rt-sack-stripped · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

部分防火墙或加速设备删除或改写 TCP 选项后,丢多个包时每个往返只能恢复一个,或者窗口(一次能发送的量)变小,速度变慢。

起因 防火墙的“TCP 规范化”、老旧的加速设备剥离 SACK、时间戳、窗口缩放选项 → 结果 丢了多个包时每个往返只恢复一个,窗口被限制在 64 KB → 画面表现 每次丢包卡住的时间长得多(没有 SACK 就用不了 RACK-TLP),恢复后表现为快进。下载更新包这类大流量传输也很慢

症状
卡住, 快进
因素
停顿, 延迟
谁会遇到
特定地区/运营商, 全服
何时出现
一直
负责方
主责 运维团队·网络运维 · 配合 运维团队·系统运维
运维团队要做的事
网络:关闭相关设备的 TCP 规范化设置;同时检查防火墙的序列号随机化;在两端抓包比较 SYN 中的选项。服务器/OS:在 ss -ti 中确认缺少 sack、wscale 标记的连接是否集中在特定路径(Windows PC 按设置可能不使用 ts,所以只缺 ts 可能是正常的);确认服务器的 net.ipv4.tcp_sack 为 1。
监控图上
一直偏高 · 不带 SACK 开始的恢复次数(TcpExtTCPRenoRecovery)
查看位置
在 ss -ti 中看每个连接有没有 sack、wscale 标记,看 nstat 中 TcpExtTCPRenoRecovery(不带 SACK 开始的恢复)与 TcpExtTCPSackRecovery 的比例,以及 TcpExtTCPSACKDiscard(因对不上而丢弃的 SACK 块数)。对可疑路径在两端抓 SYN,比较其中的选项(Wireshark 的 tcp.options.sack_perm 等)
确认依据
只有经过特定路径或设备的连接缺少 sack、wscale,TcpExtTCPRenoRecovery 占比高。发送端 SYN 里有的 SACK 允许选项,接收端收到的 SYN 里没有。如果原因是序列号随机化,选项还在,但 TcpExtTCPSACKDiscard 增加
排除依据
所有连接都缺少 sack 时,先查服务器的 net.ipv4.tcp_sack 值。选项完整,TcpExtTCPSACKDiscard 也不变,则恢复慢另有原因(“thin stream 恢复慢”)
确认手段
运维工具即可确认(无需游戏代码)
深入了解
即使选项还在,SACK 也可能失效。防火墙的序列号随机化(sequence randomization)只改头部的序列号、不改 SACK 里的序列号时,发送方会丢弃对不上的 SACK。2019 年爆出 SACK 安全漏洞时,在服务器上用 tcp_sack=0 关掉 SACK 后忘了恢复,结果也一样。
出处 10 条

零窗口(看起来像重传的停顿) Zero window, often mistaken for retransmission

ID rt-zero-window · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·系统运维

接收方程序没有及时读取 socket,缓冲区满了之后,发送方就会停止发送,只发零窗口探测。这与线路无关。

起因 客户端帧停住或服务器线程阻塞,读不了 socket → 结果 接收窗口变为 0,发送方停止发送,只发探测(间隔越来越长) → 画面表现 卡住后快进。抓包中能看到“ZeroWindow”,没有丢包

症状
卡住, 快进
因素
停顿
谁会遇到
只有我, 全服
何时出现
人多的时候, 偶尔随机
负责方
主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·系统运维
研发团队要做的事
先在抓包中确认是哪一方发出 ZeroWindow(读不了 socket 的一方);网络接收放在独立线程中持续读取;接收缓冲区设为合适大小。客户端:解决加载、GC 等导致帧停住的原因。服务器:解决读 socket 的线程被阻塞的原因。
运维团队要做的事
把服务器 nstat 的 TcpExtTCPToZeroWindowAdv(服务器通告接收窗口为 0 的次数)加入监控(增加说明问题在服务器侧,转交服务器开发);提供服务器侧抓包。
监控图上
断流后集中到达 · 每个连接的接收量,零窗口次数
查看位置
在抓包中用 Wireshark 过滤条件 tcp.analysis.zero_window 找出通告窗口为 0 的一方。服务器 nstat 中分别看 TcpExtTCPToZeroWindowAdv(服务器通告窗口为 0)和 TcpExtTCPWinProbe(对方窗口为 0 时发送探测),并看服务器 socket 的 Recv-Q(ss 中程序尚未读取的字节)
确认依据
停顿期间没有重传,只有零窗口和探测在往来。服务器的 TcpExtTCPToZeroWindowAdv 或服务器 socket 的 Recv-Q 增加,说明服务器没能及时读取;TcpExtTCPWinProbe 增加,说明客户端没能及时读取
排除依据
抓包中没有零窗口,且同样的数据被重新发送,看丢包或虚假重传方面的原因
确认手段
运维工具即可确认(无需游戏代码)
真实案例
Roblox 2021: Roblox 73 小时故障:服务发现(Consul)集群的争用问题
出处 7 条

连接请求(SYN)重传 SYN retransmission on connect

ID rt-syn · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维, 研发团队·客户端开发

连接请求因连接队列(backlog)溢出或被防火墙拦截而丢失时,客户端操作系统会从 1 秒后开始,以逐渐拉长的间隔重发。

起因 维护结束后连接涌入,服务器连接队列溢出,或防火墙、DDoS 防护丢弃 SYN → 结果 客户端操作系统从 1 秒后开始按固定间隔重传 SYN(旧版 Linux 为 1 秒 → 2 秒 → 4 秒) → 画面表现 点击连接后延迟正好是 1 秒、3 秒这样的整秒数,一直失败就会连不上/无限加载

症状
连不上/无限加载
因素
丢包
谁会遇到
全服, 特定地区/运营商
何时出现
刚登录/维护结束后
负责方
主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维, 研发团队·客户端开发
研发团队要做的事
服务器:调大 listen 的 backlog 参数(与 somaxconn 一起);让游戏服务器及时调用 accept;登录排队系统。客户端:拉长连接重试间隔(随机打散)。
运维团队要做的事
服务器/OS:用 nstat 的 TcpExtListenOverflows、TcpExtListenDrops 和日志中的“Possible SYN flooding”警告确认服务器连接队列是否溢出;调大 somaxconn(与 listen 参数一起);启用 SYN Cookie。网络:放宽防火墙、DDoS 防护的 SYN 限制。
数值参考
Linux(包括 Android)的第一次 SYN 重传在 1 秒后。旧内核此后间隔每次翻倍,在第 1、3、7、15 秒……时重发,6.5 及以上版本则在 1、2、3、4、5 秒重发五次,之后再翻倍(7、11、19 秒……)(tcp_syn_linear_timeouts=4)。Android 手机即使升级了系统,往往还在用出厂时的内核,所以即使 Android 版本相同,不同机型也可能不一样。无论哪种,全部失败后约 2 分钟放弃。Windows 视版本和设置从 1 秒或 3 秒开始拉长,重发次数为 2~4 次,20~30 秒就放弃(该 PC 的值可用 netsh int tcp show global 的 Max SYN Retransmissions 查看)。
监控图上
开服/维护后激增 · 连接尝试数,连接队列溢出数
查看位置
看服务器 nstat 中的 TcpExtListenOverflows、TcpExtListenDrops 和 dmesg 中的“Possible SYN flooding on port”警告,用 ss -lnt 看监听 socket 的 Recv-Q(等待 accept 的连接数)是否达到 Send-Q(backlog 上限)。用服务器侧抓包确认 SYN 是否到达、是否回了 SYN-ACK
确认依据
维护结束后连接涌入时 TcpExtListenOverflows 增加,Recv-Q 紧贴 Send-Q。抓包中同一客户端的 SYN 以秒级间隔重复到来,服务器却不响应
排除依据
SYN 没到服务器,服务器计数器也不变,就是前端防火墙或 DDoS 防护丢弃了,看该设备的 SYN 限制和丢弃日志。服务器已回 SYN-ACK 而连接仍慢,就是回程方向丢包
确认手段
运维工具即可确认(无需游戏代码)
出处 11 条

分场景排查流程

版本更新后卡顿

某次版本更新或发布之后卡顿反馈变多时使用。“这次更新以后就不对劲”的反馈集中出现,或监控图从某一时刻起台阶式上升并一直停在高位时,都按这个流程排查。

  1. 确定开始时间,收集前后所有变更: 找出反馈最早集中出现的时刻和监控图台阶式上升的时刻,把这前后上线的变更一个不漏地列出来。客户端更新、服务器发布、配置变更、DB 表结构变更(DDL)与重启、网络和防火墙作业、基础设施更换(实例规格、内核、驱动)都要一起看。如果每次发布都用监控工具的标注(annotation)功能在所有监控图上留下一条竖线,这一步很快就能完成。游戏版本更新和基础设施作业在同一个维护时段上线时,两者都要留作候选。优先联系:提交变更的研发团队和运维团队双方。 (发布/重启, OS/内核/驱动/固件更新后的性能变化, 线上表结构变更(DDL)锁, 执行计划变化导致查询变慢, 冷缓存(刚重启时))
  2. 划分范围:版本、设备、服务器、地区: 看异常集中在哪个维度。只有新版本客户端的用户变差,先怀疑客户端;只有特定 OS、显卡、设备变差,先怀疑客户端性能或驱动;只有特定服务器、分线、场景变差,先怀疑服务器;只有特定国家/地区、运营商变差,先怀疑网络路径;所有人同时变差,先怀疑公共资源(DB、负载均衡器、网关)或刚上线的服务器发布。如果客户端遥测带有版本号,就把旧版本和新版本的 ping、FPS、帧耗时尖峰、掉线次数并排对比。ping 没变而只有 FPS 变差,比起网络,更可能是客户端性能的问题。优先联系:集中在版本、设备上,找研发团队(客户端);集中在服务器、分线上,主机指标正常时找研发团队(服务器),异常时找运维团队(服务器/OS);集中在国家/地区、运营商上,找运维团队(网络)。 (帧耗时尖峰, 主线程同步加载/Shader 编译, 显存(VRAM)不足, 客户端崩溃)
  3. 在同一时段对比新旧版本: 只对比发布前后,星期、时段、活动带来的变化会混在一起,影响判断。条件允许时,先把新版本放到部分服务器上(金丝雀),与同一时段运行旧版本的服务器(对照组)并排对比 tick 耗时 p50/p99、tick 超预算次数、CPU、内存、错误率。如果已经全量发布,就和上周同一天的同一时段对比。只看全服平均值会掩盖部分服务器、场景的问题,所以要按服务器、场景拆开看。优先联系:研发团队(服务器)。 (Tick 超出预算, 内存分配暴增, 内存泄漏, 广播激增)
  4. 对比更新前后的流量指纹: 即使不了解服务器代码,也能用网络侧看得到的数值确认这次更新是否改变了流量形态。对比更新前后每个玩家的每秒包数(pps)和字节数、平均和最大数据包大小、连接数,以及每个 tick 集中发出的突发发送大小。UDP 数据包开始超过路径 MTU(通常为 1,500 字节)时,就会发生 IP 分片。只要丢一个分片,整个数据包就丢了,还有些 NAT、防火墙会直接丢弃分片。途经 MTU 较小链路(隧道、VPN)的玩家,只有大包会消失。如果 pps 增加了,要看是否碰到了云实例的 PPS 上限,或防火墙、DDoS 防护设备的处理上限。优先联系:指纹变了,附上证据找研发团队(服务器);指纹没变、只有丢包和重传增加,找运维团队(网络)。 (版本更新导致流量特征变化, UDP 数据包的 IP 分片, MTU 黑洞(只有大包反复丢失), 超出云 PPS 上限, 中间设备超出处理上限(防火墙/IPS/DDoS 防护), 突发发送导致浅缓冲区溢出)
  5. 对比更新前后 DB 查询的种类和次数: DB 延迟上升时,先看查询量(QPS)是否也一起上升。PostgreSQL 的 pg_stat_statements 和 MySQL Performance Schema 的 digest 汇总,会把只有参数值不同的查询归为一类,统计执行次数和总耗时。对比更新前后的 Top 查询列表,就能找出新出现的查询、次数翻了几倍的查询(N+1),以及不走索引、读全表的查询(MySQL 看 SUM_NO_INDEX_USED 列)。优先联系:QPS 或查询形态变了,找研发团队(服务器);查询没变、只有延迟增加,找运维团队(DB:执行计划、IOPS、锁)。 (缺少索引的查询, 登录激增与 N+1 查询, 执行计划变化导致查询变慢, 缓存雪崩)
  6. 用主机和服务器进程指标区分层级: 不看代码,只用 OS 上看得到的数值区分问题出在服务器进程内部还是主机。服务器 socket 的接收队列(Recv-Q)堆积,说明服务器进程没能及时读取(tick 停顿、GC、锁);只有一个线程跑到 100%,是单线程瓶颈;GC 日志里的停顿时间变长,说明内存使用模式变了。还要看是否带着调高的日志级别发布,导致日志写入增加。反过来,如果 CPU 窃取时间(steal)、CPU 限流、网卡丢包增加,就去看同一时刻变更过的基础设施(实例规格、内核、容器资源上限)。优先联系:进程内部的信号找研发团队(服务器),主机的信号找运维团队(服务器/OS)。 (服务器 GC 全局停顿, 单线程区域过载(热点), 同步写日志, 容器 CPU 限流(CFS 配额), CPU 窃取时间(虚拟机), OS/内核/驱动/固件更新后的性能变化)
  7. 通过回退确认原因并记录: 只在部分服务器或部分玩家身上撤销最可疑的变更(回滚、关闭功能开关),或把配置改回旧值,看症状是否随之消失。只有回退的一侧好转,原因就确定了。回退本身也可能因重启和冷缓存短暂变慢,不着急的话放在低峰时段做。把结果连同原因 ID 记入故障记录,并把数据包大小、查询次数、tick 耗时的上限加入下次版本更新的发布前检查项。优先联系:提交变更的团队。 (发布/重启, 冷缓存(刚重启时))

新增海外国家/地区

新开服务国家/地区,或新增区域、数据中心时使用。上线前的检查,以及判断“国内没问题,只有新国家的玩家卡”这类反馈时,都可以用这个流程。

  1. 上线前测量当地各运营商的线路质量: 针对目标国家的每家主要运营商(ASN),测量到候选游戏服务器位置的往返时间(RTT)分布、抖动(到达间隔的波动)和丢包。只看一个平均值会掩盖运营商之间的差异,所以要按运营商分别看中位数和第 95 百分位数,并区分晚高峰和凌晨。公开测量网络 RIPE Atlas 可以按国家、ASN 挑选全球的探针发送 ping 和 traceroute;也可以在候选区域临时开一台 VM 来测。途中的设备有时会限制 ICMP 应答,所以条件允许时,也要用和游戏相同的协议、端口测一遍。如果只有某家运营商特别绕经远方城市,就是对等互联或路由问题。运营商选路时比起延迟更看重成本,所以近处的目的地也可能绕远路。优先联系:运维团队(网络);路由问题在运营商一侧时,找外部(运营商、IX)。 (传播延迟(物理距离), 路由绕行, 高峰时段对等互联链路拥塞, 海底光缆/国际线路故障)
  2. 把测量值与游戏设计能承受的上限对比: 把测得的 RTT、抖动与游戏的判定窗口(闪避、弹反这类反应时间)、延迟补偿上限、插值缓冲长度、输入缓冲大小对比。例如弹反判定窗口为 0.2 秒时,往返延迟加上插值缓冲超过这个值的运营商用户,即使及时反应也来不及。如果放宽延迟补偿来迁就他们,挨打的一方又会反馈“明明躲到墙后了还被打中”。超出上限的运营商较多时,运维团队要考虑把区域、边缘 PoP 部署得更近,研发团队要重新评估判定、插值、延迟补偿的参数。本白皮书的“同步方式”一章就是对照标准。优先联系:研发团队(服务器、客户端:设计上限),运维团队(网络:区域、PoP 位置)。 (被 ping 吃掉的短判定窗口, 没有延迟补偿的判定, 延迟补偿过度, 没有插值缓冲或缓冲过短)
  3. 确认 MTU 和 UDP 能否通过: 确认游戏最大的数据包能否完整通过当地网络。发送设置了禁止分片(DF)标志、大小各不相同的 ping,测出路径 MTU,看途中是否有 PPPoE、隧道、移动网络这类小于 1,500 字节的链路。UDP 等数据报传输的标准(RFC 8899)建议 IPv4 以 1,200 字节作为大部分路径都能通过的基础大小;游戏的最大数据包超过这个值时,要和研发团队商定缩小或拆分发送的方案。还要确认公共 Wi-Fi、公司网络和部分运营商是否封锁 UDP 或游戏端口、是否限速,以及被封时有没有备用通道(TCP、443 端口)。优先联系:运维团队(网络)和研发团队(服务器:数据包大小)。 (MTU 不匹配(只有大包丢失), MTU 黑洞(只有大包反复丢失), UDP 数据包的 IP 分片, 国家/运营商级 UDP 限制与包检测, 公共 Wi-Fi/公司网络限制, 运营商限速/流量管理)
  4. 测量 NAT/CGNAT 空闲超时,调整心跳间隔: 测量当地家用路由器和移动网络(CGNAT)多久会删除空闲 UDP 连接的映射。每轮测试先让测试设备向服务器发一个数据包建立映射,之后设备不再发送任何数据,由服务器在预设的时间(30 秒、60 秒、120 秒……)过后向设备发包。设备开始收不到这个包的时间,就是该网络的空闲超时。标准(RFC 4787)规定 UDP 映射的过期时间不得短于 2 分钟,并建议默认 5 分钟以上,但各设备的取值差别很大,也有删除得更早的设备。只有从设备发出的数据包才能可靠地刷新映射,所以心跳要由客户端发送,并检查心跳间隔是否不超过测得值与负载均衡器、云安全组空闲超时中最小值的一半。优先联系:研发团队(客户端:心跳间隔;服务器:超时值),运维团队(负载均衡器、安全组配置)。 (NAT 映射过期, 运营商共享 IP(CGNAT), 连接中途 NAT/负载均衡器映射过期, 负载均衡器空闲超时, 云安全组连接跟踪过期)
  5. 检查当地会经过的外部服务和安全设备: 确认当地平台的登录、支付、实名认证能否以正常速度响应,当地 DNS 能否正确解析登录服务器和更新服务器的地址,CDN 是否从靠近该国的节点分发更新包。检查 DDoS 防护和防火墙的国家/地区封禁规则与限速规则是否误伤新国家的 IP 段,尤其要看多个用户共用一个 IP 的 CGNAT 地址段是否被整段封禁。优先联系:运维团队(安全设备、DNS、CDN),外部(平台、支付服务商、运营商)。 (依赖外部服务, DNS 故障/延迟, DDoS 防护引流/误判, 运营商共享 IP(CGNAT))
  6. 上线后按国家/地区、ASN 拆分查看: 给连接日志、负载均衡器日志里的客户端 IP 打上国家和 ASN 标签,按国家/地区、运营商查看 RTT、重传、掉线次数及原因(心跳超时、RST、服务器踢出)。用 MaxMind GeoLite ASN 这类免费数据库,可以把 IP 转换成 ASN 和组织名称;按照当地的个人信息保护法规,IP 只保留到 /24 或 ASN 粒度。问题只集中在一个 ASN 上,先看该运营商的路由(运维团队、外部);新国家整体都差,先看距离和设计上限(运维团队、研发团队);只在晚上变差,先看对等互联拥塞。如果只有部分玩家 ping 一直偏高,要和研发团队(服务器)一起排查是否因 GeoIP 错误、VPN 或按队长位置分配而被分到了远处的区域。拨测正常而只有玩家侧变差,问题在玩家环境或客户端。 (高峰时段对等互联链路拥塞, 路由绕行, 瓶颈队列溢出(拥塞丢包), 集中在特定运营商玩家身上的校验误判, 匹配/区域分配错误)
  7. 检查远距离玩家对其他玩家的影响: 远距离接入的玩家多了,影响不只是他们自己的画面变差。网络慢的玩家,输入会扎堆到达;在其他人的画面上,只有这个角色出现快进现象;还会触发服务器的速度、冷却检查,出现拉回或技能被拒。在需要全队配合的机制里,一个人反应慢就会导致整个队伍失败;采用帧同步时,所有人都要等最慢的那个人。新国家上线后,要看老玩家“只有某个角色看起来异常”的反馈是否增多,并与研发团队商定输入缓冲、校验容差、按地区分开匹配等方案。优先联系:研发团队(服务器)。 (网络差的玩家在别人画面上快进移动, 集中在特定运营商玩家身上的校验误判, 一个网络差的队友与 BOSS 机制, 帧同步中等待最慢的玩家)

真实故障案例

只收录游戏公司和基础设施公司自己公开发布的故障复盘。

CCP Games 2014: EVE Online HED-GP 大规模舰队战中的服务器过载

经过
据 2014 年 1 月的复盘文章,HED-GP 星系的大规模舰队战中服务器严重过载。Time Dilation(过载时放慢游戏时间的功能)已经降到下限 10%,整个战场都变成慢动作,负载却仍在继续堆积。处理模块停用与循环运转的任务积压程度(Dogma Lateness)最高达到游戏时间 193 秒,折合实际时间约 32 分钟。规模几乎相同的 2013 年 7 月 6VDT 之战,最高为 42 秒(实际约 7 分钟)。
原因
CCP 事先说明:性能分析工具本身就会增加负载,这种情况下不会开启,所以结论并不确定。在此前提下,CCP 列出了两个最可能的原因。一是战斗持续时间长,处理不完的负载不断累积。二是无人机使用量增加:战斗期间投放的无人机数量(去重)6VDT 为 21,123 架,HED-GP 为 38,852 架,多了 84%。一个人的操作要通知所有能看到的人,这类传输随人数的平方(O(n²))增长,而无人机每次攻击产生的消息更多。无人机选择攻击目标的代码也常常要遍历同一战场上所有可攻击的目标,开销接近按 n² 增长。
经验教训
人员密集区域的处理量超过上限时,整个区域都会变成慢动作;战斗越久,积压的处理越多,操作延迟越大。确认信号是负责该区域的服务器(节点)的 tick 耗时、积压任务量,以及人数和实体数,特征是其他区域一切正常。主责是研发团队(服务器),要改的是一次操作需要通知的对象范围,以及 AI 搜索目标的开销。放慢游戏时间的设计无法消除过载,但能让所有人以同样的速度变慢,避免只有部分操作被无限期推迟。
相关原因
广播激增, Tick 超出预算, 消息队列积压, 单线程区域过载(热点)
原文
CCP Games

Riot Games 2015: 绕远路的 League of Legends 流量与 Riot Direct

经过
这是 Riot Games 解释互联网为何不适合实时游戏的一篇技术文章。一位 League of Legends 玩家反馈的真实流量,本该从旧金山直达波特兰,实际却绕经洛杉矶、丹佛、西雅图,直达只需 14 ms 的路程用了 70 ms。Riot 解释说,路由器过载、丢弃数据包时,画面上其他英雄会跳来跳去,投射物看起来像在瞬移。
原因
Riot 把原因归结为路由和路由器。骨干网服务商和运营商转发流量时,比起延迟最短的路径,更倾向于走成本最低的路径;BGP 选出的路径一旦绕远,经过的路由器数量也随之增加。路由器的处理负担取决于数据包的个数,与包的大小无关。游戏数据包在 55 字节左右,同样的数据量,个数是 1,500 字节数据包的 27 倍,填满路由器输入缓冲区的速度也快这么多。按 Riot 的说法,很多路由器在过载时会先丢弃 UDP 数据包。作为解决方案,Riot 在美国 10 个大型互联网枢纽部署路由器,尽可能多地与运营商直连(对等互联),建成了自有网络 Riot Direct。据该系列第 2 部分,ping 低于 80 ms 的玩家比例在 9 个多月里从 31% 升到 50%;游戏服务器迁到芝加哥后,一夜之间达到 80%。
经验教训
即使在同一个国家,如果只有某家运营商的用户 ping 特别高,就要怀疑路由。确认信号是按运营商(ASN)划分的 RTT 分布,以及 traceroute 显示的途经城市。主责是运维团队(网络),修复手段是与运营商直接对等互联、接入 IX、选择服务器位置。运营商侧的路由策略需要与外部(运营商)协商。这个案例也说明,仅仅把服务器移到靠近用户分布中心的位置,效果就很明显。
相关原因
路由绕行, 传播延迟(物理距离), 瓶颈队列溢出(拥塞丢包)
原文
Riot Games

Riot Games 2020: League of Legends 欧洲、巴西服务器的边缘主机过载

经过
2020 年 2 月下旬,League of Legends 的 EUW、EUNE、BR 服务器多次发生故障,新开局数大幅减少。匹配、游戏服务器等后端服务的状态全部正常,但几乎没有流量进来。为避免在可能不稳定的集群上开放锦标赛模式(Clash),Riot 把日程推迟了一周。复盘文章没有写明每次故障持续了多久。
原因
三个因素叠加在一起。发往某个服务的请求构造有误,在特定情况下持续失败、不断重试,导致请求量暴增。容器系统与 OS 版本之间存在已知的兼容问题,OS 内部内存一直在泄漏;升级只在 Riot 全部容器环境的约 60% 上完成,欧洲和拉丁美洲集群还在升级中。负责接收互联网流量、过滤后转发给后端的边缘容器,在同一个分片(服务器组)内会分散部署,但不同分片之间没有这种限制,每次故障时都至少有三个分片的边缘容器挤在同一台主机上。暴增的重试压到这台主机上,内存泄漏又让它停摆。
经验教训
后端服务都回答“状态正常,但没有流量进来”时,就去看它们前面的一层(边缘、网关、负载均衡器)。确认信号是各主机入站连接数是否倾斜,以及特定请求的失败率、重试率。主责是研发团队(服务器:错误的请求和重试方式),容器调度规则、OS 升级、倾斜告警由运维团队(服务器/OS)负责。Riot 修复了请求代码,改成不会让重试激增的方式,并在实现跨分片分散部署之前设置了倾斜告警。
相关原因
经由网关/代理, 级联故障
原文
Riot Games

Riot Games 2021: League of Legends EUW 5 小时故障:一个辅助 DB 导致全服停摆

经过
2021 年 1 月 22 日,League of Legends EUW 服务器有 5 个多小时无法正常运行。已登录玩家数和游戏中玩家数两项指标同时中断;两次重启之间,登录人数在增加,却几乎没有对局开始。
原因
负责非关键功能的一个 DB,其主服务器发生硬件故障,而这个 DB 没有配置自动切换到备用服务器。每个 DB 的连接池是分开的,但所有连接池共用同一个线程池;发往故障 DB 的任务迟迟不结束、一直占着线程,整个系统可用的线程被耗光。告警铺天盖地,团队先怀疑最近遭遇过的恶意网络攻击和其他地区的硬件作业,故障 DB 的告警约 1 小时后才被注意到。所有系统都跑在同一个 JVM 里,重启后在重连负载下,GC 每次让进程停顿几秒,指标采集也因此出现大段空白。登录排队也没有遵守设定的上限,涌入量忽高忽低。
经验教训
即使是被认为不重要的一个辅助 DB,也可能通过线程池这类共享资源让整个系统停摆。确认信号是各 DB 排队中的请求数、线程池使用率,以及开局数与登录数之比明显偏低。负责方是研发团队(服务器:线程池隔离、超时)和运维团队(DB:自动切换)。告警铺天盖地时,人很容易先怀疑最近遇到过的问题(攻击等),所以要按判定顺序(范围 → 时间点 → 层级)逐一排除。重启后还要看登录排队是否按设定限制了涌入量。
相关原因
线程池耗尽, DB 故障切换, 级联故障, 服务器 GC 全局停顿
原文
Riot Games

Roblox 2021: Roblox 73 小时故障:服务发现(Consul)集群的争用问题

经过
2021 年 10 月 28 日下午(太平洋时间),故障从一台 Consul 服务器的 CPU 高负载开始;16:35 在线玩家数降到平时的一半,随后整个服务停摆。直到 10 月 31 日 16:45,所有玩家才能重新进入,距故障开始共 73 小时。Roblox 表示每天有 5000 万人使用其服务。
原因
Roblox 用 HashiCorp Consul 做服务发现(服务之间互相查找地址的功能)、健康检查和 KV 存储,一个 Consul 集群同时承担多种工作负载。根本原因有两个。第一,Consul 新的 streaming 功能用了几个月逐步扩大启用范围,故障前一天又在流量路由服务上启用,并把该服务的节点数增加了 50%;在读写都非常多的负载下,这个功能在一个共享资源(Go channel)上产生了争用。故障期间换上的核心数更多的双路(NUMA)服务器上,争用更加严重。第二,Consul 用来存储 Raft 日志的 BoltDB,其空闲页列表(freelist)管理变得异常缓慢,每追加 16 kB 以下的数据,就要向磁盘写入 7.8 MB。平时低于 300 ms 的 KV 写入延迟中位数升到 2 秒;在变慢的 leader 服务器上,还观察到 TCP 缓冲区写满的零窗口。遥测依赖 Consul,排查原因所需的指标也一起消失了。
经验教训
多个服务共同依赖的基础系统(服务发现、配置存储、认证)一旦变慢,所有功能会同时停摆。确认信号是该系统的写入延迟、leader 切换、CPU,以及故障前一刻的配置变更。负责方是研发团队(服务器)和运维团队(服务器/OS)双方。监控要与被监控的系统分开、不依赖它,故障时才看得到指标。恢复时缓存是空的,一下子放进所有人可能再次崩溃,所以 Roblox 通过 DNS 控制允许进入的玩家比例,每次增加约 10%。
相关原因
级联故障, 锁竞争, 零窗口(看起来像重传的停顿), 冷缓存(刚重启时)
原文
Roblox

Square Enix 2021: FINAL FANTASY XIV 资料片上线时的拥挤与登录排队错误

经过
2021 年 12 月,从资料片《晓月之终途》(Endwalker)的抢先体验开始,各个 World(游戏世界服务器)都极度拥挤。登录排队越来越长,在角色选择界面登录时或在队列中等待时,经常出现 Error 2002。部分 World 和场景宕机(Error 3001)、排队超时(Error 4004)的情况也有发生。到 12 月 11 日发布公告时,抢先体验已进入第 8 天,拥挤仍在持续。
原因
Error 2002 出现在两种情况下。一种是每个逻辑数据中心的排队人数超过 17,000 人时。这是为防止队列过长导致登录服务器宕机而设的上限,此时客户端会完全退出。12 月 7 日把开发用的备用设备投入大厅服务器、调高上限后,这个错误减少了,队列反而更长了。另一种是排队玩家的线路不稳定时。等待时间一长,因互联网路径丢包或 Wi-Fi 不稳定导致连接短暂中断的情况就多了起来。大厅服务器会等待重连几十秒到 1 分钟左右,在此期间重新连上,就从队列中原来的位置接着排;超过时限则要排到队尾。Square Enix 表示,大部分反馈属于这种情况。由于芯片短缺,也无法马上增加 World。
经验教训
队列越长,排队玩家线路上的短暂中断就越容易变成连接错误。同样的拥挤下,错误集中在使用 Wi-Fi 或线路不稳定的人身上,成为“只有部分人遇到的问题”。确认信号是队列长度、等待时间,以及断线原因中排队期间断开所占的比例。主责是研发团队(服务器:排队上限与重连保留时间),大厅服务器、World 服务器的扩容由运维团队配合。把重连保留时间留得宽裕一些,可以减少玩家线路的短暂中断演变成丢失排队位置的情况。
相关原因
登录排队上限/重连保留不足, Wi-Fi 干扰/信号弱, 无线链路丢包
原文
Square Enix

Cloudflare 2020: Cloudflare 骨干网配置错误导致部分城市流量丢失

经过
很多游戏把网站、API 和 DDoS 防护交给 CDN 服务商,这类基础设施故障会连带影响游戏。2020 年 7 月 17 日 21:12 至 21:39(UTC)的 27 分钟里,Cloudflare 全网流量减少约 50%。影响仅限于接入骨干网的美国、欧洲、俄罗斯、巴西部分城市节点,其他节点正常。
原因
纽瓦克到芝加哥的骨干链路出现故障,亚特兰大到华盛顿的链路随之拥塞,工程师为分流亚特兰大的骨干流量修改了路由器配置。本该禁用整个策略条目(term),却只禁用了其中的条件(prefix-list),结果亚特兰大的路由器以更高的优先级(local-preference 200)把所有 BGP 路由通告到整个骨干网。各节点给通往自己服务器的路由设的优先级是 100,于是接入骨干网的节点流量全部涌向亚特兰大。亚特兰大过载,受影响的节点则几乎没有流量可处理。把亚特兰大的路由器从骨干网中撤下后恢复正常。Cloudflare 表示此事与攻击或入侵无关。
经验教训
只有特定城市、地区的玩家同时出现掉线或连不上/无限加载,其他人一切正常时,先怀疑刚做过的路由配置变更。监控图上只有一个节点的 CPU 和流量飙升,受影响的节点反而跌到接近 0。主责是运维团队(网络);如果是服务商侧的故障,则是外部。Cloudflare 决定给骨干网 BGP 会话设置可接收的路由数上限(maximum-prefix),并调整了优先级,让一个节点无法把其他节点的流量吸走。
相关原因
BGP 路由变更/收敛, 路径变更/ECMP 故障路径
原文
Cloudflare

Fastly 2021: Fastly CDN 全球大面积报错

经过
很多游戏通过 CDN 分发更新包、启动器和网页,这类基础设施故障会连带影响游戏。2021 年 6 月 8 日 09:47(UTC)起,Fastly 网络的 85% 返回错误。49 分钟内 95% 的网络恢复正常,12:35 故障处理完毕。
原因
5 月 12 日开始的一次软件发布中带有一个 bug,特定的客户配置遇到特定条件时就会触发。6 月 8 日,一位客户提交了一次正常的配置变更,恰好满足了这个条件。Fastly 在 1 分钟内发现异常,找到并禁用引发问题的客户配置后,开始恢复。修复 bug 的发布于同日 17:25 开始。
经验教训
已经发布几周的代码,遇到罕见条件也会瞬间演变成全球故障。游戏侧的确认信号是更新包、启动器、网页请求的 HTTP 错误率在所有地区同时上升,以及 CDN 服务商的状态页。特征是已建立的游戏连接只要不经过 CDN 就不受影响,只有新连接、更新包下载、网页登录受阻。主责是外部(CDN 服务商);研发团队和运维团队要提前准备绕行方案,例如同时使用两家以上 CDN,或直接从源站获取。
相关原因
依赖外部服务
原文
Fastly

Meta 2021: 一条骨干网命令让 Facebook 连 DNS 都消失的故障

经过
这类基础设施故障同样可能发生在游戏公司的自有网络和 DNS 上。2021 年 10 月 4 日,Facebook(现 Meta)的服务在全球范围无法访问。连接各数据中心的骨干网全部中断,互联网上也找不到 Facebook 的 DNS 服务器。复盘文章没有写明故障持续了多久。
原因
例行维护中,为评估全球骨干网容量而下发的一条命令,意外断开了骨干网的所有连接;本该拦下这类命令的审计工具因为 bug 没能拦住。小型节点的 DNS 服务器按设计在无法与数据中心通信时,会判定自身异常并撤回 BGP 通告,于是 DNS 服务器虽然还在运行,互联网上却访问不到。平时的访问通道和带外(out-of-band)访问全部中断,内部工具也失去了 DNS,只能派工程师亲赴数据中心,安全流程又耽误了更多时间。恢复时,各数据中心的用电量都下降了几十 MW,团队判断一下子全部恢复可能危及从电力设施到缓存的各个环节,于是逐步提升负载。
经验教训
所有地区、所有运营商同时出现连不上/无限加载时,先看 DNS 和 BGP 路由,再看游戏服务器。通过外部 DNS 查询和公开的 BGP 路由信息,在公司外部也能确认。主责是运维团队(网络)。要事先检查故障时使用的带外访问通道和内部工具是否依赖同一套 DNS 和网络;恢复时逐步提升负载,避免重连一下子涌入。
相关原因
BGP 路由变更/收敛, DNS 故障/延迟
原文
Meta

AWS 2021: AWS us-east-1 内部网络拥塞

经过
很多游戏把服务器、登录和数据放在公有云上,这类基础设施故障会连带影响游戏。2021 年 12 月 7 日上午 7:30(太平洋标准时间),北弗吉尼亚区域(us-east-1)的内部网络发生拥塞。从 7:33 起,EC2 API 错误和延迟增加,难以启动新实例(实例启动在下午 2:40 恢复),随后又出现控制台登录失败、无法修改 Route 53 配置、CloudWatch 指标延迟及部分丢失。网络设备在下午 2:22 完全恢复。已在运行的 EC2 实例和现有 DNS 应答没有受到影响。
原因
一项为主网络上某个服务扩容的自动化任务,在内部网络的大量客户端上引发了意料之外的行为,连接尝试暴增。连接内部网络与主网络的设备不堪重负,通信出现延迟,延迟又进一步增加了连接尝试和重试,拥塞持续不退。客户端本来有在这种拥塞时拉长请求间隔的退避机制,但因为一个潜在缺陷没能正常工作。内部监控也依赖同一网络,AWS 的运维人员只能在没有实时指标的情况下靠日志应对。
经验教训
重试如果不能拉长间隔,短暂的拥塞就会变成几个小时的故障。从游戏侧看,已在运行的游戏服务器即使正常,新服务器扩容(弹性伸缩)、依赖云 API 的登录、匹配、支付,以及监控都可能一起受阻。确认信号是云厂商的状态页、云 API 错误率和实例启动失败。主责是外部(云厂商);研发团队要给所有重试加上带随机间隔的指数退避和次数上限,运维团队要准备好扩容受阻时也能撑住的富余容量,以及其他区域的备选方案。
相关原因
级联故障, 弹性伸缩延迟, 依赖外部服务
原文
AWS

Cloudflare 2025: Cloudflare 公共 DNS 1.1.1.1 故障

经过
这是一起公共 DNS 解析器故障。公共 DNS 是玩家在设备或路由器上手动设置的,所以只有使用这一设置的玩家会遇到所有游戏和服务同时受阻。2025 年 7 月 14 日 21:52 至 22:54(UTC)的 62 分钟里,1.1.1.1 解析器在全球范围内没有响应。Cloudflare 表示,对许多用户来说,这意味着几乎所有互联网服务都无法使用。UDP、TCP、DNS over TLS 查询受到影响,通过域名访问的 DNS over HTTPS 相对稳定。
原因
6 月 6 日,在为另一项尚未上线的服务准备服务拓扑(决定在哪些节点通告哪些 IP 段的配置)时,1.1.1.1 解析器的 IP 段被误绑进了这份配置。7 月 14 日修改这项服务的配置后,通告解析器 IP 段的节点从全部节点缩减为一个离线节点,BGP 路由在全球被撤回。这次变更没有经过金丝雀发布,直接推送到了所有数据中心。22:20 回退配置后,流量恢复到约 77%,但在此期间约 23% 的边缘服务器上必要的 IP 配置已被删除,需要重新配置,直到 22:54 才恢复正常。Cloudflare 表示这是一次内部配置错误,与攻击或 BGP 劫持无关。
经验教训
游戏服务器和其他玩家都正常,只有部分玩家连接登录服务器、更新服务器时出现连不上/无限加载,就要怀疑这些玩家使用的 DNS。特征是已建立的会话保持不断,只有新连接失败。让玩家换一个 DNS 设置,或直接查询服务器地址试试,马上就能分辨。主责是外部(DNS 运营方、运营商);如果研发团队(客户端)把域名解析失败和其他错误区分开来提示,客服就能当场判定。
相关原因
DNS 故障/延迟, BGP 路由变更/收敛
原文
Cloudflare

AWS 2025: AWS us-east-1 DynamoDB DNS 故障与漫长的恢复

经过
很多游戏把服务器、登录和数据放在公有云上,这类基础设施故障会连带影响游戏。从 2025 年 10 月 19 日晚上 11:48 到 20 日下午 2:20(太平洋夏令时间),北弗吉尼亚区域分三个阶段受到影响。20 日凌晨 2:40 之前 DynamoDB API 错误增加;凌晨 2:25 到上午 10:36 新 EC2 实例启动失败(部分新实例的连接问题在下午 1:50 解决);凌晨 5:30 到下午 2:09 部分 Network Load Balancer(NLB)的连接错误增加。
原因
管理 DynamoDB DNS 的自动化系统存在一个潜在的竞态条件(race condition)。在不同可用区应用 DNS 计划的执行器(DNS Enactor)中,有一个异常滞后,它用旧计划覆盖了新计划;紧接着另一个执行器的清理任务删除了这份旧计划,区域端点(dynamodb.us-east-1.amazonaws.com)的 DNS 记录变成了空值。自动化无法修复这一状态,只能人工恢复。EC2 的物理服务器管理系统依赖 DynamoDB,在此期间每台物理服务器维持的租约(lease)都过期了。DynamoDB 恢复后,由于物理服务器数量太多,重新建立租约的任务还没完成就超时,重试任务又再次堆积,陷入“拥塞崩溃(congestive collapse)”状态。新启动实例的网络配置传播缓慢,NLB 健康检查在成功与失败之间来回切换,连正常节点也反复从 DNS 中被摘除又加回。
经验教训
一处 DNS 记录错误会蔓延到依赖该服务的其他服务;即使原因已经解决,积压的任务和时好时坏的健康检查也会让恢复再拖上几个小时。从游戏侧看,已在运行的服务器还能撑住,但新服务器起不来,弹性伸缩就停了;健康检查时好时坏,负载均衡器还会把正常的服务器摘掉。确认信号是云厂商状态页、托管服务 API 错误率、实例启动失败,以及负载均衡器的健康目标数。主责是外部(云厂商);运维团队要限制因健康检查失败而同时被摘除的服务器数量,并准备其他区域的备选方案。
相关原因
依赖外部服务, 级联故障, 弹性伸缩延迟, 负载均衡倾斜/健康检查误判, DNS 故障/延迟
原文
AWS

术语表

ping 值
Ping, RTT. 自己发出的信号到达服务器再返回所用的时间(往返)。游戏里显示的 ping 有时还掺杂了服务器处理的排队时间。
延迟
Latency. 数据包从发出到到达所用的时间。很多时候只说单程,所以大约是 ping 的一半。
抖动
Jitter. 到达间隔的波动。即使平均 ping 一样,抖动大时画面也会一卡一卡。
数据包
Packet. 通过网络一次发送的一组数据。通常最大 1,500 字节,游戏的状态更新一般只有几十到几百字节。
丢包
Packet loss. 发出的数据包没能到达、中途消失。用 TCP 的游戏丢包率只要到 1%,每隔几秒到十几秒就能感觉到顿一下;具备插值和输入重复发送的 UDP 游戏,丢包到百分之几也可能掩盖得住。
带宽
Bandwidth. 线路每秒能传输的最大数据量(Mbps)。和数据多快到达(延迟)是两个不同的概念。
tick
Tick. 服务器计算一次游戏状态的单位。20 tick 的服务器每秒计算 20 次,即每 50 ms 一次。
tick 率
Tick rate. 每秒跑多少次 tick。越高反应越快,但服务器成本和传输量也越大。为了节省传输量,发包频率有时会设得比 tick 率低。
tick 预算
Tick budget. 一个 tick 必须跑完的时间上限。超出后下一个 tick 会推迟,tick 间隔随之拉长。
FPS
Frames per second. 每秒绘制多少次画面。60 FPS 时每帧 16.7 ms。
帧耗时
Frame time. 绘制一帧所用的时间。比起平均 FPS,偶尔突然变长的帧对体感的影响更大。
快照
Snapshot. 服务器每个 tick 发出的“当前游戏状态”摘要,包含位置、血量、状态等。通常只挑出与接收方已有状态不同的部分发送(增量压缩)。
插值
Interpolation. 在收到的两个快照之间连线绘制、让画面看起来平滑的技术。代价是显示的内容会稍微滞后。
插值缓冲
Interpolation buffer. 为了做插值而故意推迟绘制的时间,是吸收抖动和一两次丢包的缓冲余量。通常为包间隔的 2 倍(每秒收 20 次时为 100 ms),也有游戏会在抖动变大时自动加长。
外推
Extrapolation, Dead reckoning. 收不到新数据包时,按最后的速度推测对象接下来会在哪里并绘制出来的技术。猜错了看起来就像瞬移,所以很多游戏只外推 0.25 秒左右就停下(Source 引擎默认 0.25 秒)。
客户端预测
Client-side prediction. 不等服务器确认,先让自己的角色动起来的技术。
服务器校正
Reconciliation. 服务器结果到达后与预测比对,修正自己角色位置的过程。以服务器确认的位置为起点,把尚未被确认的输入重新应用一遍来计算。偏差大时看起来就是拉回。
延迟补偿
Lag compensation. 服务器做命中判定时,回溯到攻击者当时看到的过去时间点,确认是否命中的技术。为了不让被击中的一方太冤,回溯幅度设有上限。竞技射击游戏常见 0.2~0.25 秒左右,也有像 Source 引擎默认值那样回溯到 1 秒的。
权威服务器
Authoritative server. 只有服务器做最终判定的设计。能防作弊,但所有结果都要经过一次服务器往返,所以要用预测和预表现来掩盖等待。
帧同步
Deterministic lockstep. 所有人只交换输入,在同一个逻辑帧里做完全相同计算的方式。输入会加上固定的延迟,只要有一个人的输入迟到,所有人都要等。
服务器输入缓冲
Server-side input buffer. 服务器为每个人先攒一点输入,每个 tick 取出一个来用的缓冲区。抖动大的人在别人眼里也显得平滑,但这个人的操作在服务器上生效的时间也相应推迟。
Listen Server
Listen server. 由某位玩家的电脑一边玩游戏一边兼任服务器的方式。房主的 ping 为 0,但房主的线路或电脑一慢,所有人都会卡。
位面
Phasing. 同一个地点,按任务进度显示不同 NPC 和地形的功能。两个角色进度不同时,只有一方看不到 NPC 是正常的。
回滚网络代码
Rollback netcode (GGPO). 预测对手的输入先往下推进,实际输入不同时就回退到过去的帧重新计算的方式。格斗游戏用得很多。和数据库的回滚是两回事。
预输入
Input buffer, spell queue. 在冷却或动作结束前稍早按下的下一个输入先记下来,结束的瞬间立即执行。这样连招之间不会插入一次往返时间。
预表现
Client-side feedback. 不等服务器确认,先播放动画、音效、特效。只有伤害、奖励这类需要确定的结果才等服务器回复。服务器拒绝时,要把已经表现出来的内容撤回。
TCP
Transmission Control Protocol. 按顺序、一个不漏地交付数据的协议。丢失的数据包重新收到之前,不会把后面的数据包交给游戏。
UDP
User Datagram Protocol. 不做任何保证、发什么就送什么的协议。没有等待,代价是丢包和乱序要由游戏自己处理。
可靠 UDP
Reliable UDP (KCP, ENet…). 在 UDP 之上按需自行实现重传和按序交付的方式。
队头阻塞
Head-of-line blocking. 排在前面的一个被堵住,后面的全部都要等的现象。TCP 游戏出现快进,原因就在这里。
RTO
Retransmission timeout. 重传定时器,即 TCP 判定数据包丢失、到重新发送之前等待的时间。Linux 为 ping + 200 ms 以上,每失败一次翻倍。
Nagle 算法
Nagle’s algorithm. 在之前发出的数据收到确认(ACK)之前,先把小块数据攒起来一次发出,以减少数据包数量的 TCP 功能。游戏里通常要关掉。
TCP_NODELAY
TCP_NODELAY. 关闭 Nagle 算法的 socket 选项,小消息会立即发出。
延迟 ACK
Delayed ACK. 把“已收到”的确认稍微推迟、和其他数据一起发送的功能。Linux 通常为 40 ms(最长 200 ms);Windows 旧版本为 200 ms,新版本为 40 ms。
socket 缓冲区
SO_SNDBUF / SO_RCVBUF. 操作系统为每个 socket 准备的发送、接收等待空间的大小。太小会溢出,太大则会积压过时的数据,让后面的数据跟着等。
keepalive
SO_KEEPALIVE. 检查空闲连接是否还活着的 TCP 功能。默认关闭,即使打开,默认也要 2 小时后才检查。
RST
TCP reset. 当场强制断开连接的 TCP 信号,还没发出的数据会被丢弃。
心跳
Heartbeat. 由游戏自己定期发送的“还活着”信号。用于检测断开的连接,并让中间设备保持连接。
超时
Timeout. 在这段时间内没有响应就判定为失败的标准。太短会误判,太长则发现得晚。
NAT
Network Address Translation. 路由器让家中多台设备共用一个公网 IP 上网,并把各个连接记录在 NAT 表里的功能。
CGNAT
Carrier-grade NAT. 运营商让多个用户共用一个 IP 的大规模 NAT。
MTU
Maximum Transmission Unit. 一次能发送的最大数据包大小。通常为 1,500 字节,经过 VPN、PPPoE 的链路会更小。
缓冲区膨胀
Bufferbloat. 设备把队列攒得过大,导致延迟涨到几百 ms 的现象。
SQM
Smart Queue Management (fq_codel, CAKE). 让队列保持很短、并按流公平发送的路由器功能,是解决缓冲区膨胀的办法。
QoS
Quality of Service. 为重要流量设置优先级、让它先发送的功能。
对等互联
Peering. 运营商之间互相连接网络的地方,晚上容易拥堵。
BGP
Border Gateway Protocol. 互联网上各运营商相互通告走哪条路径的协议。它一变,路径和 ping 就会跟着变。
DDoS
Distributed Denial of Service. 从大量来源发送海量流量、让服务瘫痪的攻击。
清洗中心
DDoS scrubbing center. DDoS 攻击时先接下发往服务器的流量、过滤掉攻击、只把正常流量转发过去的防护厂商节点。节点离得远,路径就会变长。
防火墙
Firewall. 只放行被允许的连接的设备或程序,用会话表跟踪连接。
负载均衡器
Load balancer. 把进来的连接分配到多台服务器的设备。
会话表
Session table, conntrack. 设备或操作系统跟踪当前各个连接的表,大小有上限。
微突发
Microburst. 平均流量不高,但在 1 ms 以下的极短瞬间流量集中涌来的现象。
NIC
Network Interface Card. 服务器的网卡。
环形缓冲区
Ring buffer. 网卡收到的数据包在被 CPU 取走之前存放的缓冲区。循环使用固定数量的槽位,槽位全满时新的数据包会被丢弃。
中断
Interrupt. 设备通知 CPU“有事要处理”的信号。
RSS
Receive Side Scaling. 把收到的数据包分到多个接收队列、让多个 CPU 核心处理的网卡功能。
PPS
Packets per second. 每秒数据包数。游戏服务器往往在带宽用满之前,先碰到这个数字的上限。
内核
Kernel. 操作系统的核心,负责网络、内存和 CPU 分配。
backlog
Listen backlog. 新连接请求在服务器还没取走之前排队等候的队列。队列满了之后,Linux 会悄无声息地丢弃新请求,Windows 则会回复拒绝。
TIME_WAIT
TIME_WAIT. 先关闭连接的一方为防备迟到的数据包,把这组端口保留一小段时间(Linux 为 60 秒)的状态。
CPU 窃取时间
Steal time. 虚拟机想用 CPU,却因为物理服务器把 CPU 分给了其他虚拟机而等待的时间。可以看 top 里的 st 值。
CPU 限流
CFS throttling. 容器在规定周期(CFS period,通常 100 ms)内用完 CPU 配额(quota)后,被强制暂停到下一个周期。
文件描述符
File descriptor. 进程打开的每个文件、每个连接都会分到的编号(fd),数量有上限。
线程
Thread. 程序内部可以独立执行的工作单元,多个线程可以同时运行。
上下文切换
Context switch. CPU 把正在执行的线程换成另一个线程,有一定开销。
锁
Lock, Mutex. 让共享数据同一时间只能被一个线程使用的机制。
死锁
Deadlock. 多个线程互相等待对方持有的锁、永远停住的状态。
线程池
Thread pool. 预先创建好的一组工作线程。全部忙碌时,新任务只能等待。
异步 I/O
epoll, IOCP, io_uring. 不等待输入输出完成、先去做别的事,完成后再接收通知的方式。
AOI
Area of Interest. 每个玩家“能看到的范围”。只发送这个范围内的变化,以减少传输量。为了降低判断谁在范围内的开销,通常把地图划成格子(网格),只检查附近的格子。
广播
Broadcast, fan-out. 把一个变化发给所有能看到它的人。聚在一起的人如果都互相可见,要发送的量会按人数的平方增长。
GC
Garbage collection. 自动回收用完丢弃的内存的机制。GC 期间程序可能会停顿。
堆
Heap. 程序运行时按需动态分配使用的内存区域。
内存泄漏
Memory leak. 用完的内存不归还、使用量持续增长的 bug。即使有 GC,只要某处一直引用着已经用完的对象,照样会发生。
swap
Swap, paging. 内存不够时把一部分内存挪到磁盘上。挪出去的内存再用时,比直接读内存慢 1,000 倍以上。
OOM Killer
Out-of-memory killer. 内存耗尽时,Linux 挑出内存用得最多的进程强制结束的机制。容器只要碰到内存上限就会触发。
缓存未命中
Cache miss. CPU 附近的缓存里没有所需数据,只能去更慢的内存里取。
IOPS
I/O operations per second. 磁盘每秒能处理的读写次数。云盘的上限按付费档位确定。
fsync
fsync. 等待数据确实写入磁盘的命令。普通写入会先放进操作系统内存,稍后再刷到磁盘,在这之间如果服务器断电,数据可能丢失。fsync 安全,但慢。
突发积分
Burst credits. 云盘或云服务器为了能短时间超出基准性能而积攒的额度。用光后性能回落到基准水平。
索引
Index. 数据库为加快查找而建的目录。没有索引就得读取整张表。
全表扫描
Full table scan. 不走索引、逐行检查整张表的查询。
执行计划
Query plan. 数据库决定按什么顺序、用哪个索引来执行查询的方案。即使代码没变,数据库一换计划,同一条查询也可能突然变慢。
事务
Transaction. “要么全部成功,要么全部不做”的一组数据库操作。交易必须放在事务里处理。事务结束前会一直锁住改过的行,所以越短越好。
连接池
Connection pool. 预先建立好的一组数据库连接。全部被占用时,新请求只能等待。
热点行
Hot row. 被大量请求同时修改的某一行,是锁竞争的根源。
复制延迟
Replication lag. 从库跟不上主库、落后的时间(主从延迟)。
回滚
Rollback. 保存被撤销、回到之前的状态。玩家感受到的是“物品没了”。
缓存
Cache (Redis etc.). 把常用数据复制到更快的地方存着,用来减轻数据库负载。
检查点
Checkpoint. 数据库把在内存中攒下的变更定期集中写入磁盘。那一刻保存和查询可能会短暂变慢。
故障切换
Failover. 主服务器或主库宕机时切换到备用节点。切换期间短暂无法保存,如果复制有延迟,最后一部分数据可能丢失。
MVCC
Multi-version concurrency control. 为了让读的人和改的人互不阻塞,数据库暂时保留旧版本数据的方式。如果有长时间未结束的事务,旧版本会越积越多,导致变慢。
缓存雪崩
Cache stampede. 缓存同时失效,请求一下子全部涌向源头(DB)的现象。
网关
Gateway. 接收客户端连接、再转发给后端游戏服务器的中间服务器。
熔断器
Circuit breaker. 对持续失败的服务调用暂时断开、直接按失败处理,以阻止级联故障的机制。过一段时间先试探性地调用一两次,恢复了就重新放行。
级联故障
Cascading failure. 一处故障沿着调用链蔓延到其他服务。
弹性伸缩
Autoscaling. 根据负载自动增减服务器数量的功能。扩容需要时间。
看门狗
Watchdog. 监视服务器是否停住的定时器。游戏循环停住超过规定时间(数秒到数十秒),就留下状态记录(dump)并强制结束服务器,让它重新启动。
利用率
Utilization. worker(CPU 核心、线程、DB 连接这类处理请求的主体)处于忙碌状态的时间占比。超过 80~90% 后,排队会急剧增加。
p99
99th percentile. 100 次里有 99 次比它快、约 1 次比它慢的值。比平均值更能反映体感上的卡顿。
V-Sync
Vertical sync. 按屏幕刷新周期输出帧的功能。能消除画面撕裂,但会带来操作延迟;FPS 低于刷新率时,还可能在 60 和 30 之间来回切换,出现一卡一卡。
可变刷新率
VRR, G-Sync, FreeSync. 显示器配合帧准备好的时机刷新画面的功能。可以减轻垂直同步在 60 和 30 之间来回切换造成的一卡一卡和操作延迟。
反作弊
Anti-cheat. 防止游戏外挂的安全模块。定期检查或与服务器的心跳失败时,可能导致一卡一卡或掉线。
游戏内覆盖层
Overlay. 聊天、录屏、FPS 显示等程序在游戏画面上叠加绘制内容的功能。它会插入游戏的绘制流程,可能造成一卡一卡。
Shader 编译
Shader compilation. 把图形效果程序转换成 GPU 可执行形式的工作。没有提前做好的话,第一次看到某个效果时画面会顿一下;更新显卡驱动后,已保存的编译结果会失效,需要重新编译。
主线程
Main thread, Game thread. 依次处理游戏规则计算和画面准备的核心线程。这里只要有一件事耗时过长,那段时间画面就会停住。
定时器精度
Timer resolution. 操作系统能唤醒休眠程序的最短间隔。Windows 默认 15.6 ms,所以程序不专门修改的话,即使要求“1 ms 后唤醒”也会醒得晚。
发热降频
Thermal throttling. 设备过热时自动降低 CPU、GPU 频率的保护功能。手机玩上几分钟到几十分钟就很常见。
VRAM
Video memory. 显卡上的专用内存(显存)。纹理和模型放在这里再绘制。不够用时要通过较慢的通道和电脑内存来回交换数据,于是一卡一卡。
网络状态面板
Net graph. 在游戏画面上用实时曲线显示 ping、丢包、FPS、tick 的开发调试面板。卡顿反馈视频里如果一起录到它,查原因会容易得多。
重传率
Retransmission rate. 发出的 TCP 数据包中被重发的比例。没有公认标准,但全服平均低于 0.1% 算健康,超过 1% 时很多玩家容易感到卡顿。也要看它比平时高了几倍。
SACK
Selective ACK. 接收方详细告知“这一段收到了、只缺这部分”的 TCP 功能。即使丢了多个包,也能一次恢复。
RACK-TLP
Recent ACK, Tail Loss Probe. 以时间为依据判断丢包,一段时间没收到 ACK 就把末尾的数据包再发一次、提前恢复的 TCP 功能。在较新的 Linux 和 Android 上默认开启。Windows 从 10(1607)和 Server 2016 起默认启用 TLP 和 RACK,能连丢失的重传也恢复的新版 RACK 从 Server 2022 起提供。只在开启了 SACK 的连接上生效。
虚假重传
Spurious retransmission. 数据包其实没丢,只是晚到或乱序,却被误判为丢失而重发。既浪费线路,又让发送速率白白降低。
零窗口
Zero window. 接收方缓冲区已满、通知对方“先别发”的状态。看起来像重传,其实线路没问题,是接收方程序没能及时读取。
thin stream
Thin stream. 像游戏这样稀疏地发送小数据包的连接。快速重传的信号不容易凑齐,丢包时会停很久。
流量监管
Policer. 对超过设定速率的数据包不排队、直接丢弃的限速方式。先放进队列再慢慢发出的方式叫流量整形(shaper)。
平滑发送
Pacing. 把要发的数据包在时间上均匀分开发送,避免一下子全倒出去,防止小缓冲区溢出。
ECN
Explicit Congestion Notification. 拥塞时给数据包打上“拥塞”标记、让发送方降速的功能,可以在不丢包的情况下通知拥塞。两端和拥堵路段的设备都支持才有效果。
MSS
Maximum Segment Size. TCP 在一个数据包里装载数据的最大长度。通常为 1,460 字节,按隧道链路调小可以避免 MTU 黑洞。
基站切换
Handover. 移动中的手机更换所连接的基站。
百分位数
Percentile (p50, p95, p99). 把数值从小到大排列后,排在第百分之几位置的值。p50 是中位数,p99 是 100 次中最慢那 1 次附近的值。能看出被平均值掩盖的跳变。
长尾延迟
Tail latency. 大多数都很快,偶尔出现的长延迟。在平均值里几乎看不出来,却是玩家记住的“卡”。
拨测
Synthetic monitoring. 用测量专用的设备或服务器代替真实玩家,在固定地点定期发送 ping、traceroute 等,测量路径质量。RIPE Atlas 是代表性的公开工具。
聚合粒度
Aggregation interval. 图表上的一个点是几秒或几分钟的值汇总而成的。粒度越粗,短暂的跳变越容易被平均掉、变得不明显。
故障复盘
Postmortem. 故障结束后,整理发生了什么、为什么发生、要改什么的文档。目的是防止问题重演,避免追究个人责任。
C-state
CPU idle state. CPU 空闲时进入的节能状态。状态越深越省电,但重新唤醒所需的时间越长。
热迁移
Live migration. 云厂商为了宿主机维护等原因,把运行中的虚拟机迁移到其他宿主机。迁移的瞬间可能会短暂停顿。
SNAT
Source NAT. 把出站数据包的源地址转换成公网地址的 NAT。一个公网地址可用的端口数有上限,用完后新连接会失败。
NAT 网关
NAT gateway. 让私有网络中的服务器共用一个公网地址访问互联网的云上组件。每个目的地的并发连接数有上限。
低轨卫星互联网
LEO satellite internet. 通过距地面几百到几千 km 的卫星群接入的互联网。延迟比地球同步轨道卫星短得多,但切换所连卫星时延迟可能跳变。
GeoIP
IP geolocation. 根据 IP 地址推测国家、城市、运营商的数据库。有些条目错误或过时,可能导致玩家被分配到远处的服务器。
TLS 证书
TLS certificate. 证明服务器确实是该服务器的电子文件。有有效期,过期后加密连接会失败,无法登录。
帧生成
Frame generation. 显卡在实际绘制的帧之间插入预测出的帧、提高 FPS 的技术。画面更流畅,但从输入到画面的延迟可能增加。

参考文献

共 616 条资料,来自 83 家发布方,包括标准文档,内核、操作系统、云、引擎、数据库的官方文档,论文,以及原开发商的技术文章。

Microsoft 85

Linux kernel 62

IETF 59

AWS 51

MySQL 32

Unity 26

PostgreSQL 25

ACM 20

Epic Games 19

Android (Google) 17

Linux man-pages 15

Microsoft Azure 12

Oracle 9

Redis 9

Cloudflare 8

Gaffer On Games 7

Google Cloud 7

iproute2 7

Apple 6

Bufferbloat.net 6

Google 6

Microsoft SQL Server 6

Wireshark 6

.NET 5

OpenJDK 5

systemd 5

ITU 4

sysstat 4

Valve 4

AMD 3

CCP Games 3

chrony 3

Go 3

IEEE 3

Intel 3

IO Visor 3

Kubernetes 3

NVIDIA 3

perf 3

Riot Games 3

RIPE NCC 3

APNIC 2

Istio 2

Let's Encrypt 2

MaxMind 2

numactl 2

OpenSSL 2

SK텔레콤 2

Solidigm 2

Square Enix 2

USENIX 2

util-linux 2

Amazon Builders' Library 1

Apache Software Foundation 1

coreutils 1

Envoy 1

ethtool 1

Frontiers 1

Game Developer 1

gdb 1

GDC 1

GGPO 1

GNU Project 1

HDMI Licensing Administrator 1

id Software 1

iputils 1

IRTF 1

jemalloc 1

Juniper Networks 1

Lua.org 1

Meta 1

mtr 1

net-tools 1

netfilter 1

Netflix 1

Network Time Foundation 1

OpenWrt 1

procps-ng 1

Red Hat 1

Seagate 1

Starlink 1

VLDB Endowment 1

과학기술정보통신부 1