一卡一卡
动作不流畅,反复短暂停住又继续动。 ping 值正常,多半是自己电脑的帧问题(客户端、操作系统);ping 忽高忽低,多半是 Wi-Fi 或线路的抖动。不过游戏内显示的 ping 通常是在每帧运行的游戏循环里测的,帧耗时一跳,ping 数字也可能跟着跳。
本白皮书从自己的游戏画面一直到服务器的数据库,分 13 层讲解画面一卡一卡、角色瞬移、掉线的原因。每个原因都写明了确认方法和负责团队,还可以在实验中调整条件亲自验证。内容以 MMO 案例为主,但大部分与游戏类型无关,适用于各类网络游戏。
原因有一百多个,但造成卡顿的因素大致可以归为四类:数据包来得晚、时快时慢、根本没到,或者某个环节停止了计算。游戏会用各种技术掩盖这些因素,掩盖失败时留下的痕迹,就是我们看到的卡顿的“样子”。
在 MMO 里,自己画面上的世界是根据服务器发来的数据包重新绘制出来的。具体因游戏而异,服务器通常每秒计算 10~30 次游戏状态(每一次称为一个 tick),再从结果中只挑出每个玩家周围的变化,用数据包发出去。自己的电脑读取到达的数据包,再绘制画面。所以卡顿大多是“数据包没有按时到达”时,游戏怎样呈现的问题。
也有四个因素之外的情况。服务器和自己的电脑对同一件事算出不同结果时(移动规则不一致、bug),即使线路正常,也会出现拉回或隐身/幽灵实体。这类卡顿的线索是:和 ping 无关,总在同一地点、同一操作上反复出现。
拿快递来说。快递总要 3 天才到,是延迟;有的一天就到,有的要五天,是抖动;箱子丢了,是丢包;物流中心关门了,是停顿。游戏靠“箱子晚到或没到,就根据前后的箱子推测补上(插值、外推)”“没到就请对方重发(重传)”这类方法撑下去。
讨论卡顿时,大多以毫秒(ms,1/1000 秒)为单位。只要记住下表中的几个数字,就更容易听懂研发团队和运维团队在说什么。
| 参照 | 时间 | 含义 |
|---|
CPU、磁盘、数据库、路由器、运营商线路,层虽然不同,结构却一样:有处理请求的 worker(CPU 核心、线程、DB 连接等),它们前面会形成队列。worker 空闲时队列是空的,但繁忙程度(利用率)一旦超过 80~90%,队列就会急剧变长。只有一个 worker、请求随机到达时,平均等待时间在利用率 50% 时等于处理时间,80% 时是 4 倍,90% 时是 9 倍。“CPU 明明还剩 10%,为什么会卡?”答案就在这里。而且监控看板上的 CPU 数值通常是多个核心、1~5 分钟的平均值,会掩盖只有一个核心跑满 100% 的情况,以及只集中在几秒内的瞬间高峰。
本白皮书讨论网络游戏在游玩过程中导致卡顿的原因,范围从绘制游戏画面的电脑、手机开始,到家庭网络、运营商、数据中心,再到服务器和数据库。以视频流方式接收游戏画面的云游戏、作为独立服务运行的语音聊天,以及更新与下载速度,因为结构不同,不在讨论范围内。不过其中的网络原因(Wi-Fi、缓冲区膨胀、线路拥塞等)和这里介绍的原因相同。
按下技能键后,这个信号会经过自己的电脑、家里的网络、运营商、数据中心,最后到达服务器。在服务器内部经过多个层处理的结果,再反向经过同样这些层,绘制到画面上。一共 13 层,任何一层堵住都会卡顿。点击下方地图中的某一层,即可跳转到对应章节。
这是一个由一台服务器、一条线路、一台电脑组成的小型模拟。逐个破坏条件,看看一卡一卡、瞬移、拉回、快进、慢动作、操作延迟、卡住、掉线分别是怎么产生的。数据包时间线用线条表示每个包何时发出、何时到达。线越斜,说明用时越长;× 表示丢失的包。
玩家通常只会说“卡了”,但卡顿的表现形式能透露不少原因。每个症状旁的小图,是画面中角色走过的轨迹。点重叠表示停住了,点拉开表示变快了或跳过去了。
动作不流畅,反复短暂停住又继续动。 ping 值正常,多半是自己电脑的帧问题(客户端、操作系统);ping 忽高忽低,多半是 Wi-Fi 或线路的抖动。不过游戏内显示的 ping 通常是在每帧运行的游戏循环里测的,帧耗时一跳,ping 数字也可能跟着跳。
角色没有移动过程,一下子出现在很远的位置。 通常说明数据包中断了一段时间。排查丢包、线路短暂中断、服务器停顿、外推失败。别人都正常、只有一个人在跳,先怀疑这个人的线路。
自己的角色往前走着,又被拽回刚走过的位置。 自己画面上的预测和服务器判定对不上了。可能是自己的输入没到服务器(丢包),可能是服务器的移动校验把这段移动砍掉了,也可能是双方的移动计算结果不同。
停住的画面恢复后,积压的移动、打击和伤害一下子快速放完。 数据包在某处积压,然后一次性放出来了。典型原因有 TCP 等待重传、服务器追赶进度、客户端处理积压。
所有东西都动得很慢,技能施放和怪物移动像被拉长了一样。视服务器设计而定,也可能速度不变,表现为一卡一卡或瞬移。 服务器没能按时跑完 tick。线路没问题,所以在游戏外测的 ping 不变;游戏内的 ping 如果包含服务器处理的排队时间,可能会略有上升。排查人数暴增、视野计算、广播、内存不足。
按下之后要过一会儿才看到结果。画面本身可能很流畅。 往返时间(ping)长,或者某处的队列在堆积。排查距离、路由器队列、Nagle(把小数据包攒起来再发的 TCP 功能)、服务器队列。ping 很低却总是反应慢,就看自己电脑这边(垂直同步、低 FPS 等),或者每个操作都要等服务器确认的设计(见“同步方式”一章)。
画面里的一切短暂停住(0.5 秒到数秒),然后又动起来。 服务器整个停住了(GC、死锁、同步调用),或者线路短暂中断,或者自己的电脑停住了。
明明做了的操作像没发生过,或者结果过了好一阵又被推翻。 要么请求丢了(丢包、队列溢出),要么服务器的判定和自己画面上的不一样(判定时间点不同、预表现后被拒绝),要么保存时失败了(DB 锁、DB 故障、服务器崩溃)。
游戏中途连接断开,退回登录界面或弹出重连窗口。 超时时间内一个数据包都没收到。排查线路长时间中断、空闲超时、服务器崩溃或重启,以及服务器或自己的电脑停住的时间超过超时时间(长时间加载)。如果没有任何提示、游戏直接关掉了,先查客户端闪退(崩溃、内存不足),再查连接。
进不了游戏,或者停在加载、进场界面。 接收新连接的环节(服务器的连接队列、防火墙、登录服务器、DB)满了。维护刚结束时尤其多见。
本该在的 NPC、怪物、玩家只在自己画面上看不到,或者已经消失的实体只在自己画面上还留着。 多半是少了某个数据包或者绘制失败,和快慢关系不大。排查分线或位面不同、出现和消失通知丢失、加载中被丢弃、资源加载失败。离开视野再回来能不能看到,是关键线索。
有的游戏 ping 150 ms 也毫无感觉,有的游戏 60 ms 就显得迟钝。不只是动作游戏如此。线路相同时,这种差异通常来自客户端和服务器事先约定“由谁、在什么时候、决定什么”的方式,也就是同步设计。其中有些是有意的取舍,有些则确实是没做好。
所有网络游戏都在解决同一个问题。服务器和自己的电脑之间一定有时间差,双方总得有一方决定怎样处理“还没确定的事”。可选的做法大致有四种。
所以对 ping 是否敏感,比起游戏类型,更取决于两个问题:一个核心操作要等几次服务器往返,以及游戏规则允许的时间是否比“ping + 人的反应时间”宽裕。
| 方式 | 工作原理 | 常见于 | ping 150 ms 时的表现 | 弱点 |
|---|---|---|---|---|
| 请求-响应 服务器确认后显示 | 按下后先询问服务器,收到回复后才播放表现。 | 回合制、卡牌、放置类游戏,商店、交易、制作 UI,早期 MMO 的技能与物品使用 | 所有操作都晚约 0.2 秒才开始。回合制几乎感觉不到 | 连续操作、一个界面内有多次往返的 UI |
| 状态同步 + 插值 服务器权威 | 服务器每个 tick 下发游戏状态,客户端在两个状态之间插值绘制。 | 大多数 MMO 中其他玩家、怪物的显示 | 其他人显示的是约 0.2 秒前的样子。平时几乎察觉不到 | 抖动(到达间隔的波动)、丢包 → 瞬移,tick 率过低 |
| 客户端预测 + 服务器校正 | 自己的输入立即生效,服务器结果到达后再比对、纠正。 | FPS、动作 MMO、大多数 MMO 的移动 | 自己的操作即时响应。偶尔短暂拉回 | 与服务器计算不一致时频繁校正 |
| 延迟补偿 服务器回溯判定 | 服务器回溯到攻击者当时看到的过去时间点,判定是否命中。 | FPS、无锁定动作游戏 | 开枪的人觉得公平,被打的人却觉得“明明躲到掩体后面了还是被打中” | 被打的一方觉得冤。攻击者 ping 越高,回溯幅度越大,情况越严重 |
| 指令/目的地同步 | 只发送“走到这里”“攻击这个目标”这样的意图,双方各自计算。 | 点击移动的 MMO、Tab 锁定战斗、部分 MOBA | 只是起步稍晚,移动和攻击都很流畅 | 路径或结果不一致时需要校正 |
| 定时事件 基于服务器时间 | 像“在服务器时间 T 开始”这样连同未来的时间点一起通知,各端在那个时间点播放。 | 团本 BOSS 机制、过场动画、整点活动 | 预警时间比 ping 长时,实际上没有影响 | 比约定时间晚到时,会跳过开头部分 |
| 确定性帧同步 | 收集所有人的输入,在同一个逻辑帧里做完全相同的计算。输入会加上固定延迟。 | RTS(星际争霸类)、部分合作、解谜游戏 | 所有输入都固定延后(按下时立即用音效、提示来掩盖)。抖动大时所有人的画面一起卡住 | 抖动、丢包、最慢的那个人 |
| 回滚 先预测,再回溯 | 预测对手的输入先往下推进,猜错就回溯到过去重新计算。 | 格斗游戏(GGPO 类)、部分动作、体育游戏 | 操作手感几乎即时(通常有 1~3 帧的输入延迟)。对手的动作偶尔会跳几帧 | ping 高时回溯幅度变大,看起来像瞬移 |
| 客户端权威 | 各端自己决定自己的结果,服务器只负责转发和记录。 | 部分手游、休闲游戏,P2P、中继架构 | 自己的画面很流畅。和其他人画面上的结果对不上 | 外挂,“我明明打中了却没算” |
实际游戏会混用这些方式。通常按操作分别选择:移动用预测,技能先预表现再确认,BOSS 机制用定时事件,交易用请求-响应。
1. 一个操作里不夹任何一次往返。按下按钮就立即开始播放动画、音效、特效(预表现),服务器结果只用在伤害数字这类晚一点也看不出来的地方。
2. 游戏规则允许的时间比 ping 宽裕得多。BOSS 预警有 1~2 秒时,即使数据包晚 0.2 秒左右到、人的反应再花 0.25 秒,也完全来得及躲开。自己的技能如果有施法时间,服务器确认会在施法条读满的过程中一起完成,等待就藏在施法时间里。这是 Tab 锁定 MMO 对 ping 不敏感的最大原因。反过来,0.5 秒左右的短预警,ping 只要到 150 ms 就很难看清再躲开(见下方判定窗口实验)。
3. 提前接收连续操作。如果有预输入(技能队列),在冷却结束前按下一个技能也会先记下来,连招之间就不会夹着往返时间。
4. 吸收抖动。插值缓冲和基于服务器时间的表现,会把“大多 150 ms、偶尔 250 ms”才到的数据包,变成始终晚 250 ms 左右的稳定数据流。代价是看到的画面再旧一点,换来的是流畅。人对固定的延迟很快就能适应,对忽快忽慢却很难适应。做得好的游戏会随着抖动变大变小,自动加长或缩短缓冲。
5. 判定与“自己看到的”一致。以玩家看到的时间点判定闪避和命中(延迟补偿),或者干脆采用与位置无关的规则(锁定目标)。
6. 一个人的延迟不会让其他人等待。在服务器权威架构下,即使自己的 ping 很差,其他人也不受影响。在帧同步或房主架构下,最慢的那个人决定所有人的体感。
对 ping 敏感,有时是有意为之的设计,有时是没做好造成的。
按下按钮后,在服务器回复之前既没有动画也没有声音。ping 值直接就是反应速度。
起因: 技能、移动、拾取都等服务器确认后才播放 → 结果: 从按下那一刻起,在往返时间加 tick 等待的这段时间里毫无反应 → 画面表现: ping 150 ms 时,所有动作都慢 0.2 秒
症状: 操作延迟 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
一次操作需要依次与服务器往返多次时,ping 就要乘以这个次数。
起因: 打开商店 → 请求列表 → 确认价格 → 购买 → 刷新背包,每一步都单独请求 → 结果: 收到上一个请求的回复后才发下一个请求 → 画面表现: ping 150 ms 时买一次东西要将近 1 秒。加载时间格外长
症状: 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
必须等服务器确认上一个技能结束后才能按下一个技能时,每次衔接中间都会插入一段往返时间。
起因: 只有在“上一个技能确认后”才接受下一个技能的输入 → 结果: 每两个技能之间都空出一段与 ping 相当的时间 → 画面表现: 每次衔接之间都出现空当,ping 越高 DPS 越低
症状: 操作延迟, 吞操作/回档 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
闪避、弹反、格挡这类需要反应的时间很短时,ping 会把这段时间吃掉,出现躲不开的攻击。
起因: BOSS 攻击预警 0.5 秒、弹反判定 0.2 秒这样的短判定窗口 → 结果: 预警看到得晚(下行延迟 + 插值),自己的输入也到得晚(上行延迟 + tick 等待) → 画面表现: 明明躲开了却被打中,弹反被吞
症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
服务器只按“当前服务器上的位置”判定命中时,自己看到的画面就会与判定结果对不上。
起因: 自己画面上的对手是约 0.2 秒前的位置(ping 150 ms、插值 100 ms 时) → 结果: 服务器按当前位置判定,自己瞄准的地方对手已经不在了 → 画面表现: 明明打中了却判定未命中。打移动目标必须打提前量
症状: 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
按攻击者的视角回溯得太远时,被打的一方明明已经躲好了,还是会被打中。
起因: 为照顾 ping 高的攻击者,服务器大幅回溯后判定 → 结果: 在被打者的画面上,自己早已躲到掩体后 → 画面表现: “躲到墙后还被打中”,ping 高的人反而占优
症状: 吞操作/回档 · 主责 研发团队·服务器开发
各自决定自己的结果,自己的画面很流畅,但结果会与别人的画面对不上,也容易被外挂利用。
起因: 位置、命中由客户端决定,服务器只负责转发 → 结果: 两个人都说自己先打中了对方,服务器无法验证 → 画面表现: 对手瞬移、穿墙,“我打中了却没算”
症状: 瞬移, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
所有人一起计算同一个逻辑帧的结构下,只要有一个人的输入晚到,所有人都得等。
起因: 每个逻辑帧都要集齐所有玩家的输入才能计算 → 结果: 某个人的输入因抖动、丢包而晚到 → 画面表现: 所有人同时顿一下,严重时弹出“正在等待玩家”窗口
症状: 卡住, 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
先预测对手的输入并显示出来,猜错了就回滚重新计算。ping 越高,回滚的幅度越大。
起因: 对手改变了输入(与预测不同) → 结果: 实际输入晚到约半个 ping,就要回滚这么长时间重新计算 → 画面表现: 对手的动作跳过几帧或突然改变
症状: 瞬移 · 主责 研发团队·客户端开发
服务器事件不附带发生时刻、一收到就播放时,网络抖动会原封不动地让表现时机忽快忽慢。
起因: “开始攻击”“播放特效”事件一到就执行 → 结果: 每个数据包到达时间不同,间隔忽长忽短 → 画面表现: 连续攻击动作时快时慢,BOSS 技能时机每次都不一样
症状: 一卡一卡, 快进 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
请求攒到下一个 tick 才处理,结果又在再下一个 tick 才发出,tick 间隔就被叠加了两次。
起因: 收到的请求在下一个 tick 处理 → 结果: 处理结果也攒到下一个发送 tick 统一发出 → 画面表现: 线路 ping 很低,反应却稳定地慢约 1.5 倍 tick 间隔。10 tick 服务器平均 0.15 秒,最差 0.2 秒
症状: 操作延迟 · 主责 研发团队·服务器开发
服务器对移动速度、冷却、射程检查得过于严格时,连因抖动扎堆到达的正常输入也会被拒绝。
起因: “一个 tick 内可移动的距离”“冷却容差 0 ms”这类严格标准 → 结果: 因抖动,两条指令挤在同一个 tick 到达,被判为违规 → 画面表现: 拉回,冷却好了技能却被拒绝
症状: 拉回, 吞操作/回档 · 主责 研发团队·服务器开发
由某个玩家的电脑充当服务器时,这个人的线路和电脑性能决定了所有人的体验。
起因: 房主的电脑充当服务器(P2P、Listen Server) → 结果: 房主线路或电脑慢,就会波及所有人;房主自己的 ping 为 0 → 画面表现: 只有房主占优,房主一退出所有人都卡住或掉线
症状: 一卡一卡, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
在自己画面上先显示出来的命中、技能,服务器事后不认可时,明明看到的结果就当没发生过。
起因: 命中特效、技能动作在服务器确认前先播放(预表现) → 结果: 服务器重新核对射程、目标位置、冷却、资源后拒绝 → 画面表现: 血溅出来了却没有伤害,技能动作放出去了却没效果,只有冷却在转
症状: 吞操作/回档, 拉回 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
只互传“去这里”,路径由双方各自计算时,计算稍有差异,角色或怪物就会走上另一条路,然后被拽回原位。
起因: 点击移动、怪物追击时只发送目的地,路径由客户端另行计算 → 结果: 地形数据差异、与其他角色碰撞、计算顺序不同,导致走了和服务器不同的路径 → 画面表现: 怪物穿墙走着走着突然被挪走,点击移动的角色像打滑一样转向
症状: 瞬移, 拉回 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
服务器每秒只发几次位置更新(快照)时,插值缓冲就得相应拉长,看到的其他角色也就是更久以前的状态。
起因: 为节省流量,位置更新每秒只发 5~10 次 → 结果: 要画得流畅,缓冲需设为包间隔的 2 倍(200~400 ms);设得短,只要漏掉一个包就会卡住 → 画面表现: 对手转向看起来很晚,与判定对不上。缓冲短时一卡一卡,丢包时瞬移
症状: 一卡一卡, 瞬移, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
卡顿反馈中最让人困惑的,是只有几个人或只有一边遇到的情况。慢的那个人在别人眼里是什么样子、会不会连带拖慢别人,完全取决于服务器处理输入的方式和同步方式。同一台电脑上开的两个客户端中只有一个看不到 NPC 的现象,也在本章讨论。
现在大多数 MMO 都是服务器权威架构。服务器决定所有结果,客户端绘制收到的结果。在这种架构下,卡顿大多只出现在慢的那个人身上。
但也有一个慢的人会拖慢所有人的架构。共同点是“有人在等那个人”。
| 服务器处理输入的方式 | 慢的本人遇到的情况 | 慢的人在别人眼里的样子 | 其他人自己的游戏 |
|---|---|---|---|
| 每个 tick 集中处理 固定 tick,收到的输入一次性处理 | 技能结果要晚 ping 加上等待 tick 的时间(操作延迟)。移动校验严格时会拉回 | 顿一下后一次走好几步(快进、瞬移)。只是 ping 高而没有抖动时很流畅 | 没有影响 |
| 到达即处理 事件驱动,收到立即应用并发送 | 操作延迟约等于 ping。只省下了等待 tick 的那段时间 | 移动忽快忽慢(轻微快进)。一起涌来的多个技能在一瞬间全部执行 | 没有影响 |
| 每个玩家单独的输入缓冲 每人各自暂存,一个 tick 处理一个 | 确认会晚一个缓冲的长度 | 相对流畅。缓冲空了会暂时停在原地 | 没有影响 |
| 延迟补偿判定 回溯到攻击者看到的时间点 | 瞄哪儿打哪儿(在回溯上限内) | 躲起来之后仍会被这个人的攻击打中 | 冤枉被打中(波及) |
| 帧同步、回合等待 | 操作延迟。输入晚到时卡住 | 所有人卡住 | 卡住。只是晚到也会操作延迟(波及全员) |
| 阻塞发送、同步处理 服务器等待这个人 | 卡住后快进 | 这个服务器线程负责的所有人都变慢 | 慢动作、卡住(波及这个线程负责的人) |
| 慢的人是主机 P2P、Listen Server | 本人的 ping 为 0 | 所有人的画面都一卡一卡 | 全员卡顿 |
| 由慢的人控制怪物 把怪物移动计算交给客户端 | 本人画面上的怪物正常 | 这个人负责的怪物顿一下后瞬移 | 所有和这些怪物战斗的人(波及) |
同一个人在同一台电脑上开了两个客户端,却只有一个看不到 NPC 时,原因几乎不会是线路。两个客户端用的是同一个路由器、同一条线路。差异出在三个地方。
最有力的线索有三个:头顶名字还在,只是角色模型不见了吗(服务器发了,绘制失败),离开视野再回来能看到吗(漏了一条出现通知),以及把看不到的那个窗口切到前台会好转吗(后台窗口处理受限)。反过来,已经死了的怪物只在自己画面上还站着的“幽灵实体”,是漏了离开通知。
线路差的人,输入会忽多忽少地扎堆到达服务器。服务器每个 tick 收到多少就应用多少时,在别人眼里,这个角色会顿一下,然后一下子走出好几步。
起因: 网络差的人的移动指令,有的 tick 到 0 条,有的 tick 一次到 2~3 条 → 结果: 服务器在收到的那个 tick 一次性全部应用,该角色的位置呈台阶式变化 → 画面表现: 在别人画面上只有这个角色顿一下,然后一下子快进移动。其他都正常
症状: 快进, 瞬移 · 主责 研发团队·服务器开发 · 配合 外部·外部
在包一到就立即处理并广播的服务器上,网络差的人扎堆到达的动作会被接连立即执行。
起因: 网络差的人的技能、移动请求扎堆到达 → 结果: 服务器一收到就按顺序执行,并立即通知所有人 → 画面表现: 在别人眼里,这个人一瞬间放出好几个技能,或像快进一样移动
症状: 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
服务器为每个人先攒一点输入,每个 tick 取出一个使用时,别人看起来很流畅,但本人动作在服务器上确定的时间也会相应推迟。
起因: 服务器把网络差的人的输入攒在缓冲里,每个 tick 应用一个 → 结果: 缓冲小,就经常被取空,该角色原地站住,或由服务器按最后一个输入推测移动;缓冲大,本人的输入就确定得晚 → 画面表现: 缓冲小,别人看到他顿一下;缓冲大,本人的技能结果出得晚(操作延迟)
症状: 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
使用抖动大的线路的人,输入会扎堆到达,经常触发服务器的速度、冷却检查。
起因: 特定运营商、地区的线路晚上抖动变大 → 结果: 服务器把扎堆到达的正常输入判为超速、冷却违规 → 画面表现: 只有这家运营商的玩家出现拉回、技能被拒,严重时被服务器踢出而掉线
症状: 拉回, 吞操作/回档, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维
在需要所有人在规定时刻一起反应的团本机制中,一个网络差的人反应晚了,就会让整个队伍失败。
起因: “所有人同时散开”“一个人按按钮”这类团队机制 → 结果: 网络差的人预警看到得晚,输入也到得晚 → 画面表现: 因为这一个人而团灭,其他队友觉得“都怪那个卡的人”
症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
有的游戏为减轻服务器负载,把怪物的移动计算交给附近某个玩家的客户端。这个人的线路差时,这只怪物在所有人的画面上都会动得很怪。
起因: 服务器把怪物的移动计算交给最近(或最先到)的玩家的客户端 → 结果: 负责的人上报结果晚到或扎堆到达服务器 → 画面表现: 只有这只怪物在周围所有人的画面上顿一下然后瞬移。负责的人自己的画面上正常
症状: 瞬移, 一卡一卡, 快进 · 主责 研发团队·服务器开发
物品、邮件堆了几千个,或者好友列表、黑名单、buff 特别多的角色,登录、存盘、向周围广播的数据量是别人的好几倍。与线路无关,只有用这个角色时才慢。
起因: 长期培养的角色或活动奖励在背包、邮箱里堆了几千个 → 结果: 每次登录、切换区域、存盘都要读写对应数量的 DB 数据,要发给周围的装备、buff 信息也很大 → 画面表现: 只有这个角色进入时加载很久,打开背包、邮件时顿一下。如果服务器在游戏线程里等待存盘完成,周围的人也会跟着出现短暂停顿
症状: 连不上/无限加载, 操作延迟, 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
两个角色在不同的分线或副本里,或者处在按任务进度显示不同 NPC 的不同“位面”时,看到的就是不同的世界。
起因: 第二个角色被分到了别的分线,或任务阶段不同 → 结果: 服务器不给这个角色发送该 NPC(正常) → 画面表现: 只有一边没有 NPC。看起来像 bug,其实是按设计运行
症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
刚进入场景,服务器就发来周围 NPC 的出现通知,而客户端还在加载地图,就把这些通知丢掉了。
起因: 服务器在完成进入处理后立即发送周围实体的出现通知 → 结果: 客户端正在加载,消息处理函数还没注册,于是丢弃通知 → 画面表现: 服务器认为已经发过了,不会再发。在离开视野再回来之前,NPC 一直看不到
症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
角色注册到视野网格的时刻与 NPC 跨网格移动的时刻重合时,这个 NPC 的出现通知可能会漏掉。
起因: 进入、换线、传送的处理与 NPC 移动在同一时刻重合 → 结果: 计算“新进入视野的实体”时漏掉了这个 NPC → 画面表现: 只有特定几只 NPC 看不到,或已经离开的 NPC 还留在原地
症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发
在服务器只发“与上次相比变化的部分”的方式下,丢了最初那一份完整信息(基准),之后的变化量就没法应用。
起因: 实体的完整信息(基准)包丢失,或在处理前被丢弃 → 结果: 客户端没有可以应用后续变化量的对象,于是忽略 → 画面表现: 这个实体看不到,或过了很久突然出现
症状: 隐身/幽灵实体, 瞬移 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
反过来,漏掉“已消失”的通知时,已经死亡或离开的 NPC、玩家只会留在自己的画面上。
起因: 死亡、离开、离开视野的通知丢失或顺序错乱 → 结果: 客户端认为这个实体还在 → 画面表现: 怎么打都没反应的怪物,早已离开的玩家还站在那里
症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
进入场景的那一刻,服务器会一次性发出周围几十到几百个实体的出现信息。如果用不可靠(unreliable)通道发送,或者客户端在加载中读不了 socket、接收缓冲区溢出,就会丢掉一部分,而且不会再来。
起因: 刚进入时出现信息在极短时间内集中到达 → 结果: 正在加载的客户端读 socket 读得晚,OS 接收缓冲区溢出;或者大的 UDP 包被分片,只丢一个分片整个包就没了。不可靠通道也不会重发 → 画面表现: 只有加载慢的那个客户端缺了几只 NPC。离开视野再回来就能看到
症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
死掉的 NPC 重新出现时,如果服务器重用同一个实体 ID,期间漏掉离开通知的客户端会把新 NPC 误认成旧 NPC。
起因: NPC 死亡后以同一个实体 ID 重新出现 → 结果: 漏掉离开通知的客户端认为是“已知实体”,忽略出现通知,或保留死亡状态不变 → 画面表现: 只有一边画面上没有 NPC,或 NPC 倒地不起,有时还显示成别的 NPC 的样子
症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
客户端被做成使用固定的本地端口时,同一台电脑上的第二个客户端要么用不了这个端口,要么和第一个客户端分着收包。
起因: 两个客户端要打开同一个本地 UDP 端口(用重用选项强行共用) → 结果: OS 只把收到的包交给其中一个 socket,或不保证由哪一个接收。路由器和服务器也把两个客户端看成同一个地址 → 画面表现: 一边收不到世界数据包,看不到 NPC 和其他玩家,或者掉线
症状: 隐身/幽灵实体, 掉线, 连不上/无限加载 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
服务器或中间服务器按 IP 或设备 ID 区分连接时,会把同一台电脑(同一公网 IP)上的两个客户端认成同一个人。
起因: 会话表按 IP 或 IP+设备 ID 建立 → 结果: 第二个客户端的信息覆盖或混进第一个会话 → 画面表现: 一边看不到 NPC,另一边掉线或收到别人的信息
症状: 隐身/幽灵实体, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
安全模块或服务器策略限制一台电脑上开多个客户端时,第二个客户端会无法启动或连接,或者先开的那个掉线。有些游戏只禁用额外客户端的部分功能。
起因: 安全模块检测到重复运行,或服务器限制同一设备的额外连接 → 结果: 拒绝第二次启动或连接,或断开其中一个。少数情况下只屏蔽额外客户端的部分功能 → 画面表现: 连不上,或其中一个掉线。只屏蔽功能的游戏中,只有一边看不到 NPC、商店
症状: 连不上/无限加载, 隐身/幽灵实体, 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
处于后台窗口的客户端,游戏、引擎、OS 都会减少它的帧和处理量。收到的包不能及时处理,就会积压或溢出。
起因: 游戏选项或显卡驱动的后台帧率限制(例如 NVIDIA 驱动可在每秒 20~200 帧之间指定),省电,引擎的后台暂停设置。OS 也会优先把 CPU、GPU 分给前台窗口 → 结果: 每帧处理的包数减少,队列堆积,接收缓冲区溢出后包被丢弃 → 画面表现: 窗口切到前台时一下子全冒出来,或者有的 NPC 始终看不到
症状: 隐身/幽灵实体, 快进, 掉线 · 主责 研发团队·客户端开发 · 配合 外部·外部
两个客户端同时写同一个缓存文件夹或锁住文件时,其中一个会加载不了 NPC 模型、纹理。
起因: 两个客户端同时写同一安装目录下的缓存、补丁文件 → 结果: 文件加锁失败,或读到写了一半的文件,导致加载失败 → 画面表现: 有名字牌却没有角色模型,或 NPC 是透明的
症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发
两个客户端共用显存时,新需要的模型、纹理没有地方加载,有一部分就画不出来。
起因: 两个客户端共用显存和内存。OS 有时还会先削减后台窗口的显存配额 → 结果: 引擎无法加载新的模型、纹理,或反复卸载再加载 → 画面表现: NPC 出现得晚、模糊或看不到,一卡一卡
症状: 隐身/幽灵实体, 一卡一卡 · 主责 研发团队·客户端开发 · 配合 外部·外部
显示人数限制、隐藏 NPC 名字牌或模型、低配模式这类选项在两个客户端上不同时,看到的东西就不一样。
起因: 只有一个客户端开了“周围角色显示数量限制”或低配模式 → 结果: 不绘制远处或优先级低的 NPC(正常) → 画面表现: 只有一边没有 NPC
症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发
第二个客户端来自另一个安装目录,或补丁没打完时,它不认识服务器发来的新 NPC ID,就会悄悄忽略。
起因: 装在其他文件夹里的客户端,或在打补丁过程中启动的客户端 → 结果: 收到不认识的 NPC ID、模型 ID 就跳过 → 画面表现: 只有新加的 NPC 在一边看不到
症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
服务器给每个连接的发送量设上限、按由近及远的顺序发送时,上限定得低的一方收到远处 NPC 会很晚,甚至收不到。
起因: 人多的地方,服务器在每个连接的发送量上限内按重要程度依次发送 → 结果: 带宽估算偏低的连接(例如因为是后台窗口而确认回得晚的一方),排在后面的实体会被一直往后推 → 画面表现: 远处的 NPC 只在一边显示得晚或看不到
症状: 隐身/幽灵实体, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
客户端估算的服务器时间不准时,刚到达的实体信息会被当成“还在未来”而搁置,或被当成“太旧”而丢弃。
起因: 某一个客户端对服务器时间的估算偏差很大(在加载中测量、从省电状态恢复) → 结果: 插值基准时间与实体信息的时间对不上 → 画面表现: 实体出现得晚,或看起来停在原地不动
症状: 隐身/幽灵实体, 一卡一卡 · 主责 研发团队·客户端开发
服务器指标上的“TCP 重传(Retransmission)”增加时,卡顿反馈往往也会随之增加。重传是“数据包丢了”或者“误以为丢了”的信号。原因可能出在从 Wi-Fi 到服务器网卡这条路径上的任何一处;在游戏这种稀疏发送小包的连接上,只丢一个包也可能让画面卡住几百 ms。本章整理重传的根本原因、查找原因的方法和解决方向。
| 种类 | 何时发生 | 恢复所需时间 | 游戏中的表现 |
|---|---|---|---|
| 快速重传 Fast retransmit | 后面的包先到,接收方通知“中间缺了”(重复 ACK、SACK)时 | 往返时间 + 后面 3 个包到达所需的时间 | 短暂顿一下。包越密集,恢复越快 |
| RACK/TLP 按时间判断丢包,重发尾包 | 较晚发出的包已到达,而前面的包过了一定时间仍没到时;或者一段时间没有 ACK 时,把最后一个包再发一次 | 后面包的确认一到就很快重发(RACK。可能只是顺序颠倒,所以会多等约往返时间的 1/4)。没有后续包时约为往返时间的 2 倍(TLP);尚未确认的包只有一个时,考虑到延迟 ACK,还要再多等 200 ms | thin stream 上也只是相对短暂地顿一下。新版 Linux 默认 |
| RTO 重传 Retransmission timeout | 没有任何信号、等待时间到期时 | 往返时间 + 至少 200 ms,每失败一次翻倍 | 几百 ms 到几秒的卡住后快进,持续太久会掉线 |
| SYN 重传 | 连接请求本身丢失时(连接队列 backlog 溢出、防火墙拦截) | 1 秒、2 秒、4 秒、8 秒 ……(Linux 6.5 及以上先以 1 秒间隔重发最多五次,然后才开始翻倍;旧版 Windows 从 3 秒开始) | 按下连接后,延迟正好落在 1 秒、3 秒这样的整秒数上,持续失败就会连不上/无限加载 |
| 虚假重传 Spurious retransmission | 没有丢,只是晚到或顺序颠倒,被当成丢失而重发 | 没有东西需要恢复,却白白缩减了发送量(Linux 通过 DSACK 或时间戳检测到时,有时会撤销缩减;DSACK 是接收方发出的“已经收到过”通知) | 浪费带宽,大流量传输变慢。指标上只有重传率偏高 |
| 零窗口探测 容易和重传混淆 | 接收方缓冲区满了、处于“先别发”状态时,发送方用来探询的包 | 直到接收方开始读取 | 卡住。线路本身正常,是接收方程序没能及时读取 |
重传率是“发出的数据包中重发的比例”。开机以来的累计值会把近期变化淹没在平时的数值里,所以要用 1 分钟这类固定间隔内的增量来计算。没有公认的标准,但服务器整体平均值的大致参考是:低于 0.1% 为健康,0.1~1% 时部分玩家偶尔顿一下,超过 1% 时很多玩家能感觉到,超过 3% 属于严重。手机、海外玩家多的游戏,平时数值就会更高。所以不要只凭一个数字判断,还要同时看比平时高了几倍。平均值会被少数差线路拉偏,按地区、运营商、服务器、时段拆开来看,是找到原因的捷径。如果中间有先接下连接、再重新连到服务器的设备(代理、部分负载均衡器和网关),游戏服务器的指标只能反映这台设备和服务器之间这一段。玩家侧的重传要在这台设备上看。
| 在哪里看 | 看什么 | 能看出什么 |
|---|---|---|
| 整台服务器(Linux) | nstat 间隔 1 分钟执行两次得到的增量:TcpRetransSegs ÷ TcpOutSegs,以及 TcpExt 系列的 TCPTimeouts、TCPLossProbes、TCPLossProbeRecovery、TCPLostRetransmit、TCPSpuriousRTOs、TCPDSACKRecv、TCPSynRetrans | 重传率、走到 RTO 的次数、发出 TLP 的次数及其中真正补上丢包的次数、连重发的包都再次丢失的次数、连接请求重传。DSACK、Spurious 多表示“没丢却重发了”。Linux 的 OutSegs 不含重传部分,严格的比例是 RetransSegs ÷ (OutSegs + RetransSegs),但在 1% 左右时差别很小 |
| 每条连接(Linux) | ss -ti 中的 retrans(正在恢复的/累计)、rto、backoff、rtt、cwnd、lost、reordering、bytes_retrans | 是否只有特定玩家、地区重传多,RTO 拉长了多少(backoff 是 RTO 连续翻倍的次数)。bytes_retrans ÷ bytes_sent 就是这条连接的重传率 |
| 逐次重传(Linux) | eBPF 工具 tcpretrans(bcc)。-c 按连接汇总,-l 包含 TLP | 每发生一次重传,就显示一行对端 IP、端口、连接状态。不用抓包就能轻量地确认重传集中在哪些玩家网段、哪台服务器 |
| 服务器网卡 | ip -s -s link 的 dropped、missed、crc,ethtool -S 的 rx_missed_errors、rx_no_buffer_count、rx_crc_errors 等(名称因驱动而异,mlx5 是 rx_out_of_buffer、rx_discards_phy),/proc/net/softnet_stat 的第 2 列(dropped)、第 3 列(time_squeeze) | 服务器网卡是否一收到就丢弃(环形缓冲区、CPU),或者网线、光模块有问题(CRC)。softnet_stat 每个 CPU 一行,数值是十六进制。time_squeeze 持续增长,说明负责收包处理的核心没能按时处理完 |
| 云网络 | AWS ENA 看 ethtool -S 的 bw_in_allowance_exceeded、bw_out_allowance_exceeded、pps_allowance_exceeded、conntrack_allowance_exceeded、linklocal_allowance_exceeded。默认的 CloudWatch 页面里没有这些指标,要用 CloudWatch agent 另外采集 | 是否在实例上限处被悄悄丢弃。数值在增长就是超出了上限。其他云厂商也有按实例规格划分的带宽、连接数上限 |
| 交换机、路由器、防火墙 | 端口的 CRC 与输入错误、出方向丢弃、流量监管超限、会话表使用量、丢弃日志 | 是否在数据中心设备这一段被丢弃。5 分钟平均利用率很低、出方向丢弃却在增加,就是微突发(极短时间内流量集中涌入) |
| 路径 | 用 mtr、pathping 看一直延续到终点的丢包。要发几百次以上才能看出 1% 左右的丢包,用和游戏相同的 TCP 端口发送(mtr -T -P PORT)会更准确 | 从第几跳开始丢包。只有中间某一跳显示丢包、后面都正常时,只是这台设备限制了测量用的回应(ICMP)。去程和回程的路径可能不同,所以还要从服务器侧向玩家侧测一次 |
| 抓包(两端) | Wireshark 过滤器 tcp.analysis.retransmission,以及同属 tcp.analysis. 系列的 fast_retransmission、spurious_retransmission、duplicate_ack、lost_segment、zero_window | 原始包在发送方的抓包里有、接收方没有,就是在两端之间丢的。接收方也有时,是虚假重传,或者 ACK 在回程上延迟或丢了。在接收服务器的环形缓冲区里被丢弃的包,在抓包中也会显示为“两端之间丢失”,所以要和网卡计数器一起看 |
| Windows Server | 性能监视器的 TCPv4\Segments Retransmitted/sec ÷ Segments Sent/sec、Network Interface\Packets Received Discarded、netsh int tcp show global、pktmon(Windows 10 1809、Windows Server 2019 及以上内置) | 重传率趋势、网卡是否一收到就丢弃、TCP 配置、在 Windows 内部哪个环节丢弃 |
排查顺序:和运维团队一起看时,按下面的顺序最快。
反馈中如果有时间(精确到秒)、玩家的运营商和地区、所在服务器、症状名称,运维团队就能立刻按这个顺序排查。
1. 不丢包(根本解决)
2. 快速恢复
tcp_thin_linear_timeouts;Linux 6.15 及以上可用 TCP_RTO_MAX_MS 降低 RTO 上限TCP_NODELAY(如果 Nagle 把新包攒着不发,RACK 可以利用的后续包就没了)ip route … rto_min)TCP_USER_TIMEOUT 和游戏心跳尽快断开失效连接并重连3. 降低对重传的敏感度(架构)
TCP_NOTSENT_LOWAT 等)。长时间卡住之后的快进会变短和重传恢复相关的参数大多是操作系统(内核)参数,能只对游戏连接单独开启的 socket 选项只有几个。经常因名字被混淆的 TCP_NODELAY 并不能加快恢复。不过如果不开(使用 Nagle),恢复期间新包会更晚发出。下表以 Linux 为准,Windows 的名称和支持范围不同。
| 参数 | 在哪里设置 | 改变什么 | 注意 |
|---|---|---|---|
TCP_NODELAY | socket 选项 | 关闭 Nagle。小消息不攒,立即发送 | 消除即使不丢包也会出现的 40~200 ms 等待。重传定时器(RTO)本身不变。但 Nagle 开着时,等待恢复期间新包也会被扣住,恢复后还要再等一次往返;快速重传和 RACK 依赖的后续包也发不出去,容易走到 RTO。游戏通常会开启 |
net.ipv4.tcp_recovery (RACK) | 内核参数 | 按时间判断丢包。不怕乱序,thin stream 也能快速恢复 | 默认值 1(开启)。Linux 4.4 引入,4.18 前后成为现在的形态。从 6.17 起 RACK 是唯一的丢包判断方式,改成 0 也没有效果。在没有 SACK 的连接上不起作用 |
net.ipv4.tcp_early_retrans (TLP) | 内核参数 | 一段时间(约往返时间的 2 倍)没有 ACK 时,把最后一个包再发一次,尽早发现尾部丢包(tail loss) | 默认值 3(开启),0 为关闭。需要 SACK 才能工作。已发出未确认的包(in-flight)只有一个时,会再多等 200 ms,变得和 RTO 差不多 |
net.ipv4.tcp_sack, tcp_dsack, tcp_timestamps | 内核参数 | 选择性确认(SACK,通知中间缺了哪些)、重复接收通知(DSACK)、往返时间测量(时间戳) | 默认全部开启。有些服务器在 2019 年 SACK 安全问题时关掉后一直没恢复。SACK 关闭时,RACK、TLP 也会一起失效 |
net.ipv4.tcp_thin_linear_timeouts / TCP_THIN_LINEAR_TIMEOUTS | 内核参数 / socket 选项 | 已发出未确认的包(in-flight)少于 4 个的连接,前 6 次 RTO 不翻倍 | 默认关闭。可以用 socket 选项只对游戏连接开启。不会缩短第一次 RTO |
TCP_RTO_MAX_MS / net.ipv4.tcp_rto_max_ms | socket 选项 / 内核参数(Linux 6.15 及以上) | 降低不断翻倍的 RTO 的上限(默认 120 秒)。最小 1 秒 | 避免连续丢包后 RTO 膨胀到几十秒。判定失效连接也会随之变快 |
net.ipv4.tcp_mtu_probing | 内核参数 | 大包持续丢失时缩小包大小,穿过 MTU 黑洞 | 默认 0(关闭)。1 = 重传持续 3 秒左右、怀疑有黑洞时才缩小(在此之前连接一直停住)。2 = 从一开始就以 1,024 字节起步,逐步尝试加大 |
ip route … rto_min | 路由配置 | 调低这条路由的 RTO 最小值(默认 200 ms) | 只用于服务器之间的内网。在公网链路上调低会增加虚假重传。Linux 6.11 及以上的 net.ipv4.tcp_rto_min_us 是整台服务器的值,公网连接也会一起改变。6.15 及以上可以用 socket 选项 TCP_RTO_MIN_US 只对内部连接调低 |
TCP_USER_TIMEOUT | socket 选项 | 重传持续时,放弃连接之前的时长 | 不会加快恢复。用于尽快断开失效连接并重连。如果不设置,Linux 会重传 15 次左右、持续约 15 分钟后才断开(tcp_retries2) |
SO_KEEPALIVE + TCP_KEEPIDLE 等 | socket 选项 | 确认空闲连接是否还活着 | 与重传无关。用于维持 NAT/LB 映射和检测失效连接 |
fq 队列 + SO_MAX_PACING_RATE、BBR | 队列配置 / socket 选项 / 内核参数 | 把数据包均匀分散发送,减少突发(一次性集中发送)造成的丢包 | 属于“预防”丢包。与恢复速度无关 |
Wi-Fi 和移动网络会在无线链路上重传几次,仍然失败就丢弃数据包。被丢弃的包要等过好一阵,才由 TCP 重新发送。
起因: 信号弱或干扰严重,无线链路上的传输接连失败 → 结果: 超过无线设备的重试上限(通常几次到十几次)就丢弃数据包 → 画面表现: 卡住的时长等于 TCP 重传的等待时间,后面的包在接收缓冲区里等着,之后表现为快进
症状: 卡住, 快进, 瞬移 · 主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发
路由器、运营商之间的互联链路、数据中心线路这类最窄处的队列一旦满了,新来的数据包就会被丢弃。
起因: 视频、下载和其他用户的流量把瓶颈链路占满 → 结果: 队列满的期间,新到的数据包接连被丢弃(tail drop)。没被丢弃的包也要在满队列的末尾排队 → 画面表现: 多个包同时丢失,长时间卡住后表现为快进,晚上多发
症状: 卡住, 快进, 拉回 · 主责 运维团队·网络运维 · 配合 外部·外部, 研发团队·客户端开发
服务器每个 tick 在一瞬间集中发出几千人份的更新,交换机的小缓冲区或云平台的瞬时上限不到 1 ms 就会溢出,一部分包被丢弃。
起因: tick 开始的瞬间,把发给所有人的数据包一次性发出 → 结果: 多台服务器流量汇聚的交换机端口缓冲区(每个端口几百 KB 到几 MB)或云实例的上限瞬间被冲破(平均利用率很低) → 画面表现: 多名玩家同时瞬移或顿一下,看平均值指标找不到原因
症状: 瞬移, 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维
运营商套餐限速、云实例上限和 DDoS 防护设备,有时会把超过规定速率的数据包直接丢弃,不放进队列。
起因: 瞬时发送量超过允许速率或允许突发量 → 结果: 超出的数据包不排队,直接丢弃(流量监管) → 画面表现: 每逢突发量大的瞬间就丢好几个包,卡住后表现为快进;平均速率看起来低于上限
症状: 卡住, 快进, 瞬移 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
线缆损坏、光纤连接器积灰、光模块老化都会产生误码,损坏的数据包会被设备悄无声息地丢弃。
起因: 线缆、光模块或连接器不良,导致比特翻转 → 结果: 设备丢弃校验和(CRC)不对的数据包 → 画面表现: 只有经过这条路径的玩家反复顿一下再快进,与时段无关
症状: 卡住, 快进, 瞬移 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 外部·外部
一端用自动协商,另一端把速率和双工写死,就会有一端以半双工工作,每当负载上来就因冲突丢包。
起因: 只有一端设备固定了速率和双工 → 结果: 一端以全双工、另一端以半双工工作,产生冲突和晚期冲突(late collision) → 画面表现: 平时正常,流量一上来,经过这台设备的所有人都先卡住再快进
症状: 卡住, 快进 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维
数据包已经到了服务器,却因网卡的环形缓冲区(暂存到达数据包的缓冲区)溢出,或内核中负责接收处理的 CPU 核心饱和而被丢弃。
起因: 在线人数激增、中断集中在一个核心、虚拟机 CPU 窃取时间、虚拟交换机过载 → 结果: 在环形缓冲区(rx_missed_errors 等,名称因驱动而异)或内核接收队列(softnet dropped)处被丢弃 → 画面表现: 人一多,全服同时出现输入生效慢、顿一下的情况
症状: 操作延迟, 卡住, 快进, 瞬移 · 主责 运维团队·系统运维
防火墙或 Linux 连接跟踪(conntrack,把经过的连接记录到表里的功能)在表满了,或判断连接状态不对时,会丢弃数据包。
起因: 连接跟踪表已满(table full),或去程和回程路径不同,只有一个方向经过防火墙(非对称路径) → 结果: 防火墙把数据包当成“未知连接”或“序列号超出窗口范围”的包丢弃 → 画面表现: 表满时新连接进不来;路径不对称时,只有走这条路径的人在反复重传后掉线
症状: 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发
防火墙、入侵防御设备(IPS)、DDoS 防护设备会逐个检查经过的数据包。一旦超出检测能力,处理不过来的包就会被丢弃。
起因: 高峰时段或活动期间,体积小的游戏数据包每秒涌入几十万个以上,或检测规则太重 → 结果: 设备的 CPU 或每秒包数上限被打满,在设备上丢弃。误判时正常数据包也会被拦截 → 画面表现: 这台设备后面的所有服务器同时出现卡住、瞬移,只在人多时加重
症状: 卡住, 快进, 瞬移, 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发
中间链路能通过的包变小了,“包太大”的通知(ICMP)却被拦截,大包无论重发多少次都会丢失。
起因: VPN 或隧道段的最大包长变小,包过大通知被防火墙拦截 → 结果: 发送方不知道原因,一直重传同一个大包,RTO 每次翻倍 → 画面表现: 平时正常,一到打开背包、人多的地方、进场加载这类要传大数据的时刻,连后面跟着的小包也全部卡住,最终掉线或无限加载
症状: 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
中间设备删掉空闲连接的映射(记录该连接应转发到哪里的条目)后,之后发出的包就送不到了。要么反复重传后掉线,要么设备回一个拒绝连接的 RST,立刻断开。
起因: 一段时间内没有数据包往来的连接(暂离、大厅) → 结果: 路由器 NAT、运营商 CGNAT、防火墙、负载均衡器、云安全组删除空闲映射 → 画面表现: 再次操作的瞬间开始连续重传,然后掉线,或者直接掉线
症状: 掉线, 卡住 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维, 运维团队·系统运维
互联网路由切换的几秒内,或者被分配到多条 ECMP 路径中某条故障路径的连接上,会出现丢包。
起因: BGP 路由重新计算,或多条路径(ECMP、LAG)中某一条的设备或线路故障 → 结果: 路径切换期间暂时丢包,或只有走这条路径的连接持续丢包 → 画面表现: 突然卡住几秒后快进,或者“重连就好了”(被分到了别的路径)
症状: 卡住, 快进, 瞬移 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部
数据包只是一时到得特别晚,并没有丢。但只要这段延迟超过 RTO,发送方就会判定为丢包并重传。
起因: 缓冲区膨胀、Wi-Fi 省电、移动网络无线状态切换、虚拟机挂起,使瞬时延迟达到几百 ms → 结果: RTO 先到期并重传,原包随后也到达(接收方收到重复数据) → 画面表现: 卡住和快进是延迟飙升本身造成的。虚假重传几乎不会让卡住变长,只会推高重传指标,被误认为丢包
症状: 卡住, 快进, 操作延迟 · 主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·客户端开发
数据包经过多条路径或聚合链路时顺序被打乱,接收方会用重复 ACK 通知“有包缺失”,发送方就把好好的包又发一遍。
起因: 按包分配路径的设备、按包分摊的 LAG(链路聚合)、路径切换的瞬间,都会打乱顺序 → 结果: 后面的包先到,累积 3 个重复 ACK → 快速重传 → 画面表现: 稀疏往来的游戏包几乎不受影响。人多处的大块状态更新和更新包下载会变慢,偶尔一卡一卡
症状: 一卡一卡, 操作延迟 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维
数据已经顺利到达,但表示“已收到”的 ACK 在塞满的上行队列里被耽搁或丢掉,发送方就会判定为丢包并重传。
起因: 家里在上传视频或做云备份,上行被占满 → 结果: ACK 在路由器队列里被耽搁几百 ms,或因队列溢出被丢弃 → 画面表现: 服务器发来的游戏包大多能按时到。堆在同一上行队列里的自己的输入被耽搁,出现操作延迟、拉回,偶尔有虚假重传
症状: 操作延迟, 拉回 · 主责 外部·外部 · 配合 研发团队·客户端开发
RTO 最小值调得太低,稍微晚一点就会产生虚假重传;默认值(200 ms)对游戏来说又太长,每丢一次包都要停顿很久。
起因: 为数据中心场景把 RTO 最小值调得很低,或者在公网链路上原样使用默认值 → 结果: 太低时瞬间的延迟也会引发大量重传,太高时每次丢包都要等很久 → 画面表现: 用默认值时,丢一次包就卡住几百 ms 再快进;调得太低,卡住会缩短,但虚假重传激增,浪费线路
症状: 卡住, 快进, 操作延迟 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
像游戏这样稀疏地发小包时,还没等到“后续 3 个包”,RTO 就先到期了。同样的丢包,停顿时间比大流量传输长得多。
起因: 包间隔在 100 ms 左右,已发出未确认的数据包(in-flight)只有寥寥几个 → 结果: 攒够 3 个重复 ACK 要 300 ms 以上,RTO(ping + 200 ms)先触发;连续丢包则每次翻倍 → 画面表现: 丢一次包卡住 0.3 秒左右,重传的包也丢了就卡住将近 1 秒,然后快进
症状: 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发
部分防火墙或加速设备删除或改写 TCP 选项后,丢多个包时每个往返只能恢复一个,或者窗口(一次能发送的量)变小,速度变慢。
起因: 防火墙的“TCP 规范化”、老旧的加速设备剥离 SACK、时间戳、窗口缩放选项 → 结果: 丢了多个包时每个往返只恢复一个,窗口被限制在 64 KB → 画面表现: 每次丢包卡住的时间长得多(没有 SACK 就用不了 RACK-TLP),恢复后表现为快进。下载更新包这类大流量传输也很慢
症状: 卡住, 快进 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维
接收方程序没有及时读取 socket,缓冲区满了之后,发送方就会停止发送,只发零窗口探测。这与线路无关。
起因: 客户端帧停住或服务器线程阻塞,读不了 socket → 结果: 接收窗口变为 0,发送方停止发送,只发探测(间隔越来越长) → 画面表现: 卡住后快进。抓包中能看到“ZeroWindow”,没有丢包
症状: 卡住, 快进 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·系统运维
连接请求因连接队列(backlog)溢出或被防火墙拦截而丢失时,客户端操作系统会从 1 秒后开始,以逐渐拉长的间隔重发。
起因: 维护结束后连接涌入,服务器连接队列溢出,或防火墙、DDoS 防护丢弃 SYN → 结果: 客户端操作系统从 1 秒后开始按固定间隔重传 SYN(旧版 Linux 为 1 秒 → 2 秒 → 4 秒) → 画面表现: 点击连接后延迟正好是 1 秒、3 秒这样的整秒数,一直失败就会连不上/无限加载
症状: 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维, 研发团队·客户端开发
同样是卡顿,修的地方也不同。客户端、服务器代码和同步设计由研发团队负责,线路、网络设备、服务器硬件、DB 服务器由运维团队负责。玩家的电脑和家庭网络、运营商链路、云厂商一侧的问题,两个团队都无法直接修复,只能引导玩家、向对方提需求或绕行。每张原因卡片都标出了主责和配合方,展开卡片上的“数值参考·确认方法·各团队应对”,就能看到各团队分别要做的事。
| 负责方 | 负责范围 | 常用解决手段 |
|---|---|---|
| 研发团队客户端 | 游戏客户端代码:帧、GC、加载,插值、外推、预测,客户端的网络处理(包括发送心跳和自动重连) | 修改代码、调整插值缓冲与预测、改变加载方式、心跳间隔与重连流程、客户端更新 |
| 研发团队服务器 | 游戏服务器代码:tick、线程、锁,同步设计,连接处理(accept 循环、listen 参数),心跳响应与断开连接的清理,socket 选项,查询与事务设计 | 逻辑优化、异步调用、分散 tick 与区域负载、登录排队、用会话令牌接续会话、socket 选项(TCP_NODELAY 等)、查询与索引设计、服务器更新 |
| 运维团队网络 | 线路与 IDC 网络设备(交换机、路由器、防火墙、负载均衡器、DDoS 防护),云上的网络 ACL、VPC 路由、负载均衡器,运营商与对等互联 | 设备配置与更换、线路与对等互联扩容、路由调整、向运营商升级处理、调整负载均衡器和防火墙的空闲超时与会话上限 |
| 运维团队服务器/OS | 服务器硬件与云实例(包括安全组和连接跟踪),操作系统与内核配置,网卡,发布与监控环境 | 扩容、更换实例、内核参数(sysctl:somaxconn、conntrack 等)、安全组配置与连接跟踪超时、网卡环形缓冲区与中断分散、调整 cron 和备份时间 |
| 运维团队DB 服务器 | DB 服务器与存储,DB 配置、复制、备份,缓存服务器 | DB 扩容、保障存储 IOPS、DB 参数与复制配置、调整备份与检查点 |
| 外部玩家/运营商/云厂商 | 玩家的电脑与家庭网络、运营商链路(不在我方合同范围内)、云厂商 | 引导玩家(改用有线等)、向运营商和云厂商提需求、游戏侧绕行与缓解 |
粗体数字是该负责方担任主责的原因数,+数字是配合的原因数。点击格子,下方会显示这些原因和该团队要做的事。
主责是根本原因所在之处,或者能消除根本原因的一方。即使原因在线路或设备,研发团队在此期间也要靠降低影响的设计(插值缓冲、输入重复发送、重连)撑住;原因在服务器代码时,运维团队加设备也只是暂时推迟问题。经常混淆的边界规定如下。
表中的“先找”是第一个要找的负责方,按符合这种现象的原因卡片的主责计数决定。列出两个时,前一个是担任主责的卡片最多的一方,后一个是一开始就要一起找来的一方。
| 现象 | 研发团队要做的事 | 运维团队要做的事 | 优先查看的指标 |
|---|---|---|---|
| 特定运营商、地区的丢包、抖动大 先找运维团队网络 | 自适应插值缓冲、输入叠加发送、抗丢包的 UDP 传输、用每条连接的丢包与重传统计找出受影响玩家的 IP/端口/时间、按线路状况放宽移动校验标准 | 用和游戏相同的协议、端口双向测量路径(mtr)、避开不良路径、向运营商升级处理、增加对等互联与线路 | 各运营商的丢包率与抖动分布、重传率 |
| TCP 重传增加 先找运维团队网络研发团队服务器 | TCP_NODELAY、把一个 tick 的发送分散到 tick 内、及时读取 socket(防止零窗口)、用心跳维持映射、实时数据包改走 UDP 或单独连接、不在发送缓冲区堆积旧位置(TCP_NOTSENT_LOWAT) | 消除丢包点(网线、光模块、双工、流量监管、防火墙连接跟踪、MTU)、调整 MSS、服务器环形缓冲区与中断分散、内核恢复参数(RACK、tcp_mtu_probing) | 重传增量(nstat)、零窗口次数、网卡与交换机端口的丢弃、CRC 计数器 |
| 服务器 CPU 饱和导致 tick 超出预算 先找研发团队服务器 | 优化视野计算与广播、把 tick 拆到多个线程、拆分拥挤的场景和分线、让工作线程数与 CPU 上限匹配、把 tick 处理时间记录为指标 | 单核性能(主频)高的 CPU 和实例、按核心的 CPU 利用率告警、确认 CPU 窃取时间(steal)和容器 CPU 限流、把处理中断的核心和 tick 线程的核心分开 | tick 处理时间、各核心 CPU 利用率、steal、限流次数(nr_throttled) |
| DB 响应延迟 先找研发团队服务器运维团队DB 服务器 | 查询、索引、事务设计(事务要短、统一加锁顺序)、在游戏线程之外异步调用、合并查询与缓存、调整连接池大小和等待超时 | 找出慢查询、执行计划、锁等待并同步给研发团队,检查点、复制、统计信息更新的配置,存储 IOPS,确认服务器数 × 连接池大小不超过最大连接数,DB 扩容 | 慢查询日志、锁等待、连接等待、复制延迟、IOPS |
| 维护结束后连不上 先找研发团队服务器 | 别让接收连接的线程(accept 循环)被其他工作阻塞、加大 listen 的 backlog 参数、登录排队系统、合并登录查询(消除 N+1)、客户端重试间隔逐步拉长并随机打散 | 内核 somaxconn 与 SYN cookie、防火墙和负载均衡器的会话上限、服务器 conntrack 与文件描述符上限、DB 缓存预热、活动前提前扩容服务器 | ListenOverflows、会话表与 conntrack 使用率、登录查询数与连接等待 |
| 挂机就掉线 先找研发团队客户端 | 客户端:以不超过最短空闲超时一半的间隔发送心跳(即使一个晚到或丢失,下一个也能在超时前到达),断开时自动重连。服务器:响应心跳,一段时间收不到就先清理连接,再用会话令牌接续。 | 汇总路径上负载均衡器、防火墙的空闲超时(网络)和云安全组的连接跟踪超时(服务器/OS),同步给研发团队,我方设备必要时调大。玩家路由器、运营商 CGNAT 的超时改不了 | 已断开连接的空闲时长分布(集中在某个值附近,就是超时值为这个值的设备)、网络类型(移动、有线) |
| 每到固定时间服务器就停顿 先找研发团队服务器运维团队服务器/OS | 把整点活动、存盘、定时器、缓存过期的时间随机打散,批量查询拆小、分批少量执行,明确指定停顿短的 GC | 错开 cron、备份、日志压缩的时间并调低 I/O 优先级,DB 备份放在从库上做、检查点均匀分布,确认磁盘突发积分,限制备份传输速度 | 停顿时刻与作业计划(cron、备份、批处理、检查点)的对照,GC 日志 |
| DDoS、流量暴增 先找运维团队网络 | 把游戏流量特征(端口、包大小、每秒包数)同步给运维团队,按账号、角色限制请求频率,尽早拦截异常数据包 | DDoS 防护(清洗)和贴合游戏流量的防护规则、隐藏服务器地址、设备的每秒包数上限;基于 IP 的限制要考虑运营商共享 IP 和网吧 | 每秒包数、设备 CPU 与丢弃、各地区和运营商的连接失败率(确认误判) |
| 玩家 Wi-Fi、电脑问题 先找外部玩家/运营商/云厂商研发团队客户端 | 游戏内网络状态显示(ping、丢包)、根据抖动自动调整插值缓冲长度、在出现延迟时的日志中记录网络类型和电脑 CPU 利用率、建议改用有线等提示文案 | 无法直接修复。同一运营商、地区的反馈集中时,重新归类为线路问题 | 反馈中的线路与设备信息、同一运营商与地区的占比 |
研发团队 → 运维团队
运维团队 → 研发团队
两个团队共同:指定一名故障处理负责人,在同一个频道里按时间顺序记录,并提前告知下一次同步进展的时间。主责转到另一个团队时,把这份记录一并转交,避免重复做同样的确认。结束后,用同一份记录修正相应原因卡片的负责方和要做的事。
在玩家电脑或手机上运行的游戏程序本身。即使网络完美,这里的帧一旦延迟,画面就会一卡一卡。网络不好时能把问题掩盖得多好,也是在这里决定的。
游戏每秒大约重复 60 次同样的工作:读取输入、处理收到的数据包、把游戏状态推进一步、绘制画面。这个循环跑一次就是一帧,60 FPS 时每帧有 16.7 ms(以 30 FPS 运行的手游是 33.3 ms)。一帧延迟多久,画面就停住多久,然后在下一帧把落后的进度一次性补上。
在网络方面,客户端要做的是“补足缺失的信息”。其他玩家的位置是服务器断断续续发来的,所以要把中间连起来画(插值);数据包中断时要靠推测继续移动(外推);自己的角色则不等服务器确认,先动起来显示(预测)。这些技术失败时的样子,就是瞬移、拉回、一卡一卡。
游戏客户端就像制作直播画面的电视台导播间。现场(服务器)的照片断断续续地传来,导播间把中间自然地接起来,看起来就像视频。照片晚到时没有照片可接,画面就会停住;导播间自己太忙,直播也会中断。
某一帧的计算比平时多花了几倍时间,画面短暂停顿。
起因: 技能特效激增、大量实体生成、UI 整体刷新挤在同一帧 → 结果: 16.7 ms 内没能完成,要花 50~300 ms → 画面表现: 画面顿一下,下一帧所有人一下子同时移动
症状: 一卡一卡, 卡住 · 主责 研发团队·客户端开发
回收用完丢弃的内存(垃圾对象)期间,整个游戏会停住。特点是按固定间隔一卡一卡。
起因: 每帧都创建并丢弃临时字符串、数组、列表 → 结果: 垃圾对象堆积后,GC 暂停主线程来回收 → 画面表现: 每隔几秒到几十秒,有规律地一卡一卡
症状: 一卡一卡, 卡住 · 主责 研发团队·客户端开发
第一次绘制没见过的区域、怪物、特效之前,要先读文件、生成 Shader,画面因此卡住。
起因: 进入新区域,首次出现的技能、装备、怪物 → 结果: 主线程等待文件读取和 Shader 编译 → 画面表现: 只在第一次卡住 0.1~1 秒,第二次起就正常
症状: 卡住, 一卡一卡 · 主责 研发团队·客户端开发
在 HDD 这类慢速存储设备上,开放世界读取纹理、模型的速度跟不上移动,实体会晚出现,游戏也会因等待读取而一卡一卡。
起因: 骑坐骑、传送等快速移动,或进入人多的地方,一下子需要大量新纹理、模型 → 结果: HDD 等慢速存储设备达不到所需的读取速度,读取请求堆积,部分加载还会让主线程一直等到完成 → 画面表现: 纹理有一段时间是模糊的,建筑、角色出现得晚,等待读取的瞬间出现一卡一卡、卡住
症状: 隐身/幽灵实体, 一卡一卡, 卡住 · 主责 研发团队·客户端开发 · 配合 外部·外部
攻城战、世界 BOSS 这类数百人同屏的场景,光是绘制开销就承受不住。
起因: 同一画面里数百人和特效叠在一起 → 结果: 动画、阴影、头顶名字、特效的开销随人数成比例增加 → 画面表现: FPS 从 60 掉到 15,所有动作一卡一卡,操作也有延迟
症状: 一卡一卡, 操作延迟 · 主责 研发团队·客户端开发
收到的数据包每帧只处理固定数量时,一下子涌来的数据包会不断顺延到下一帧。
起因: 人多的地方每秒涌入数千条更新 → 结果: 主线程受每帧处理量上限限制,读不完 → 画面表现: 其他人的动作体现得越来越晚,而且是集中补上
症状: 快进, 操作延迟 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
收到服务器数据包就立刻绘制,抖动(到达间隔的波动)会原样暴露在画面上。
起因: 收到的位置直接绘制,或缓冲比抖动短 → 结果: 数据包晚到多久就停多久,扎堆到达就跳一下 → 画面表现: 其他角色一顿一顿地移动
症状: 一卡一卡 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
数据包没到的这段时间,按最后的速度继续显示移动,发现猜错后再纠正回去。
起因: 数据包接收中断,按最后的方向和速度继续移动 → 结果: 实际上对方已经停下或转向 → 画面表现: 对方角色往前走了好一段,又突然被挪到真实位置,或者穿墙而过。数据包到达间隔忽长忽短时,会反复冲到前面又被拽回,移动时像在颤动
症状: 瞬移, 一卡一卡 · 主责 研发团队·客户端开发
自己的客户端先把移动显示出来,服务器却算出了不同的结果,自己的角色就会被拽回去。
起因: 客户端在服务器确认之前先移动(预测) → 结果: 服务器对碰撞、移动速度、buff 的计算不同,或者没收到指令 → 画面表现: 确认到达时,自己的角色被往回拽
症状: 拉回 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
停顿一次之后集中补算积压的计算,又因为这些计算再次积压。
起因: 游戏模拟按固定间隔运行,中途停顿了一次 → 结果: 把积压的步长挤到一帧里集中计算 → 画面表现: 长帧接连出现、不断冲高,或者撞到上限后整个世界变慢
症状: 一卡一卡, 快进, 慢动作 · 主责 研发团队·客户端开发
客户端估算的服务器时间不准时,插值时间点和冷却判定都会错位。
起因: 连接时只对一次服务器时间,ping 变化后也不再调整 → 结果: 插值时间点、冷却结束时刻与服务器错位 → 画面表现: 对方偶尔顿一下,冷却已结束技能却被拒绝
症状: 一卡一卡, 吞操作/回档 · 主责 研发团队·客户端开发
用精度较低的小数类型(float)保存游戏时间时,运行越久,时间分辨率(能区分的最小时间差)越低,动作和特效会发生颤动。
起因: 把游戏启动后经过的时间累加到 float 里,或直接传给 Shader → 结果: 运行越久,float 能表示的最小差值越大 → 画面表现: 只有开了几天的客户端,角色、动画、流动特效不停颤动,重启后就正常
症状: 一卡一卡 · 主责 研发团队·客户端开发
GPU 画好的帧先在队列里攒几张,再按显示器刷新周期送出,这段时间里输入响应就会变迟。
起因: 显卡驱动预先在队列里攒 1~3 帧 → 结果: 输入体现到画面上要多花这么久 → 画面表现: ping 不高,操作却发沉、迟钝
症状: 操作延迟, 一卡一卡 · 主责 研发团队·客户端开发 · 配合 外部·外部
运行越久内存占用越大,游戏越来越慢,最终被强制关闭。
起因: 切换区域时纹理、UI、特效没有释放 → 结果: GC 变频繁,系统内存不足,发生 swap → 画面表现: 玩几个小时后越来越一卡一卡,最后被强制关闭(玩家看来像掉线)
症状: 一卡一卡, 掉线 · 主责 研发团队·客户端开发
未处理的错误导致游戏关闭。玩家看来像掉线,服务器其实正常。
起因: 空引用、内存不足、显卡驱动错误 → 结果: 游戏进程被强制结束 → 画面表现: “闪退了”的反馈。同一时刻其他人正常
症状: 掉线 · 主责 研发团队·客户端开发 · 配合 外部·外部
为防外挂,与游戏一起运行的安全模块会定期扫描。扫描太重,或与安全服务器之间的心跳(定期发送的存活确认信号)延迟,就会一卡一卡或掉线。
起因: 安全模块定期扫描游戏内存、正在运行的程序和驱动 → 结果: 扫描期间游戏线程停住,或者心跳没能按时发出 → 画面表现: 按固定间隔顿一下,严重时弹出安全错误提示并掉线
症状: 一卡一卡, 卡住, 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
游戏运行在 Windows、Android、iOS 上,与其他程序共用 CPU、内存、网络。操作系统给游戏分配 CPU 太晚、为了省电降低速度,或者把后台应用挂起时,就会卡顿。
操作系统(OS)的调度器(决定轮到谁使用 CPU 的功能)把 CPU 时间分给多个程序。游戏、杀毒软件、浏览器、更新程序都在等“轮到自己”,OS 以几 ms 到几十 ms 为单位(时间片)轮流分配核心。OS 会给前台的游戏稍高一点的优先级,但要做的事比核心多时,游戏也得等,这段等待就会拖慢帧。
网络也要经过 OS。网卡、Wi-Fi 芯片收到的数据包,先放进驱动和 OS 的接收缓冲区,等游戏来取。游戏太忙、取得太晚,缓冲区就会溢出;一次全部取出,就会出现快进。在手机上特别要注意的是,OS 为了省电会让无线连接进入省电状态,也会频繁挂起应用本身。
OS 就像让多位厨师共用唯一一间厨房的主厨。即使游戏正在做急菜,只要名叫“杀毒扫描”的厨师占住了灶眼,游戏就得等。厨房太热时(发热),主厨还会把火调小。
杀毒扫描、Windows 更新、直播软件、浏览器视频占着 CPU 核心时,游戏线程分不到 CPU,只能等待。
起因: 其他程序长时间占用 CPU 核心 → 结果: 游戏线程等待调度 → 画面表现: 帧变慢,收到的数据包也处理得晚
症状: 一卡一卡, 快进 · 主责 外部·外部 · 配合 研发团队·客户端开发
笔记本电池模式、手机省电模式、设备发热都会让 CPU、GPU 降速。发热的特点是一开始正常,过一阵子才变慢。
起因: 处于电池模式或省电模式,或者设备发烫 → 结果: 视设备不同,CPU、GPU 频率降低 30~50% → 画面表现: 出现 FPS 下降、一卡一卡:省电模式下一开游戏就出现,发热则在玩几分钟到 20 分钟左右后出现
症状: 一卡一卡, 操作延迟 · 主责 外部·外部 · 配合 研发团队·客户端开发
Windows 默认定时器以 15.6 ms 为单位,“只休眠 1 ms”实际要等到下一个定时器周期,最长会拉长到 15.6 ms。
起因: 帧率限制、数据包发送用 Sleep(短暂等待)方式实现 → 结果: 操作系统只以 15.6 ms 为单位唤醒线程 → 画面表现: 帧间隔和输入发送间隔忽长忽短
症状: 一卡一卡 · 主责 研发团队·客户端开发
为了看通知把应用暂时切到后台,几秒后系统就会挂起(suspend)应用,这期间服务器就会断开这个玩家的连接。
起因: 因看消息、接电话把游戏切到后台 → 结果: 游戏引擎暂停游戏运行,系统随后也会挂起应用和网络 → 画面表现: 切回来时已经掉线,需要重连
症状: 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
走出家门时 Wi-Fi 断开、切换到 4G 或 5G,自己的 IP 地址会改变,原有连接随之失效。
起因: Wi-Fi 信号变弱,切换到移动网络 → 结果: 自己的 IP 地址改变,用旧地址建立的连接无法再收发数据 → 画面表现: 画面短暂卡住后掉线或重连
症状: 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维
杀毒软件、防火墙逐个检查数据包会增加延迟,检查过度时还会把游戏误判为攻击并拦截。
起因: 安全软件逐个检查收发的数据包 → 结果: 每个数据包都多出延迟,检查跟不上时就被丢弃 → 画面表现: ping 不规律地飙升,或连接被拦截
症状: 一卡一卡, 连不上/无限加载 · 主责 外部·外部 · 配合 研发团队·客户端开发
游戏太忙,从 socket(操作系统提供的网络收发接口)里取数据包取得晚,操作系统的缓冲区就会溢出。
起因: 帧处理积压,游戏读 socket 读得晚 → 结果: 操作系统接收缓冲区填满后,UDP 直接丢弃,TCP 缩小接收窗口让发送方停止发送 → 画面表现: 瞬移(UDP)或快进(TCP)
症状: 瞬移, 快进 · 主责 研发团队·客户端开发
同时开着几十个浏览器标签页和游戏时,操作系统会把游戏的一部分内存换出到磁盘。
起因: 整机内存不足 → 结果: 操作系统把暂时不用的游戏内存移到磁盘 → 画面表现: 再次用到那部分内存时,视存储设备不同卡住几十到几百 ms
症状: 卡住, 一卡一卡 · 主责 外部·外部 · 配合 研发团队·客户端开发
画质选项要求的内存超过显卡显存时,操作系统要把纹理换到系统内存再取回,画面就会一卡一卡。
起因: 高纹理选项,加上人多处的各种装备、特效,把显存占满 → 结果: 操作系统把暂时不用的纹理移到系统内存,需要时再经由较慢的 PCIe 总线取回 → 画面表现: 每当出现新场景或新角色就顿一下,纹理一段时间内是模糊的
症状: 一卡一卡, 卡住 · 主责 研发团队·客户端开发 · 配合 外部·外部
操作系统为了搜索周围的 Wi-Fi,会定期切换信道,这期间通信会短暂停顿。
起因: 操作系统、驱动按固定周期搜索周围的 Wi-Fi → 结果: 搜索期间收发短暂停顿 → 画面表现: 按严格固定的间隔(例如每 60 秒)跳 ping
症状: 一卡一卡, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发
有线网卡、Wi-Fi 芯片在数据包之间进入省电状态时,重新唤醒需要时间。
起因: 网络设备的省电功能开着,或驱动太旧 → 结果: 唤醒(wake-up)延迟,偶尔设备重启 → 画面表现: 不规律的延迟,偶尔卡住几秒
症状: 一卡一卡, 卡住 · 主责 外部·外部 · 配合 研发团队·客户端开发
云同步、大文件下载、游戏更新在同一台 PC 上运行时,游戏数据包要在队列里排队。
起因: 其他应用把上传、下载带宽用满 → 结果: PC 和路由器的队列里堆积了游戏数据包 → 画面表现: ping 暴涨、操作延迟、快进
症状: 操作延迟, 快进 · 主责 外部·外部 · 配合 研发团队·客户端开发
切到别的窗口或最小化游戏时,游戏和 Windows 为了省电会让游戏降速运行。切回来时,积压的数据包会一下子涌来,或者已经掉线。
起因: 用 Alt+Tab 切到别的窗口,或最小化游戏 → 结果: 游戏不可见时大幅降低 FPS 或直接暂停,Windows 也会降低不可见程序的优先级 → 画面表现: 切回来的瞬间出现快进,切出时间长就会掉线
症状: 快进, 一卡一卡, 掉线 · 主责 研发团队·客户端开发
聊天软件、启动器、录屏软件、FPS 显示工具为了在游戏画面上叠加绘制自己的 UI,会介入游戏的渲染过程(hook)。每帧的工作量随之增加,偶尔还会与游戏冲突,导致顿一下或游戏被强制关闭。
起因: 聊天软件、游戏启动器、显卡工具、录屏软件的覆盖层处于开启状态 → 结果: 每次把帧送往屏幕时,覆盖层都会介入并叠加绘制自己的 UI → 画面表现: 帧略微变慢,弹出通知的瞬间顿一下,或出现画面异常、被强制关闭(玩家看来像掉线)
症状: 一卡一卡, 卡住, 掉线 · 主责 外部·外部 · 配合 研发团队·客户端开发
ping 正常、操作却发沉,可能是电视的画面处理、无线手柄或帧生成功能在输入和画面之间增加了延迟。
起因: 电视游戏模式没开,或使用蓝牙/无线手柄,或开启了帧生成(DLSS、FSR 帧生成) → 结果: 电视做画质处理时会延后送出帧,无线输入会因传输周期和干扰而晚到,帧生成要等下一帧到来才能生成中间帧 → 画面表现: ping 和 FPS 数字都不错,按下后要过一会儿画面才有反应,形成操作延迟
症状: 操作延迟 · 主责 外部·外部 · 配合 研发团队·客户端开发
数据包离开家之前的最后几米。距离虽短,但相当一部分卡顿反馈都出在这里。因为 Wi-Fi 是多台设备共用同一个无线信道,路由器则把全家人的流量排进同一个队列发出去。
Wi-Fi 要和邻居的路由器共用同一个无线信道(频段),2.4 GHz 频段还会和蓝牙、微波炉重叠。发送时发生冲突就稍等一下再重发,这种重传累积起来,数据包就会忽快忽慢地到达。ping 的平均值看着没问题,却时不时跳一下,是 Wi-Fi 的典型表现。
路由器是家里所有设备上网时都必须经过的设备。发出的量超过宽带线路能承接的速度时,路由器或调制解调器(猫)里就会形成队列;没有队列管理功能(SQM)的设备,会让这个队列一直堆到几百 ms 的长度。昂贵的路由器如果关掉了这个功能也一样。弟弟妹妹上传视频的那一刻,游戏数据包也得排在那个队列的最后面等。这种现象叫缓冲区膨胀(bufferbloat)。
路由器还会把“内部设备 ↔ 外部服务器”的连接记录在 NAT 表里,一段时间没有数据包往来就会从表中删除。这是挂机后掉线的常见原因。移动网络还要再加上基站切换、无线省电状态、信号弱等因素。
路由器就像小区唯一的出入口。搬家卡车(视频上传)排成长队时,送急件的摩托车快递(游戏数据包)也得在卡车后面等。聪明的路由器(SQM)会单独开一条快递专用道。
信号弱或有干扰时,无线段要重发好几次,数据包到达就会忽快忽慢。
起因: 墙体、距离、微波炉、蓝牙、邻居家的路由器导致信号质量下降 → 结果: 无线段发送失败 → 重传好几次 → 画面表现: 数据包到达忽快忽慢(抖动),角色一顿一顿,严重时丢包导致瞬移
症状: 一卡一卡, 瞬移, 拉回 · 主责 外部·外部 · 配合 研发团队·客户端开发
公寓楼这种有几十台路由器的地方,大家共用同一信道,要排队等发送机会。
起因: 几十台路由器使用同一个 2.4 GHz 信道 → 结果: 要发送就得等其他设备发完、信道空出来 → 画面表现: 大家下班回家的晚上,抖动(到达间隔的波动)增大,出现一卡一卡
症状: 一卡一卡, 操作延迟 · 主责 外部·外部 · 配合 研发团队·客户端开发
家里有人上传视频或下载大文件时,路由器队列里会堆积几百 ms 的数据包,游戏数据包也得排在后面等。
起因: 家人上传视频、云备份,自己直播推流,大文件下载,把线路占满 → 结果: 路由器或光猫把装不下的数据包堆进大队列 → 画面表现: 游戏数据包也在队列后面等待,ping 暴涨到几百 ms
症状: 操作延迟, 快进, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发
路由器会把一段时间没有数据包往来的空闲连接从 NAT 表中删除。这是挂机一段时间后一动就掉线的常见原因。
起因: 路由器把“内网设备 ↔ 外部服务器”的连接记录在 NAT 表(地址转换表)中 → 结果: 一段时间没有数据包就从表中删除(UDP 通常为 30~120 秒) → 画面表现: 服务器的数据包进不了家里,掉线
症状: 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发
便宜的路由器上挂了几十台设备、几千条连接,路由器本身就处理不过来。
起因: 几十台设备,加上 P2P、BT 下载开了几千条连接 → 结果: 路由器 CPU 和会话表饱和 → 画面表现: 数据包处理延迟、丢包,新连接建立失败
症状: 一卡一卡, 连不上/无限加载, 掉线 · 主责 外部·外部
坐公交、地铁移动时,切换基站期间通信会中断。
起因: 移动中接入的基站发生变化 → 结果: 通常只中断几十 ms,信号差导致切换失败时,可能中断几百 ms 到几秒 → 画面表现: 画面卡住后瞬移,中断久了就掉线
症状: 卡住, 瞬移, 掉线 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发
手机一段时间没有通信,就会把无线连接降到低功耗状态,下次收发数据包时要重新激活,所以会变慢。
起因: 短时间没有通信,手机就把无线连接切到省电状态 → 结果: 要发下一个数据包,就得重新激活连接 → 画面表现: 挂机后的第一个操作特别慢
症状: 操作延迟 · 主责 研发团队·客户端开发
在电梯、地下室、建筑物深处,重传增多、速度下降,最终掉线。
起因: 移动到信号弱的地方 → 结果: 无线重传增加,速度下降,短暂中断 → 画面表现: 抖动、丢包导致一卡一卡、瞬移,最终掉线
症状: 一卡一卡, 瞬移, 掉线 · 主责 外部·外部 · 配合 研发团队·客户端开发
在 5G 信号弱的建筑物内或 5G 覆盖边缘,手机会频繁在 5G 和 4G 之间来回切换,每次切换都会跳 ping 或短暂断网。
起因: 处在 5G 信号时强时弱的地方(建筑物内、5G 覆盖边缘) → 结果: 手机在 5G 和 4G 之间不时切换,每次都会出现短暂中断 → 画面表现: 原地不动也会无规律地跳 ping,偶尔卡住、瞬移
症状: 一卡一卡, 瞬移, 卡住 · 主责 外部·外部 · 配合 研发团队·客户端开发
咖啡馆 Wi-Fi 的登录页面或公司防火墙拦截了游戏连接。
起因: 尚未在登录页面完成认证,或防火墙屏蔽了游戏端口、UDP → 结果: 连接请求本身被拦截,或只有一部分能通过 → 画面表现: 连不上,能登录却进不了游戏
症状: 连不上/无限加载 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发
离开家的数据包,要经过运营商网络、多家运营商之间的互联链路,有时还要经过海底光缆,才能到达服务器所在的数据中心。这一段的延迟大多由距离和路径选择(路由)决定,很多时候游戏公司无法直接解决。
光在光纤中每秒约走 20 万 km。服务器在 1,000 km 外时,往返至少需要 10 ms,只要走的是光纤,这个数字无论怎么升级服务器或设备都无法缩短。实际的数据包要沿着运营商之间的互联点(对等互联)绕路走,所以通常要花理论值的 1.5~2 倍。像韩国到欧洲这种直线方向上几乎没有大型光缆的线路,要绕经东南亚和苏伊士或美国,达到 2.5~3 倍(往返约 230~270 ms)。
问题在于这条路径会随时间和情况变化。晚上 9~11 点前后大家都在看视频,运营商之间的互联链路容易拥塞;路由信息(BGP)变化时,数据包会有几秒到几十秒(少数情况下几分钟)到不了目的地;海底光缆断了,就会连续几周绕远路。如果卡顿是“只有特定运营商的用户”“只在晚上”“只在海外”,就先怀疑这一层。
运营商网络就像高速公路网。从首尔到釜山的路即使不堵,距离本身也要花时间;下班高峰的收费站(对等互联点)会堵;出了事故,导航会让你绕一大段远路。
光在光纤里每秒也只能走约 20 万 km。服务器离得远,条件再好也会慢。
起因: 服务器离得远(海外服务器、其他大洲) → 结果: 往返时间随距离增加(每 1,000 km 至少 10 ms) → 画面表现: 所有动作都有固定的操作延迟,判定上吃亏
症状: 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·网络运维, 研发团队·服务器开发
卫星互联网的信号要在太空中往返。静止轨道卫星光是往返就超过 0.5 秒;Starlink 这类低轨卫星平时很快,但在重新分配路径的瞬间延迟会波动,还可能短暂中断。
起因: 在家、船上或飞机上,通过静止轨道或低轨卫星互联网、走卫星的机上 Wi-Fi 接入 → 结果: 静止轨道高度约 36,000 km,往返距离本身就长;低轨卫星以很短的周期重新分配终端、卫星、地面站之间的路径,切换瞬间会短暂出现延迟和丢包 → 画面表现: 静止轨道:所有动作都有很大的操作延迟;低轨:平时正常,每隔固定间隔出现一卡一卡、瞬移
症状: 操作延迟, 一卡一卡, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发
受运营商之间互联协议的限制,离得近的服务器也可能要绕远路才能到。
起因: 自己的运营商和服务器所在的运营商之间没有直连 → 结果: 绕经其他国家或城市,距离和经过的设备都增加 → 画面表现: 只有特定运营商的用户 ping 特别高
症状: 操作延迟 · 主责 运维团队·网络运维 · 配合 外部·外部
晚上 9~11 点前后视频流量激增,运营商之间的互联链路(对等互联)容易拥塞。
起因: 晚间流媒体、下载集中 → 结果: 对等互联链路出现排队和丢包 → 画面表现: 只在晚上,特定运营商的用户出现一卡一卡、瞬移
症状: 一卡一卡, 瞬移, 拉回 · 主责 运维团队·网络运维 · 配合 外部·外部
海底光缆一旦中断,修好之前的几周(长则几个月)里流量要走很远的绕行路径,剩下的线路也会拥挤。
起因: 光缆断裂、设备故障 → 结果: 流量挤到很远的绕行路径和剩余线路上 → 画面表现: 海外玩家 ping 暴涨并伴随丢包,持续几天到几周
症状: 操作延迟, 瞬移 · 主责 外部·外部 · 配合 运维团队·网络运维
互联网的路由信息发生变化后需要重新收敛,在这几秒到几十秒(少数情况下几分钟)里会丢包。
起因: 某个运营商段的路由信息发生变化 → 结果: 几秒到几十秒内数据包丢失,或切换到新路径 → 画面表现: 突然卡住几秒,之后 ping 值变了(例如 40 → 70 ms)
症状: 卡住, 瞬移 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部
运营商和数据中心通往同一目的地的路径往往有多条,每个连接固定走其中一条。只要有一条路径出故障,分到这条路径上的人就会一直卡。
起因: 在多条线路捆绑的链路上,某一条线路或某台设备故障或拥塞 → 结果: 路径按地址、端口组合(哈希)确定,只有分到该路径的连接出现丢包和延迟 → 画面表现: 同一地区、同一运营商,只有部分人持续瞬移。重新连接后有时会恢复正常
症状: 瞬移, 拉回, 一卡一卡 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部
套餐流量用超,或套餐对特定流量做管控时,数据包会被延后或丢弃。
起因: 套餐流量用完后限速,或限制特定流量 → 结果: 数据包排队或被丢弃 → 画面表现: 用到一定流量后开始卡,移动网络尤其明显
症状: 操作延迟, 瞬移 · 主责 外部·外部 · 配合 研发团队·服务器开发, 运维团队·网络运维
有些网络会封锁特定的 UDP 地址和端口,或限制 UDP 速率;包检测设备还会过滤掉它识别不了的协议。用 UDP 通信的游戏在这类网络里会连不上,或频繁掉线。
起因: 从限制 UDP 速率的部分运营商网络,或部署了国家/运营商级流量检测(审查)设备的网络接入 → 结果: 封锁特定的 UDP 地址/端口,或在繁忙时段限制 UDP 速率,或过滤掉白名单以外的端口和协议,或只放行最初几个包之后就封锁 → 画面表现: 只有特定国家或运营商的玩家出现连不上/无限加载、连上后很快掉线,繁忙时段因丢包而瞬移
症状: 连不上/无限加载, 掉线, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发, 运维团队·网络运维
接头接触不良、线路老化或调制解调器异常,会造成持续丢包和周期性的线路中断。
起因: 线缆损坏、接触不良、调制解调器或光猫异常 → 结果: 误码导致数据包被丢弃;偶尔线路要重新连接,会中断几秒到 1 分钟左右 → 画面表现: 持续少量丢包,偶尔卡住几秒或掉线
症状: 瞬移, 卡住, 掉线 · 主责 外部·外部
DNS 负责把服务器域名解析成地址。DNS 慢或失败时,就找不到登录服务器和更新服务器。
起因: 运营商 DNS 故障或配置错误 → 结果: 找不到登录服务器、更新服务器的地址 → 画面表现: 点击登录按钮后长时间等待,或连不上。已经在线的人正常
症状: 连不上/无限加载 · 主责 外部·外部 · 配合 研发团队·客户端开发
针对游戏公司或同一网络中其他目标的大流量攻击,会把共享线路占满。
起因: 出现大量攻击流量 → 结果: 走同一线路的正常流量也被挤压、丢弃 → 画面表现: 大量玩家同时瞬移、掉线、连不上
症状: 瞬移, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 外部·外部
移动网络和部分运营商让多个用户共用一个 IP,并会在很短时间内清掉空闲连接的映射。
起因: 运营商设备管理着海量用户的会话表 → 结果: 会话表有上限,空闲超时短 → 画面表现: 挂机一会儿就掉线;共用同一 IP 的人被一起封禁的误判
症状: 掉线, 连不上/无限加载 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维
开启 VPN 或游戏加速器后,数据包会经过该公司的中转服务器。中转服务器远或拥挤时,反而会变慢。
起因: VPN、加速器把游戏数据包全部转到中转服务器 → 结果: 叠加了到中转服务器的距离和拥塞;隧道报头还让 MTU(一次能发送的最大包长)变小 → 画面表现: ping 升高并丢包;与使用同一中转地址的人一起被封,连不上
症状: 操作延迟, 瞬移, 连不上/无限加载 · 主责 外部·外部 · 配合 运维团队·网络运维, 研发团队·服务器开发
在到达服务器之前,数据包要依次经过路由器、DDoS 防护设备、防火墙、负载均衡器、交换机。平时这一段连 1 ms 都用不了,但只要有一台设备容量满了或出了故障,全服几千名玩家都会同时受影响。
每种设备的职责不同。路由器决定路径,DDoS 防护设备过滤攻击流量,防火墙只放行允许的连接,并用会话表跟踪所有连接。负载均衡器把进来的连接分给多台服务器,交换机把服务器彼此连接起来。
这些设备的共同弱点是表的大小和缓冲区大小。防火墙会话表满了就接不了新连接;负载均衡器会在一段时间后删掉空闲连接;交换机的小缓冲区在多台服务器同一时刻向几千人集中发包时(世界 BOSS 刷新),不到 1 ms 就会溢出。另外,一台设备故障、切换到备用设备(故障切换)的那几秒里,所有人都会卡住。
数据中心入口就像机场的安检口和登机口。安检口(防火墙)只放名单上的人通过,名单写满了就不能再收人。登机口工作人员(负载均衡器)会把长时间安静坐着的乘客当作“已经离开的人”,从名单上划掉。
防火墙会把放行的每个连接记到会话表里加以跟踪。表满之后就无法接受新连接。
起因: 连接激增或攻击使会话数达到上限 → 结果: 没有空位记录新连接,只能拒绝 → 画面表现: 新进来的人连不上/无限加载,部分已有连接也会掉线
症状: 连不上/无限加载, 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发
为防御攻击把流量牵引到清洗中心后,路径会变长,还可能把正常用户误判为攻击而拦截。
起因: 检测到攻击后(或常态)把入向流量牵引到清洗中心 → 结果: 路径变长,部分正常数据包被判为攻击 → 画面表现: 整体 ping 升高,只有特定地区或运营商连不上
症状: 操作延迟, 连不上/无限加载, 瞬移 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发
负载均衡器会在一段时间后清除空闲连接。游戏这边仍以为连接还在,结果就掉线了。
起因: 玩家一段时间内没有发送任何数据包(停在对话框、暂时离开) → 结果: 负载均衡器清理空闲连接(常见默认值 60~350 秒) → 画面表现: 再次操作的瞬间掉线
症状: 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发
云服务器上挂载的防火墙(安全组)同样会跟踪连接,空闲连接的跟踪条目到了规定时间就会过期。即使服务器不经负载均衡器、由玩家直接连接,挂机一段时间的玩家也可能掉线。
起因: 安全组采用会跟踪游戏连接的配置(只放行特定地址、限制出站规则、经由 NLB 等) → 结果: 连接空闲一段时间后跟踪条目过期,之后到达的数据包被安全组悄悄丢弃 → 画面表现: 暂时离开后再操作,先是没有反应,随后掉线。服务器程序很长时间都察觉不到
症状: 掉线 · 主责 运维团队·系统运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发
私有子网中的服务器访问外部(平台认证、支付、外部 API)时,由 NAT 网关替换地址和端口后发出。发往同一目的地的并发连接超过网关的端口上限,新连接就会失败。
起因: 多台服务器向平台认证、支付这类同一外部地址大量建立短连接,或长时间保持连接不关 → 结果: NAT 网关无法再为该目的地分配源端口,新连接失败 → 画面表现: 游戏内一切正常,只有登录、支付、奖励发放这类需要调用外部的功能失败或变慢(连不上/无限加载、吞操作/回档)
症状: 连不上/无限加载, 吞操作/回档 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发
连接全都挤到一台服务器上,或者一直把玩家分配到已经挂掉的服务器。
起因: 分配规则不合适,或健康检查反映不了实际状态 → 结果: 只有一台服务器过载,或连接请求被发往挂掉的服务器 → 画面表现: 只有部分分线、部分玩家出现慢动作、连不上/无限加载
症状: 慢动作, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发
多台服务器在同一瞬间向数千名玩家集中发包时,这些流量汇聚的交换机端口缓冲区很小,不到 1 ms 就会溢出。
起因: 世界 BOSS 登场、大范围技能,或多台服务器的 tick 恰好在同一瞬间对齐,集中发送 → 结果: 在多个端口汇聚到一个端口、或从高速端口转到低速端口的位置,缓冲区(每个端口几百 KB 到几 MB)瞬间被占满 → 画面表现: 部分数据包被丢弃,很多人同时瞬移、吞技能
症状: 瞬移, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维, 运维团队·系统运维
版本更新包分发、日志传输、备份与游戏共用同一条线路时,线路会被占满。
起因: 大流量传输占用同一条线路 → 结果: 线路排队和丢包增加 → 画面表现: 全服 ping 升高、出现瞬移
症状: 操作延迟, 瞬移 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维
某台路由器或防火墙发生故障、切换到备用设备(故障切换)的几秒内,所有人都会卡住。
起因: 设备故障或维护时切换到备用设备 → 结果: 切换需要几秒;会话信息未同步时连接会被重置 → 画面表现: 该服务器的所有玩家同时卡住,大量掉线
症状: 卡住, 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发
光模块或线缆不良时,经过这条路径的数据包会按一定比例损坏。
起因: 光模块、线缆不良导致误码 → 结果: 损坏的数据包被设备悄悄丢弃 → 画面表现: 只有走这条路径的部分服务器、玩家持续丢包,出现瞬移、拉回
症状: 瞬移, 拉回 · 主责 运维团队·网络运维
中间某段的 MTU(一次能发送的最大尺寸)变小,而包过大通知又被拦截时,只有大包会一直丢失。
起因: 隧道、VPN 段的 MTU 变小 → 结果: 包过大通知(ICMP)被防火墙拦截,发送方不知情 → 画面表现: 只有打开背包、角色列表这类数据量大的界面时才卡住,随后掉线
症状: 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
插在服务器上的网卡每秒要接收几十万到几百万个数据包,再交给 CPU。这里处理一旦积压,服务器程序连包来过都不知道,包就丢了。
网卡把到达的数据包依次放进环形缓冲区(循环使用固定数量槽位的接收缓冲区),再通知 CPU“包到了”(中断)。CPU 从环形缓冲区取出数据包交给 OS。进来的速度比 CPU 取的速度快,槽位就会全部占满,之后来的包都会被丢弃。只有网卡统计(ethtool -S)里的数字悄悄上涨,游戏服务器日志里不会留下任何错误,所以这种卡顿很难查。
现在的网卡有多个接收队列(环形缓冲区),可以把通知分散到多个 CPU 核心(RSS),但如果没配置好,或者流量都集中到一个队列,就只有一个核心跑到 100%,成为瓶颈。云服务器在网卡前面另有每秒包数、带宽、连接数上限,超出的部分在到达服务器之前就被丢弃。这在 CPU、环形缓冲区这类常规指标上看不出来,AWS 上只记录在 ENA 驱动统计里(ethtool -S 的 pps_allowance_exceeded 等)。
网卡是小区的信报箱,环形缓冲区是信箱的格数,中断是邮递员按门铃。信件大量涌来而取信的只有一个人时,格子就会塞满,信掉到地上。RSS 就是安排多个人取信。
网卡把数据包到达中断只发给一个 CPU 核心时,这个核心就会成为瓶颈。
起因: 只有一个接收队列,或者把负载分散到多个核心的 RSS 没有开启 → 结果: 单个核心跑满 100%,来不及取出数据包 → 画面表现: 人多时全服出现丢包和延迟(瞬移、操作延迟)
症状: 瞬移, 拉回, 操作延迟 · 主责 运维团队·系统运维
网卡用来暂存数据包的环形缓冲区太小时,流量瞬间涌入就会溢出,数据包被丢弃。
起因: 环形缓冲区保持默认值,容量偏小(因驱动而异,256~2,048 个槽位) → 结果: 突发时 CPU 还没来得及取走,缓冲区就溢出了 → 画面表现: 只在突发的瞬间丢包(瞬移、吞技能)。游戏服务器日志里没有任何痕迹
症状: 瞬移, 吞操作/回档 · 主责 运维团队·系统运维
为减轻 CPU 负担,网卡把数据包攒一批再一次性通知 CPU,攒包花了多少时间,就会晚多少。
起因: 网卡攒够一定时间或一定数量后再发中断 → 结果: 攒包期间数据包在等待 → 画面表现: 延迟略有增加。通常很小,设置过度时可达 ms 级
症状: 操作延迟 · 主责 运维团队·系统运维
云服务器每种规格都有每秒包数和带宽上限,超出部分会被悄悄丢弃。
起因: 同时在线人数增加,每秒包数超过实例上限 → 结果: 云网络丢弃超出部分 → 画面表现: 原因不明的丢包导致瞬移、吞技能。服务器 CPU 有余量
症状: 瞬移, 吞操作/回档 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部
流量用到 1 Gbps、10 Gbps 网卡的极限时,发送队列会越排越长,最终数据包被丢弃。
起因: 广播增多,发送量达到网卡极限 → 结果: 发送队列变长,溢出后丢弃 → 画面表现: 全服延迟、丢包(操作延迟、瞬移)
症状: 操作延迟, 瞬移 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
同一台物理服务器上的其他虚拟机大量占用网络和 CPU 时,自己这台服务器的处理会不规律地被拖后。
起因: 同一台物理服务器上的其他虚拟机大量占用资源 → 结果: 自己这台虚拟机的数据包处理不规律地延迟 → 画面表现: 没有明显原因,偶尔出现抖动(到达间隔的波动),表现为一卡一卡
症状: 一卡一卡 · 主责 运维团队·系统运维 · 配合 外部·外部
云厂商维护物理服务器(宿主机)时,会把虚拟机迁到其他宿主机(热迁移)或暂停片刻。这期间整台服务器停住,停住时间一长,连接就会断开。
起因: 云厂商因宿主机维护或预测到故障,把虚拟机迁到其他宿主机,或短暂挂起 → 结果: 迁移期间 CPU、内存、网络变慢,最后虚拟机会短暂完全停住(视厂商和方式而定,从不到 1 秒到 30 秒左右) → 画面表现: 该服务器上所有人同时卡住,随后出现快进、瞬移;停住时间超过超时设置时大量掉线
症状: 卡住, 快进, 瞬移, 掉线 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部
驱动 bug 或某项功能异常导致网卡停住、重启,这期间所有收发都会中断。
起因: 驱动 bug、卸载(offload)功能异常 → 结果: 网卡停住后重启(几秒) → 画面表现: 该服务器上所有人一起卡住,随后瞬移或掉线
症状: 卡住, 掉线 · 主责 运维团队·系统运维
这是把多个数据包合并成一个、以减轻 CPU 负担的功能。视配置而定,较小的游戏数据包可能要短暂等待下一个可合并的包。
起因: 网卡和内核把到达的数据包合并处理 → 结果: 开启了硬件合并(LRO)或合并等待时间设置时,会为等下一个包而短暂停留 → 画面表现: 延迟略有增加(一般不超过几十 µs)
症状: 操作延迟 · 主责 运维团队·系统运维
服务器的 Linux、Windows 内核负责接收连接、管理 socket 缓冲区,并把 CPU 和内存分配给游戏服务器程序。大部分默认值是兼顾多种用途的保守值,对几万人长时间在线的游戏服务器,往往不能直接照用。
有新连接进来时,内核先把连接请求放进连接队列(backlog),再由游戏服务器逐个取走。队列满了,Linux 会悄悄丢弃新请求,Windows 则返回拒绝响应。在 Linux 上,每个连接都要占用一个文件描述符(fd),也就是分配给打开的文件和连接的编号,而一个进程能持有的 fd 数也有上限。维护刚结束时几万人同时点击登录,最先耗尽的就是连接队列和 fd。
内存不够时,内核还会把内存换出到磁盘(swap,开启时);Linux 在内存真正耗尽时,会挑出占用内存最多的进程强制杀掉(OOM Killer)。游戏服务器通常是那台服务器上占用内存最多的进程,所以会第一个被杀掉。如果给容器设了内存上限,即使整台服务器还有余量,一碰到上限也会发生同样的事。时间同步(NTP)、计划任务、容器 CPU 上限、虚拟机的 CPU 窃取时间(其他虚拟机占用物理 CPU 期间的等待时间)这些看似与游戏无关的事,也会让服务器短暂停顿,或让定时器错乱。
服务器 OS 就像游乐园的入园闸口和管理处。开园时间(维护结束)大家一拥而上,闸口前的队伍(backlog)就会排不下;可发放的手环(文件描述符)发完了,就进不去了。
维护结束后数万人同时连接时,内核的连接队列(backlog)会溢出,连接请求被丢弃。
起因: 维护一结束,连接涌入的速度就超过了游戏服务器用 accept 处理连接的速度 → 结果: 内核的连接队列(backlog,取服务器代码传给 listen 的值与内核上限中较小的一个)已满 → 画面表现: 连接请求被丢弃,客户端反复重试,表现为连不上/无限加载
症状: 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发
每个连接都要占用一个文件描述符(fd,操作系统给打开的文件、socket 分配的编号),而一个进程能打开的 fd 数量是有限的。
起因: 同时在线人数达到进程的文件描述符上限 → 结果: 服务器无法接受新连接(Too many open files),打开日志、建立 DB 连接也一起失败 → 画面表现: 从某个固定人数开始谁都进不来,表现为连不上/无限加载
症状: 连不上/无限加载 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
收发缓冲区太小时,一旦突发流量涌来,UDP 收到的数据包会被丢弃,TCP 发送则因缓冲区没有余量而阻塞。
起因: SO_SNDBUF、SO_RCVBUF 用的是默认值或设得太小 → 结果: 突发流量或接收线程短暂停住期间,UDP 接收缓冲区溢出而丢包;TCP 发送缓冲区没有余量,只能等待 → 画面表现: 瞬移(UDP 丢包)或快进(TCP 等待)
症状: 瞬移, 快进 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
线程数远多于核心数时,操作系统光是让它们轮流运行就要耗掉大量 CPU。
起因: 比如每个连接创建一个线程,线程数达到几百到几千个 → 结果: 上下文切换(更换正在执行的线程)的开销和缓存未命中增加 → 画面表现: CPU 很忙但吞吐量低,tick 忽快忽慢,表现为一卡一卡、慢动作
症状: 一卡一卡, 慢动作 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
物理服务器(hypervisor)把虚拟机的 CPU 时间暂时让给其他虚拟机期间(CPU 窃取),游戏服务器会停住。
起因: 同一宿主机上的其他虚拟机大量占用 CPU → 结果: 本虚拟机每次失去几 ms 到几十 ms 的运行机会 → 画面表现: tick 耗时莫名飙升,表现为一卡一卡、卡住
症状: 一卡一卡, 卡住 · 主责 运维团队·系统运维 · 配合 外部·外部
给容器设置 CPU 上限后,一旦在规定周期(通常为 100 ms)内用完配额,周期剩下的时间里就会被强制停住(限流)。
起因: 在 Kubernetes 等环境中给游戏服务器容器设置了 CPU 上限(limit) → 结果: tick 计算集中的时刻用完配额,停住几十 ms,直到下一个周期 → 画面表现: 平均 CPU 不高,tick 却周期性冲高,表现为一卡一卡、慢动作
症状: 一卡一卡, 慢动作 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
空闲的 CPU 核心为了省电会进入深度省电状态(C-state),频率也会降低。数据包或定时器到来时,唤醒和升频都需要时间,处理小数据包时就会多出一段延迟。
起因: 操作系统的调频策略(governor)或 BIOS 电源设置允许深度 C-state 和低频率 → 结果: 空闲核心每次从深度省电状态唤醒都要多花最多几百 µs;频率被压在低位时,tick 计算本身也会变慢 → 画面表现: 通常很难察觉,但服务器间调用多时会累积,出现空闲时反而响应更慢的操作延迟。频率被压在低位时,人一多 tick 就会滞后,表现为慢动作
症状: 操作延迟, 慢动作 · 主责 运维团队·系统运维
Linux 在内存耗尽时,会挑出占用内存最多的进程强制杀掉,通常就是游戏服务器。
起因: 泄漏或暴增导致内存耗尽,或者达到容器内存上限 → 结果: 内核强制终止游戏服务器进程 → 画面表现: 这台服务器上的所有人同时掉线,最近的进度可能回档
症状: 掉线, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
操作系统为了凑出大页(huge page)而做内存规整,或为补充空闲内存而回收内存时,进程会停住。
起因: 空闲内存减少,或大页功能(THP)触发内存规整 → 结果: 申请内存的线程要一直等到回收或规整结束 → 画面表现: 不规则的服务器停顿(几 ms 到几百 ms)
症状: 卡住, 一卡一卡 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
服务器时钟一次性向前或向后调整几秒时,依赖系统时钟的定时器会一下子集中触发或停住。
起因: 时间同步一次性大幅调整时钟 → 结果: 定时器集中触发或停住,超时判定出错 → 画面表现: buff/冷却异常、集体掉线、快进
症状: 快进, 掉线, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
每天在同一时刻运行的日志压缩、备份、安全扫描会占用 CPU 和磁盘。
起因: 操作系统任务在固定时刻运行 → 结果: 与游戏服务器争用 CPU 和磁盘 → 画面表现: 每天凌晨 4 点这样的固定时刻出现一卡一卡、慢动作
症状: 一卡一卡, 慢动作 · 主责 运维团队·系统运维
游戏代码没变,服务器的 OS、内核、驱动、固件更新之后却开始变慢。更新可能会改变默认值、调度器、CPU 漏洞缓解措施(mitigations)和驱动行为。
起因: 定期安全补丁或新的服务器镜像更换了内核、驱动、固件 → 结果: 默认值、调度器变了,或新的漏洞缓解被开启,同样的工作要花更多 CPU 时间,线程获得 CPU 的顺序也变了 → 画面表现: 原本正常的服务器从更新那天起一直慢一点,表现为操作延迟;人一多就一卡一卡、慢动作
症状: 操作延迟, 一卡一卡, 慢动作 · 主责 运维团队·系统运维
Linux 防火墙会把所有连接记在连接跟踪(conntrack)表里,这张表达到上限后,新的数据包会被丢弃。
起因: 连接激增、反复建立短连接,使连接记录增多 → 结果: 表满后丢弃新连接和部分数据包 → 画面表现: 连不上,莫名丢包导致瞬移
症状: 连不上/无限加载, 瞬移 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发
游戏服务器频繁地与 DB 或其他服务器建立短连接又断开时,断开的连接会在一段时间内占着端口,导致新连接打不开。
起因: 每个请求都新建连接再关闭 → 结果: 先关闭的一方要占着端口约 60 秒(Linux,TIME_WAIT 状态),可用端口耗尽 → 画面表现: 内部请求失败,导致存盘失败、功能出错
症状: 吞操作/回档, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
这部分决定游戏怎样通过网络收发数据。同样的线路,用什么协议、socket 选项怎么开,决定了丢一个包的后果:可能只是“轻微跳一下”,也可能变成“卡住 1 秒后快进”。
TCP 保证按发送顺序、一个不落地交付。代价是丢了一个之后,在重新收到之前,连后面已经到达的数据也全都不交给游戏,一直等着。UDP 什么都不保证。到达的数据不用等,直接交出去,但丢了的要由游戏自己处理。所以动作性强的游戏会在 UDP 上按需自己实现可靠性(可靠 UDP),而很多 MMO 用实现简单的 TCP,接受它的弱点。
socket 选项是这些行为的细节配置:小包是否攒起来再发(TCP_NODELAY),收发缓冲区设多大(SO_SNDBUF、SO_RCVBUF),什么时候发现连接已失效(SO_KEEPALIVE、TCP_USER_TIMEOUT),关闭时剩下的数据怎么处理(SO_LINGER)。默认值大多面向用较少的包高效发送大量数据,对频繁收发小包的游戏往往不利。
TCP 只按发送顺序把收到的数据交给游戏。17 号包丢了,即使 18~30 号已经到了,也要等 17 号重新到达,全部一起等着(队头阻塞,HOL blocking)。UDP 到一个交一个,即使 17 号始终没来,其余的也能按时处理。
重传为什么会发生(Wi-Fi、拥塞、MTU 黑洞、虚假重传等)以及查找原因的方法,在 06 TCP 重传中按原因逐一讲解。
TCP 为了保证顺序,在重新收到丢失的那个包之前,不会把后面已经到达的数据包交给游戏。
起因: 一个数据包丢失 → 结果: 后面的包已经到了,却在接收缓冲区里等着 → 画面表现: 先停顿,然后一下子全部放出来,表现为快进
症状: 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
重传每失败一次,等待时间就翻一倍,短暂的线路中断会变成长时间停顿。
起因: 线路短暂中断,重传也接连失败 → 结果: 到下一次尝试的间隔按 0.3 → 0.6 → 1.2 → 2.4 秒这样翻倍(以 ping 100 ms 为例) → 画面表现: 线路只断了 1 秒,游戏却卡住 2 秒以上。断得更久,最终就会掉线
症状: 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
把小包攒起来再发的 Nagle 算法,和推迟发送 ACK 的延迟 ACK 叠加在一起,消息每拆开写一次,就会延迟 40~200 ms。
起因: 没开 TCP_NODELAY,却把小消息拆成多次写入 → 结果: 发送方在等 ACK,接收方却推迟发送 ACK → 画面表现: 线路 ping 很低,所有操作却都固定地慢半拍,表现为操作延迟
症状: 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
某个线路慢的玩家发送缓冲区已满时,如果用阻塞方式(缓冲区腾出空间之前调用不返回的发送)发送,服务器线程就会一直等这一个人。
起因: 慢客户端的发送缓冲区已满 → 结果: 阻塞发送,服务器线程一直等到缓冲区腾出空间 → 画面表现: 这个线程负责的所有人都卡住、慢动作
症状: 卡住, 慢动作 · 主责 研发团队·服务器开发
对待发送数据不断堆积的客户端,服务器会丢弃过时的更新或断开连接。
起因: 客户端线路跟不上服务器发送的量 → 结果: 服务器丢弃过时的更新,超过上限时断开连接 → 画面表现: 只有这个人瞬移或掉线
症状: 瞬移, 掉线 · 主责 研发团队·服务器开发
对方没发关闭信号就消失时,TCP 要过很久才能发现。keepalive(确认空闲连接是否还活着的 TCP 功能)默认关闭,开了也要空闲 2 小时才开始确认。
起因: 客户端因断电、线路中断,没发关闭信号就消失了 → 结果: 服务器认为连接还活着(keepalive 默认 7,200 秒。如果有正在发送的数据,约 15 分钟后才放弃重传) → 画面表现: 留下幽灵角色,重新登录时报“已在线”错误
症状: 连不上/无限加载, 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
超过 MTU(一次能发送的大小)的 UDP 包会在 IP 层分片,只要丢一个分片,整个包就被丢弃。
起因: 人多处的快照超过 1,500 字节 → 结果: 拆成多个分片发送,丢了任何一个就整个丢弃 → 画面表现: 包越大,丢包率就高出好几倍。只在人多的地方瞬移
症状: 瞬移 · 主责 研发团队·服务器开发
在 UDP 之上自己实现的重传规则太保守,恢复就慢;太激进,又会让线路更堵。
起因: 重传间隔、次数、窗口大小的设置与线路不匹配 → 结果: 恢复迟缓,或重复发送加剧拥塞 → 画面表现: 吞技能、快进,拥塞时卡顿更严重
症状: 吞操作/回档, 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
TCP 空闲一段时间后会重新缩小拥塞窗口(一次能发送的量),突然要发大量数据时只能分几次发。
起因: 在空闲了一段时间的连接上发送大量数据(例如进入城镇) → 结果: 拥塞窗口已经缩小,要分成多个往返发送 → 画面表现: 进入后周围的角色和 NPC 要晚几个往返才出现(服务器越远越明显)
症状: 操作延迟, 隐身/幽灵实体 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
TCP 把丢包当作拥塞信号,把发送速率降低 30~50%。遇到 Wi-Fi 丢包也会同样降速。
起因: 要发送的量大时,Wi-Fi 或线路上出现少量丢包 → 结果: TCP 大幅降低发送速率,然后缓慢恢复(Linux、Windows 默认的 CUBIC 降低 30%) → 画面表现: 人多的地方更新积压,表现为快进、操作延迟
症状: 快进, 操作延迟 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
服务器仓促断开连接时,最后发出的提示或存盘完成信号会丢失。
起因: 服务器以强制关闭(RST)的方式关闭连接。SO_LINGER 设为 0 秒,或者没读完收到的数据就关闭,都会这样 → 结果: 还在发送中的踢人原因和最后的数据被丢弃 → 画面表现: 莫名其妙地出现“因未知错误断开连接”
症状: 掉线 · 主责 研发团队·服务器开发
等一个 socket 时线程就干不了别的事,这种结构下人越多,整体就越慢。
起因: 每个连接都要等待读写的方式 → 结果: 一个连接的延迟蔓延到同一线程的其他连接 → 画面表现: 同时在线人数越多,整体越慢,表现为慢动作、操作延迟
症状: 慢动作, 操作延迟 · 主责 研发团队·服务器开发
多个进程分担同一个端口时,内核会按地址哈希给每个连接定好负责的进程,之后不再改变。负责的进程一旦停住,只有分到它那里的人在等。
起因: 网关、登录服务器用 SO_REUSEPORT 起了多个进程 → 结果: 即使一个进程因 GC 或过载停住,分到它的新连接和 UDP 包也不会转给其他进程 → 画面表现: 只有部分人连不上、卡住。进程数变化的重启期间,部分 UDP 会话会中断
症状: 连不上/无限加载, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
Windows 服务器向已经离开的客户端发 UDP 时,会收到“端口不可达”(ICMP)通知。这个通知会让下一次接收调用以错误结束;如果服务器代码把这个错误当作 socket 本身坏了来处理,所有使用这个 socket 的人都会受影响。
起因: 一直向刚离开的客户端地址发 UDP,收到“端口不可达”(ICMP)通知 → 结果: Windows 让接下来的接收调用以 WSAECONNRESET(10054)错误结束,服务器代码停止接收或关闭 socket → 画面表现: 使用这个 socket 的所有人一起卡住、掉线
症状: 掉线, 卡住 · 主责 研发团队·服务器开发
这是实际计算游戏逻辑的程序。移动、战斗、怪物 AI、视野计算、广播,都必须在一个“tick”内完成。人越往一个地方聚集,视野计算量和要发送的数据包就按人数的平方增长。
服务器按固定的 tick 间隔计算游戏状态。20 tick 服务器每 50 ms 计算一次,在这段时间内要应用所有玩家的输入、移动怪物、计算谁能看到谁(视野,AOI),再把变化发给所有能看到的人。这 50 ms 就是 tick 预算。超出预算,下一个 tick 就会推迟。每个 tick 只推进固定游戏时间的服务器,整个游戏世界的时间都会变慢(慢动作);按实际流逝的时间一次推进的服务器,速度保住了,但数据包变稀,于是出现一卡一卡和瞬移。无论哪种,响应都会变慢。如果一个游戏线程负责整个服务器(分线),这个服务器上的所有人都会受影响;如果按区域分了线程,就是这个区域的人一起受影响。
问题在于人数。如果所有人两两比较,100 人每个 tick 约要比较 1 万次,1,000 人约要 100 万次。所以服务器会把地图划成网格,只比较相邻格子,但世界 BOSS、攻城战、主城广场活动这类所有人都挤在一个格子附近的场景,网格的效果会打折扣,计算量和要发送的数据会暴增。再加上多个线程争同一份数据而等待的锁、tick 中途等待 DB 响应的同步调用,等待期间这个线程负责的所有玩家都会一起卡住。
服务器的 tick 就像指挥的节拍。乐团成员(玩家)越多,一拍之内要照顾的乐谱就越多,一旦跟不上节拍,整首曲子都会变慢。如果有人跑去仓库(DB)找一页乐谱,所有人都得等他。
一个 tick 内要做的事超出预算时,服务器的 tick 周期会被拉长,整个区域都会变慢或一卡一卡。
起因: 一个 tick(例如 50 ms)要处理的工作超出预算 → 结果: 本该每秒计算 20 次的游戏状态只算了 8 次 → 画面表现: 整个区域慢动作(视服务器设计也可能是一卡一卡),技能反应慢
症状: 慢动作, 操作延迟, 一卡一卡 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
所有人两两比较谁能看到谁,人数变成 10 倍时,计算量就会变成 100 倍。
起因: 所有角色两两比较距离,或者即使按网格划分,一个格子附近也挤了几百人 → 结果: 100 人约比较 1 万次,1,000 人约比较 100 万次 → 画面表现: 在世界 BOSS、攻城战这类人群聚集的地方,tick 耗时暴增,表现为慢动作、一卡一卡
症状: 慢动作, 一卡一卡 · 主责 研发团队·服务器开发
把一个人的移动发给所有能看到他的人时,要发的更新数量是聚集人数的平方。
起因: 把一个人的变化发给所有能看到他的人 → 结果: 1,000 人互相能看到时,每个 tick 有 100 万条更新 → 画面表现: 发送队列和带宽饱和,产生延迟和丢包(操作延迟、快进、瞬移)
症状: 操作延迟, 瞬移, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
每个区域由一个线程负责的架构下,人一旦聚到一处,只有那一个核心会跑到 100%。
起因: 一个线程负责一个区域(分线) → 结果: 人聚到一处时只有那个核心饱和,其余核心还有余量 → 画面表现: 只有那个区域卡顿,其他区域正常
症状: 慢动作, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
多个线程为了写同一份数据而等同一把锁时,线程再多,一次也只有一个在执行。
起因: 拍卖行、公会仓库这类公共数据被多个线程同时使用 → 结果: 拿到锁的线程结束之前,其余线程都在等 → 画面表现: 只有特定功能慢,严重时整个 tick 延迟
症状: 操作延迟, 卡住 · 主责 研发团队·服务器开发
两个线程互相等待对方持有的锁,就会永远停住。
起因: 线程 A 拿着锁 1 等锁 2,B 拿着锁 2 等锁 1 → 结果: 两者都永远停住,相关线程也一个接一个停住 → 画面表现: 整台服务器停住,看门狗重启服务器,所有人掉线
症状: 卡住, 掉线 · 主责 研发团队·服务器开发
tick 中途等待 DB 响应或文件写入时,等多久,服务器上整个游戏的运行就停多久。
起因: 在 tick 内等待 DB 查询或存盘、写日志、调用外部 API → 结果: DB 耗时 100 ms,tick 也停住 100 ms → 画面表现: 每当 DB、磁盘变慢,整个野外就顿一下
症状: 卡住, 一卡一卡 · 主责 研发团队·服务器开发
请求进来的速度超过处理速度、在队列里堆积时,排在后面的请求要过几秒才被处理,或者被丢弃。
起因: 请求到达的速度超过处理速度 → 结果: 队列越来越长,超过上限就丢弃 → 画面表现: 技能、交易反应慢或被吞
症状: 操作延迟, 吞操作/回档 · 主责 研发团队·服务器开发
所有怪物刷新、所有 buff 到期、整点奖励都挤在同一个 tick 时,那个 tick 的负载就会高出几十倍。
起因: 刷新、到期、奖励、自动存盘的定时器对齐到了同一时刻 → 结果: 那一个 tick 的工作量是平时的几十倍 → 画面表现: 每到固定时刻就顿一下
症状: 卡住, 一卡一卡 · 主责 研发团队·服务器开发
几百只怪物同时追着玩家计算路径时,会占用大量 CPU。
起因: 拉怪、大规模刷新使大量怪物同时追击 → 结果: 每只怪物都要做寻路计算 → 画面表现: 只有那片刷怪区出现慢动作
症状: 慢动作 · 主责 研发团队·服务器开发
把要发送的数据转换成字节并压缩也要消耗 CPU,人多时这部分开销会暴增。
起因: 每次更新都要把结构体转换成字节并压缩 → 结果: 开销与人数的平方成正比增长 → 画面表现: 发送变慢,表现为操作延迟
症状: 操作延迟 · 主责 研发团队·服务器开发
服务器进程因未处理的错误而崩溃时,这台服务器上的所有人会同时掉线。
起因: 指向不存在对象的错误(空引用)、错误的数据、内存不足等致命错误 → 结果: 服务器(或场景)进程终止 → 画面表现: 所有人同时掉线,上次存盘之后的进度可能回档
症状: 掉线, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
处理任务的工作线程全被慢操作占住时,新请求只能干等。
起因: 工作线程都在等外部 API、DB 响应,被占住 → 结果: 新请求没有线程可分配 → 画面表现: 登录、商城等特定功能无限加载
症状: 连不上/无限加载, 操作延迟, 卡住 · 主责 研发团队·服务器开发
bug 导致一个 tick 无法结束时,服务器会停住,然后被看门狗强制重启。
起因: 条件写错导致循环不结束,或递归失控 → 结果: tick 结束不了,服务器停住 → 画面表现: 卡住后所有人掉线
症状: 卡住, 掉线 · 主责 研发团队·服务器开发
几百人同时打一个 BOSS 时,这一只 BOSS 的计算全压在一处,命中信息还要发给所有看得到的人。
起因: 几百人不停地对一个 BOSS 施放技能、buff、debuff → 结果: BOSS 的血量、仇恨列表、debuff 计算全压在一处,每次命中都要把伤害数字、特效包发给所有看得到的人 → 画面表现: 技能延迟生效,伤害数字成批跳出,只有 BOSS 周围是慢动作
症状: 操作延迟, 快进, 慢动作 · 主责 研发团队·服务器开发
传送到挤满人的城镇时,服务器要把新进入视野的几百人的外观、装备、状态一次性发过来。
起因: 通过传送、登录、换线,突然出现在人多的地方 → 结果: 一次性生成并发送几百人的全部信息,自己的电脑也要一次性加载 → 画面表现: 刚到达时短暂停顿,角色一个个慢慢出现,操作延迟
症状: 卡住, 操作延迟, 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发
本该消失的地面物品、召唤物、已结束的定时器没有被清理而不断堆积,服务器开得越久,每个 tick 要做的事就越多。
起因: 地面物品、召唤物、过期定时器、空队伍信息没有及时删除 → 结果: 每个 tick 要遍历的列表一天比一天长 → 画面表现: 维护刚结束时正常,过几天后只有那台服务器或那个区域越来越迟钝
症状: 慢动作, 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发
新内容、特效、同步项增加了数据包的大小和频率,原本正常的服务器在版本更新后开始碰到 MTU、带宽、包数的上限。
起因: 版本更新增加了新技能特效、同步项、物品信息,数据包变大或变频繁 → 结果: 大包超过 MTU 被分片,增加的流量碰到带宽、云 PPS 上限、发送缓冲区的限制 → 画面表现: 版本更新后,人多的地方开始出现瞬移、吞技能、操作延迟。基础设施什么都没改,丢包却增加了
症状: 瞬移, 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维
服务器记住的一切,也就是角色、怪物、物品、地图,都放在内存里。内存本身很快,但一旦因 GC(回收不再使用的内存)而停顿、一点点泄漏出去,或者因内存不够而发生 swap(把部分内存挪到磁盘上),就会卡顿。
Java、C#、Go 这类自动管理内存的语言,用完丢弃的内存由垃圾回收器(GC)收集回收。有些 GC 方式会让所有线程暂停片刻。对堆(程序运行时申请使用的内存区域)整体做一次 GC 时,存活数据越多耗时越长,可达几百 ms 到几秒。ZGC 这类新一代 GC 能把停顿压到 1 ms 以下,代价是消耗更多 CPU 和内存。C++ 服务器没有 GC,但会受内存泄漏(忘记释放的内存不断累积)和内存碎片(空闲空间被切得很碎,用不了大块内存)困扰。即使有 GC,用完的对象如果还在某处被引用,同样会泄漏。
内存变慢的另一个原因是存储层次(存储设备离 CPU 有多远)。紧挨 CPU 的缓存是 1 ns,RAM 是 100 ns,从换出到磁盘的内存(swap)读回来则要 RAM 的 1,000 倍以上。可以在下面的“延迟数量级”表里,把这些差距换算成人的时间来感受一下。
内存就像厨师的操作台。食材放在手边(缓存)就快,要去冰箱(RAM)拿就慢一点,操作台堆满了只好把食材放进仓库(磁盘、swap),每取一次都要花很久。洗碗(GC)的时候,就得停下手里的菜。
Java、C# 服务器为回收垃圾对象而暂停所有线程(stop-the-world),这段时间里整个服务器都停住。
起因: 堆满,触发 GC → 结果: 暂停所有游戏线程后回收(存活数据越多,停得越久) → 画面表现: 这台服务器上的所有人同时卡住,随后快进
症状: 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
即使是 C++ 服务器,如果任务、AI、技能用 Lua 等脚本来跑,脚本引擎执行 GC 期间,对应的场景也会停住。
起因: 每个场景的脚本引擎在执行任务、AI、事件时大量创建临时对象 → 结果: 脚本引擎的 GC 一次回收太多时,这个场景的 tick 停住 → 画面表现: 只在特定场景、特定事件期间周期性地顿一下
症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发
活动期间大量创建临时对象,GC 会比平时频繁得多。
起因: 物品掉落、战斗日志、活动奖励让临时对象激增 → 结果: GC 频率翻了几倍,还没来得及丢弃的对象晋升到老年代,Full GC 也随之提前 → 画面表现: 只在活动期间周期性地顿一下
症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发
没有释放的内存一点点累积,几天后引发 GC 失控、swap 或进程被强制终止。
起因: 已下线角色的数据、事件处理函数没有释放 → 结果: 空闲内存在几天内持续减少 → 画面表现: 维护结束后正常,越往后越卡,最终服务器宕机
症状: 慢动作, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
存活数据接近堆上限时,GC 即使运行也几乎回收不到东西,于是不停地反复执行。
起因: 活动人数增加或内存泄漏,让存活数据逼近堆上限 → 结果: GC 只能回收一点点,紧接着又是 Full GC,CPU 大部分时间都花在 GC 上 → 画面表现: 全服几分钟内反复出现慢动作、卡住,最后因内存不足而终止
症状: 慢动作, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
内存不足时,OS 会把一部分内存换出到磁盘。之后每次用到这部分内存,都要等待比内存慢 1,000 倍以上的磁盘。
起因: 内存用量超过物理内存 → 结果: OS 把一部分换出到磁盘,需要时再读回来 → 画面表现: tick 耗时暴涨到数百 ms,这台服务器上的所有玩家都出现慢动作、卡住
症状: 慢动作, 卡住 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
数据分散在内存各处时,CPU 每次都要到较慢的内存里取数据并等待。
起因: 对象通过指针分散在各处,访问顺序杂乱 → 结果: CPU 缓存里没有,每次都从内存读取(慢 100 倍左右) → 画面表现: 做同样的事,tick 开销翻几倍,严重时出现慢动作
症状: 慢动作 · 主责 研发团队·服务器开发
反复分配和释放会把空闲空间切得很碎,进程占用的内存会远多于实际使用量。
起因: 多个线程长时间分配、释放大小不一的内存 → 结果: 空闲空间零散分布、无法归还给 OS,占用像泄漏一样持续增长 → 画面表现: 运行越久越会因 swap、内存不足而变慢,最后被强制终止
症状: 慢动作, 掉线 · 主责 研发团队·服务器开发
在装有两颗 CPU 的服务器上,使用挂在另一颗 CPU 上的内存,访问会变慢。
起因: 线程和内存分别位于不同的 CPU 插槽(socket) → 结果: 内存访问变慢(视硬件为 1.5~2 倍) → 画面表现: 配置相同,各进程的性能却有差异
症状: 慢动作 · 主责 运维团队·系统运维
日志、角色存盘数据、地图数据、DB 文件都在磁盘上。磁盘比内存慢几百倍(SSD)到 10 万倍(HDD),所以如果游戏服务器的设计是要等磁盘,磁盘一忙,游戏也会跟着卡住。
磁盘性能用“每秒能读写多少次”(IOPS)来衡量。老式 HDD 只有 150 次左右,SSD 有几万到几十万次。云盘的上限按付费多少确定(AWS 默认的 gp3 是 3,000 次)。部分云盘和小规格服务器在平时速度之外,还提供可以短时间跑得更快的突发积分,但忙碌时间一长,积分耗尽,速度就会突然掉下来。“每天晚上过几个小时就开始卡”的反馈就是这种表现。
关键在于谁在等。普通的文件写入由 OS 先收进内存,稍后再写到磁盘,所以通常马上就返回。问题出在要求等到“真正写入磁盘为止”时(fsync),或者 OS 能缓存在内存里的量达到上限时。这时如果游戏线程直接等待(同步),磁盘慢 100 ms,tick 也会停 100 ms。把写入交给单独的线程(异步),游戏就不会停住,但服务器突然崩溃时,还没写完的内容可能丢失(吞操作/回档)。
磁盘是仓库,IOPS 是仓库门的数量。门少了,存取货物的人就得排队。突发积分是短时间冲刺的体力,用完了就只能恢复到步行速度。
游戏线程每写一行日志都要等磁盘完成,磁盘一忙,游戏逻辑也跟着停住。
起因: 在游戏线程里直接把战斗、交易日志写入文件 → 结果: 要求确保落盘(fsync),或 OS 的写缓冲(页缓存)达到上限时,磁盘一忙,一次写入就要几十 ms → 画面表现: 日志多的战斗中顿一下
症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
要求把数据“真正”写进磁盘(确保落盘)时,视磁盘不同每次要花 0.1 ms 到几十 ms;请求一集中,队列就会变长。
起因: 定时存盘、集中下线让确保落盘的写入请求扎堆 → 结果: 磁盘队列变长 → 画面表现: 每到存盘时间就卡,下线、换线变慢
症状: 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·数据库运维
部分云盘和小规格实例有突发积分,可以短时间超过基准性能运行。繁忙时段一长、积分耗尽,速度就会突然下降。
起因: 长时间以高于基准性能的水平运行 → 结果: 突发积分耗尽,性能骤降到基准水平 → 画面表现: 每天晚上过了几个小时后开始卡
症状: 一卡一卡, 慢动作, 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维
请求超过磁盘每秒能处理的数量后,队列变长,延迟暴涨。
起因: 读写请求接近磁盘的处理能力 → 结果: 队列变长(利用率超过 90% 时通常暴涨) → 画面表现: 存盘、加载变慢,同步调用时会卡住
症状: 操作延迟, 卡住 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 运维团队·数据库运维
日志和 dump 堆积把磁盘写满后,写入会失败;没有做好应对,服务器就会崩溃。
起因: 日志、dump、临时文件堆积到 100% → 结果: 写入失败。没有错误处理就崩溃,有错误处理则存盘失败 → 画面表现: 掉线,进度回档
症状: 掉线, 吞操作/回档 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发
凌晨的备份、日志压缩、安全扫描独占磁盘时,游戏服务器的读写会被挤到后面。
起因: 定时的备份、压缩任务启动 → 结果: 占用了大部分磁盘带宽和 IOPS → 画面表现: 每天同一时间卡顿
症状: 一卡一卡, 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维
服务器第一次收到副本、地图数据请求时才从磁盘读取,那个 tick 期间所有人都会卡住。
起因: 有人第一次进入某个副本或区域 → 结果: 服务器在游戏线程里从磁盘读数据 → 画面表现: 这台服务器上的所有人短暂卡住
症状: 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
服务器崩溃时要把数 GB 内存写到磁盘,重启有时会因此推迟好几分钟。
起因: 服务器崩溃,把全部内存写成文件 → 结果: 写入数 GB 期间无法重启 → 画面表现: 服务器崩溃导致掉线,之后很长时间连不上
症状: 连不上/无限加载 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
HDD 的磁头必须在盘片上移动(寻道,seek),读写分散的数据每次要花将近 10 ms。
起因: 老旧服务器或低价存储使用 HDD → 结果: 每次随机读写约 10 ms → 画面表现: 存盘、加载普遍变慢
症状: 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发
角色、物品、货币、交易记录这类绝对不能丢的东西都存在这里。DB 一慢,战斗还正常,物品却迟迟不到账,交易失败,登录一直完成不了。如果游戏服务器的设计是要等 DB,整个野外场景都会卡住。
游戏服务器会预先和 DB 建好几个连接(连接池),轮流使用。一条查询(发给 DB 的请求)耗时太长,这个连接就一直被占用;池里的连接都被占用时,其余请求只能在队列里等。查询变慢的常见原因有两个:没有索引(相当于书的目录),只能读整张表(全表扫描);或者多个请求同时要改同一行,在等锁。
DB 为了可靠、也为了承接大量请求,会用到多种机制:分担读请求的从库、故障时切过去的备用库、定期把变更集中写入磁盘的检查点。检查点集中时会短暂变慢。从库延迟时会出现“刚买的物品看不到”;在复制延迟的状态下切换到备用库,则会出现“一上线发现回到了不久前的状态”这类吞操作/回档症状。如果游戏服务器几分钟才保存一次角色,服务器崩溃时就会变成“回到了 10 分钟前”。
DB 就像银行柜台。柜台数量(连接池)是固定的,一个业务要翻遍整本账(全表扫描)时,后面的业务都得等。所有人都要开同一个保险柜(热点行)时,只能一个一个进去。
没有索引时,要找到符合条件的行,就得把整张表读一遍(全表扫描)。
起因: 发布新功能时加了一个没有索引的条件查询 → 结果: 扫描全部几百万行,一条查询要几百 ms 到几秒 → 画面表现: 邮箱、交易记录加载慢,连接被占住,其他请求也跟着等
症状: 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
所有人都要修改同一行(公会仓库、拍卖行热门物品、全服计数器)时,同一时刻只有一个人能拿到锁。
起因: 活动、热门物品让修改集中到同一行 → 结果: 请求一直等到拿到锁 → 画面表现: 交易失败、“请稍后再试”、超时
症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
两个事务(作为一个整体处理的一组 DB 操作)互相等待对方锁住的行时,DB 会强制取消其中一个。
起因: 交易 A 按物品→货币的顺序加锁,B 按货币→物品的顺序加锁 → 结果: DB 检测到死锁,回滚其中一方 → 画面表现: 交易、制作偶尔失败,物品回退
症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
与 DB 建立的连接数是固定的,慢查询占住连接后,其余请求只能等待。
起因: 慢查询或请求激增,所有连接都在使用中 → 结果: 新请求要等到有空闲连接 → 画面表现: 登录时无限加载,存盘变慢,超时
症状: 连不上/无限加载, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
写入走主库、读取走从库时,如果从库同步落后,刚写入的内容就读不到。
起因: 写入集中涌向主库,从库落后几秒 → 结果: 刚保存的内容,去从库读时还没有 → 画面表现: 刚买的物品看不到,交易所价格还是旧值,出现重复发放 bug
症状: 吞操作/回档 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
DB 定期把内存中的变更集中写入磁盘,那一刻查询会变慢。
起因: 变更累积后定期写入磁盘 → 结果: 那一刻磁盘变忙,查询变慢 → 画面表现: 存盘、加载周期性变慢
症状: 操作延迟, 一卡一卡 · 主责 运维团队·数据库运维
DB 重启后内存缓存是空的,一段时间内所有查询都要从磁盘读取。
起因: 维护时重启 DB → 结果: 常用数据不在内存里,只能从磁盘读 → 画面表现: 维护结束后一段时间内登录、加载很慢
症状: 连不上/无限加载, 操作延迟 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
加载一个角色要分别查询几十次时,数万人同时登录就会变成几百万条查询。
起因: 加载角色时分别查询物品、技能、任务 → 结果: 维护结束后大量玩家同时登录,查询激增 → 画面表现: 登录时无限加载,正在游戏的玩家存盘也被拖慢
症状: 连不上/无限加载, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
在运营期间跑排行榜统计、批量发邮件、清理旧数据,会占用锁和磁盘。
起因: 在运营时段执行大批量任务 → 结果: 大范围加锁,占用磁盘和 CPU → 画面表现: 特定时段交易、存盘失败,加载变慢
症状: 操作延迟, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
主库宕机、切换到备库期间无法写入,最后一部分没来得及复制的数据可能会丢失。
起因: 主库故障,备库被提升为主库 → 结果: 切换期间几秒到几分钟无法写入;如果是异步复制,未复制的数据可能丢失 → 画面表现: 短时间内所有存盘失败,物品、经验回档
症状: 吞操作/回档, 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
为减轻负载,几分钟才存一次盘,期间服务器一旦崩溃,进度就会丢失。
起因: 角色状态每几分钟保存一次 → 结果: 其间服务器崩溃或发生故障 → 画面表现: 重新登录后回到几分钟前的状态(回档)
症状: 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
热门数据的缓存同时过期时,几千个请求会一下子涌向 DB。
起因: 存在 Redis 等处的热门数据同时过期 → 结果: 想重新生成同一份数据的请求一起涌向 DB → 画面表现: DB 过载,多个功能接连变慢或卡住
症状: 操作延迟, 卡住, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
一个事务长时间不结束,就会一直持有锁,DB 也没法清理(purge)旧版本数据,整体逐渐变慢。
起因: 开着事务去等其他服务器的响应,或运营期间在主库上跑长时间的统计查询 → 结果: 持有的锁不释放,待清理的旧版本数据不断堆积 → 画面表现: 用到这些行的功能超时,几个小时内存盘、查询普遍变慢
症状: 操作延迟, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
Redis 一次只处理一条命令,一条慢命令就会挡住后面所有请求。
起因: 运营中用 KEYS 全量搜索,整体读取或删除含几百万元素的排行榜、列表 → 结果: 这条命令结束前,其他所有请求都在等待(几十 ms 到几秒) → 画面表现: 用到会话、排行榜、缓存的功能同时顿一下,登录变慢
症状: 卡住, 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
代码没变,DB 却换了处理同一条查询的方式(执行计划),昨天 2 ms 的查询今天就变成几百 ms。
起因: 统计信息自动更新、DB 重启、数据分布变化,让 DB 重新制定执行计划 → 结果: 选中了不走索引的计划,同一条查询慢了几十到几百倍,连接被占住 → 画面表现: 没有任何发布,某个功能的加载却突然变慢,其他请求也跟着等待
症状: 操作延迟, 连不上/无限加载 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
在服务运行中给表加列或加索引,仅仅因为一个短暂需要的锁,使用这张表的所有请求都可能被迫等待。
起因: 通过热修复给线上表加列、加索引 → 结果: 表结构变更在等之前开启的长事务,后来的所有请求又在等这个表结构变更 → 画面表现: 使用这张表的功能(背包、邮件等)整体卡住并超时
症状: 操作延迟, 吞操作/回档, 连不上/无限加载 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发
现在的 MMO 通常由登录、网关、野外、副本、聊天、组队、拍卖行、缓存、DB 等服务器相互调用来运转。一处出故障就会蔓延到相连的地方,发布、扩容、维护这类运维操作也会造成卡顿。
把服务器拆开,可以防止一处故障蔓延到全局,但代价是会产生调用链(服务器依次调用服务器的链路)。比如游戏服务器调用拍卖行服务器,拍卖行服务器再调用缓存和 DB。链路末端的服务器一变慢,前面的服务器就会一边等响应一边持续占着线程和连接,最终连看似无关的功能也停住。这叫级联故障,靠超时和熔断器(暂时切断持续失败的调用的机制)来阻止蔓延。
运维操作也是卡顿的原因。发布更新时的重启、人多时自动扩容所需的几分钟、切换场景时把角色迁到另一台服务器的过程、机器人(bot)和宏造成的隐形负载,在玩家看来都是“卡”。
服务器架构就像多个部门互相审批的公司。审批链末端的某个部门(DB)一慢,前面的部门都得拿着文件排队,最后整个公司的工作都停下来。超时是“超过 10 分钟没回复就先驳回”的规则,熔断器是“连续驳回时,一段时间内不再把文件送到那个部门,直接退回”的规则。
在客户端和游戏服务器之间加一层中间服务器,每经过一次就多一段处理时间,这台服务器也会成为单点故障。
起因: 客户端 ↔ 网关 ↔ 游戏服务器的结构 → 结果: 中间服务器增加处理和排队时间,过载时所有人都受影响 → 画面表现: 所有人 ping 上升,网关故障时经过它的玩家全部掉线
症状: 操作延迟, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发
进入其他区域或副本时,要把角色数据交给另一台服务器,这个过程中会出现延迟和失败。
起因: 进入副本、跨大陆移动,负责的服务器随之更换 → 结果: 存盘 → 传递 → 加载;目标服务器拥挤或没有空闲副本实例时要排队 → 画面表现: 加载时间长,进入失败,移动途中掉线
症状: 连不上/无限加载, 卡住, 掉线, 拉回 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
一个服务变慢,调用它的服务器都会因等待响应而被占住,连不相关的功能也会停摆。
起因: DB、认证等某一个服务变慢 → 结果: 调用方服务器的线程和连接都在等待响应而被占住,失败请求的重试又增加负载 → 画面表现: 看起来无关的功能也全部变慢或停摆
症状: 卡住, 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维
聊天、组队、拍卖行这类与游戏服务器分开运行的服务器出故障时,只有对应的功能不能用。
起因: 功能专用服务器变慢或挂掉 → 结果: 只有该功能的请求没有响应 → 画面表现: 聊天发不出去,组队邀请没反应,交易行无限加载(战斗正常)
症状: 吞操作/回档, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
为更新而重启服务器时,如果不迁移连接,这台服务器上的玩家都会掉线,关机前的存盘和随后的重连也会一下子挤在一起。
起因: 发布热修复,按顺序重启服务器 → 结果: 不把连接迁到其他服务器就直接关闭,这台服务器上所有玩家的存盘请求同时涌向 DB → 画面表现: 没有预告的掉线,大量重连
症状: 掉线, 连不上/无限加载, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
人一多就会自动增加服务器,但准备要几分钟,这段时间现有服务器处于过载状态。
起因: 活动开始,连接数激增 → 结果: 新服务器从启动到就绪要几分钟 → 画面表现: 活动开始后的几分钟内出现慢动作、连不上
症状: 慢动作, 连不上/无限加载 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
出故障时日志会激增,同步发送日志的服务器会被日志拖得更慢。
起因: 出错时日志、指标的发送量激增 → 结果: 日志采集器处理不过来,同步发送的服务器只能等待 → 画面表现: 故障时出现的一卡一卡、卡住,因日志而更加严重
症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
每台服务器的时钟略有差异时,冷却、buff、活动开始的判定在不同服务器上就会对不上。
起因: 时间同步停掉的服务器,时钟与其他服务器相差几百 ms 到几秒 → 结果: 在服务器之间传递 buff 结束时刻这类绝对时间,判定就会对不上 → 画面表现: 一移动 buff 就消失,或冷却又从头开始
症状: 吞操作/回档 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发
机器人发请求的频率远高于真人,会吃掉服务器的处理能力。
起因: 大量不停重复打怪、移动、交易的机器人登录 → 结果: 服务器处理量和 DB 负载增加 → 画面表现: 特定练级点或全服变慢(慢动作、操作延迟)
症状: 慢动作, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维
平台登录、支付、实名认证这类外部服务变慢或停摆时,流程会卡在那一步。
起因: 外部认证、支付服务故障或延迟 → 结果: 在该步骤等待响应 → 画面表现: 无法登录,支付失败。已经在游戏中的人不受影响
症状: 连不上/无限加载, 吞操作/回档 · 主责 外部·外部 · 配合 研发团队·服务器开发
没有分到近的区域(region),被分到远区域的服务器,即使线路正常,也只有这个玩家 ping 一直偏高。
起因: GeoIP 数据错误,VPN,按队友平均 ping 给整个队伍分配,人数不够时扩大到远区域的规则,按 DNS 解析器位置分配 → 结果: 明明有近的区域,却连到了海对面区域的服务器 → 画面表现: 在多个区域部署服务器的游戏中,只有我(或只有我们队伍)ping 一直偏高,出现操作延迟、拉回、吞技能
症状: 操作延迟, 拉回, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维, 外部·外部
登录、API、补丁服务器的证书过期或缺少中间证书时,从那一刻起新建连接的客户端 TLS 连接都会失败。
起因: 证书有效期已过,服务器发送时漏掉中间证书,或玩家设备的日期时间不对 → 结果: 客户端证书校验失败,断开 TLS 连接 → 画面表现: 登录、补丁阶段连不上/无限加载,只有商城这类 HTTPS 功能失败。已经在线的人大多不受影响
症状: 连不上/无限加载, 吞操作/回档 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·客户端开发
上线、维护结束后连接集中涌入时,登录排队达到上限,开始拒绝新的排队;正在排队的玩家只要短暂断开一下就会丢掉位置,回到队尾。
起因: 想登录的人多于登录服务器一次能接纳的人数,于是设置排队;队伍太长时,为了保护服务器而拒绝新的排队 → 结果: 队伍越长,等待时间越久,这期间 Wi-Fi、移动网络只要短暂断一下,就会丢掉排队位置 → 画面表现: 连不上/无限加载,排队中报错并退出游戏,又要从队尾重新排
症状: 连不上/无限加载, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维
收到卡顿反馈时,只要选出“谁、何时、什么表现”这三项,就会从本白皮书收录的原因中,按得分顺序列出最匹配的候选。虽然不能据此确诊,但足以决定先问哪个团队。
收到反馈或告警时,按范围 → 时间点 → 层级的顺序缩小范围。异常集中在哪里,对确定负责方影响最大;和什么同时发生,可以缩小原因范围;哪一层的指标异常,用来最终确认。结合每张原因卡片上的“监控图上”和“确认方法”,就能按监控图形态挑出候选,并直接找到查看位置。
异常集中在哪里
与什么同时发生
哪一层的指标异常
| 查看什么 | 看到这种情况 | 先找谁 |
|---|---|---|
| 服务器 socket 接收队列(Recv-Q) | 服务器进程没能及时读取而堆积 | 研发团队服务器(tick 停顿、GC、锁) |
| 每条连接的重传、RTT | 只有部分连接,集中在特定 ASN | 运维团队网络 外部运营商/玩家线路 |
| 一台主机上的所有连接 | 运维团队服务器/OS(网卡、内核) | |
| 版本更新后服务器整体的重传、带宽上升 | 数据包大小、频率变了 | 研发团队服务器 运维团队网络(MTU、上限) |
| CPU 窃取时间、限流、softirq、网卡丢弃 | 增加 | 运维团队服务器/OS |
| 只有一个线程 100%、运行队列延迟、GC 停顿 | 增加 | 研发团队服务器 |
| DB 延迟上升,查询数不变 | IOPS、锁、其他作业 | 运维团队DB 服务器 |
| DB 查询数、查询形态在版本更新后改变 | N+1、新查询 | 研发团队服务器 |
| 海外节点的拨测(RTT、丢包) | 差 | 运维团队网络 外部运营商 |
| 拨测正常,只有玩家数据差 | 玩家环境或客户端 | 外部玩家环境 研发团队客户端 |
| 断线原因分布 | 心跳超时↑ / RST↑ / 服务器主动断开↑ | NAT/路径 / 设备 / 服务器 |
| 精确的周期(整点、N 分钟) | 计划任务、备份、GC、活动 | 设定这个计划的一方 |
只要知道监控图是什么形态,候选就能大大减少。下面 13 种形态,每种都汇总了会产生这种形态的原因。原因卡片上的小图也是同样的形态。实线是主要查看的指标,虚线是一起看的指标(人数、等待、错误等),浅色虚线是平时水平。
平时较低,每隔固定时间(几秒、几分钟、整点)冒一次尖峰。
客户端垃圾回收, 游戏安全模块(反作弊)扫描, Wi-Fi 后台扫描, 定时任务, 定时器集中触发, 服务器 GC 全局停顿, 脚本引擎 GC 停顿, fsync 风暴, 备份/压缩/扫描任务, 检查点/日志刷盘, 大型批处理任务, 缓存雪崩
没有固定间隔,不规则地冲高后很快回落。
帧耗时尖峰, 过度外推(航位推测), 客户端预测不一致, 固定时间步长追赶失控, 后台进程占用 CPU, 客户端内存不足/swap, 网卡省电/驱动问题, Wi-Fi 干扰/信号弱, 5G↔4G 频繁切换(5G 覆盖边缘), 线路质量差, 环形缓冲区不足, 虚拟化开销/邻居干扰, 内核 socket 缓冲区不足, CPU 窃取时间(虚拟机), 内存回收/规整导致的停顿, 系统时钟跳变(NTP step), 慢客户端导致的阻塞发送, 可靠 UDP 重传配置, RST 强制关闭导致最后的数据丢失, 游戏线程中的同步调用, 同步写日志, 服务器端懒加载, DB 死锁, Redis 慢命令, 日志/监控过载, 帧同步中等待最慢的玩家, 回滚网络代码预测失败, 没有时间戳、一到就播放, 过于严格的服务器校验, 指令同步的路径计算不一致, 视野注册顺序错乱, 基准快照丢失, 离开通知丢失(幽灵实体), 实体 ID 重用混淆, 延迟飙升导致的虚假重传
以版本更新、配置变更、路由变更等某个时间点为界,上一个台阶后就一直停在那里。
海底光缆/国际线路故障, BGP 路由变更/收敛, DDoS 防护引流/误判, OS/内核/驱动/固件更新后的性能变化, 版本更新导致流量特征变化, 缺少索引的查询, 执行计划变化导致查询变慢, 线上表结构变更(DDL)锁, 依赖外部服务, 路径变更/ECMP 故障路径
在几小时到几天内一点点往上走,随运行时长增长。
时钟同步误差, float 时间精度丢失, 客户端内存泄漏, 省电模式/发热降频, 内存泄漏, swap, 内存碎片, 磁盘写满, 长事务, 服务器之间的时钟差
慢慢升高,到重启或清理的瞬间骤降,如此反复,呈锯齿状。
每天只在固定时段隆起,比如晚高峰。
同时在线人数或聚在一处的人数一增加,它就以更陡的斜率跟着上涨。
大规模同屏渲染负载, 主线程数据包处理瓶颈, 接收缓冲区溢出, 同一设备上其他应用占用带宽, 缓冲区膨胀(路由器队列), 交换机微突发, 线程过多与上下文切换, 容器 CPU 限流(CFS 配额), UDP 数据包的 IP 分片, 阻塞 I/O 模型, Tick 超出预算, 视野(AOI)计算激增(N²), 广播激增, 单线程区域过载(热点), 锁竞争, 寻路计算激增, 序列化/压缩开销, 战斗集中在单一目标(世界 BOSS), 内存分配暴增, 热点行锁竞争, 复制延迟, 经由网关/代理, 场景切换(服务器间迁移), 按连接分配的发送预算/优先级, 突发发送导致浅缓冲区溢出, 双工不匹配
吞吐量或连接数到某个值后再也上不去,从那时起排队和报错开始增加。
显存(VRAM)不足, 路由器性能不足/过热, 运营商限速/流量管理, DDoS 导致共享线路饱和, 防火墙会话表耗尽, 云 NAT 网关连接/端口上限, 数据中心线路饱和, 网卡中断集中在单个核心, 超出云 PPS 上限, 网卡带宽饱和, 文件描述符上限, 服务器 conntrack 表耗尽, 服务器间连接的临时端口耗尽, 消息队列积压, 线程池耗尽, GC 颠簸(堆余量不足), 云盘突发积分耗尽, IOPS 上限/队列饱和, 连接池耗尽, 级联故障, 登录排队上限/重连保留不足, 内存/显存不足导致流式加载失败, 流量监管丢弃超额流量, 接收端服务器主机丢包, 防火墙/连接跟踪丢包, 中间设备超出处理上限(防火墙/IPS/DDoS 防护)
不跳变,一直停在较高的值。多由距离、路由、设计等结构性因素造成。
没有插值缓冲或缓冲过短, 垂直同步(V-Sync)与渲染队列, 定时器精度, 显示器/输入设备/帧生成延迟, 传播延迟(物理距离), 路由绕行, 中断合并过度, GRO/LRO 合并等待延迟, 服务器电源管理(C-state/调频)导致延迟跳变, Nagle 算法 + 延迟 ACK, 缓存未命中, HDD 寻道延迟, 收到服务器响应才播放表现(请求-响应方式), 顺序往返多的协议(chatty), 技能不支持预输入, 客户端权威, 双重 tick 等待, 快照发送频率低, 乱序导致的虚假快速重传, RTO 设置与环境不匹配, 中间设备剥离 TCP 选项
大部分正常,只有特定玩家、地区、运营商或设备单独偏高。
存储设备过慢导致资源流式加载滞后, 客户端崩溃, 安全软件检查数据包, 游戏内覆盖层干扰, RRC 状态切换延迟(移动网络无线省电), 手机信号弱/信号盲区, 公共 Wi-Fi/公司网络限制, 卫星互联网(低轨、静止轨道), ECMP 单条路径故障, 国家/运营商级 UDP 限制与包检测, DNS 故障/延迟, 经由 VPN/游戏加速器, 负载均衡倾斜/健康检查误判, 线缆不良/端口错误, MTU 不匹配(只有大包丢失), 慢客户端(slow consumer)处理策略, keepalive 默认 2 小时, 空闲后的慢启动, SO_REUSEPORT 分配不均, NUMA 远端内存, 脚本/机器人过多, 匹配/区域分配错误, 被 ping 吃掉的短判定窗口, 没有延迟补偿的判定, 延迟补偿过度, 主机(房主)结构, 预表现后被服务器拒绝, 网络差的玩家在别人画面上快进移动, 到达即处理的服务器造成的快进, 每个玩家的输入缓冲大小, 一个网络差的队友与 BOSS 机制, 怪物控制权在慢的客户端上, 特定角色数据过于庞大, 分线/副本/位面不同, 加载中到达的出现通知被丢弃, 固定 UDP 端口冲突, 按 IP/设备区分会话的 bug, 多开限制, 缓存/资源文件并发访问冲突, 显示选项不同, 客户端版本/数据不一致, 时钟估算误差导致实体被搁置, 无线链路丢包, 物理层错误(线缆/光模块/连接器不良), MTU 黑洞(只有大包反复丢失), ACK 延迟或丢失(上行饱和)
有一段时间收到的量为 0,随后一下子涌进来。
窗口最小化/失去焦点时处理受限, 基站切换(移动中), 云宿主机维护/热迁移, 网卡驱动/固件问题, TCP 队头阻塞, TCP RTO 与指数退避, 后台窗口处理受限, thin stream 恢复慢, 零窗口(看起来像重传的停顿)
在线连接数骤降,或者断线数瞬间飙升。
移动应用切到后台, Wi-Fi ↔ 4G/5G 切换, NAT 映射过期, 运营商共享 IP(CGNAT), 负载均衡器空闲超时, 云安全组连接跟踪过期, 网络设备故障切换(主备切换), OOM Killer, Windows UDP socket 的 WSAECONNRESET 错误, 死锁, 服务器崩溃, 死循环/逻辑失控, 写入 core dump, DB 故障切换, 存盘间隔过长导致进度丢失, 辅助服务器故障, 发布/重启, TLS 证书过期/配置错误, 连接中途 NAT/负载均衡器映射过期
开服或活动开始后猛冲高位,再慢慢回落。
主线程同步加载/Shader 编译, 连接队列(backlog)溢出, 进入密集区域时实体生成激增, 冷缓存(刚重启时), 登录激增与 N+1 查询, 弹性伸缩延迟, 刚进入时集中到达的出现信息丢失, 连接请求(SYN)重传
这里统计了每个原因最容易的确认手段。运维工具指用操作系统、网络、云、数据库工具和运行时启动参数(GC 日志等)就能确认,不用改游戏代码。游戏日志与指标是 tick 耗时、断线原因这类必须由游戏记录下来才看得到的东西。这一栏数量越多的层,越有理由请研发团队补充埋点。
平均值会掩盖尖峰。每秒 20 tick 的服务器,即使只有 1% 的 tick 变慢,也会大约每 5 秒让所有人顿一下,但平均 tick 耗时几乎不变。所以要同时看百分位数。p50(中位数)表示一半样本比它快,p99 是 100 次中最慢那 1 次附近的值。玩家记忆中的“卡”通常在 p99 这一端。
聚合粒度也会掩盖尖峰。在 1 分钟平均的监控图上,1 秒的停顿会被稀释到 1/60。查找停顿时,要同时看同一张图的最大值或 p99,或者更细的粒度。
抖动是数据包到达间隔的波动程度。即使平均 ping 很低,抖动大时插值缓冲也会被耗空,出现一卡一卡、瞬移。
| 测量方法 | 测的是什么 | 注意事项 |
|---|---|---|
ping (ICMP) | 到设备的往返时间 | 路由器、服务器可能延后处理 ICMP 回应或限制数量,结果可能和游戏数据包不同。被屏蔽时完全没有回应 |
mtr·traceroute | 每一跳的延迟、丢包 | 只有中间某台设备丢包率高、后面各跳都正常时,很可能只是这台设备限制了 ICMP 回应。只有一直延续到终点的丢包才是真实丢包 |
TCP RTT(ss -ti 的 rtt) | 内核为每条连接测得的往返时间 | 来自真实的游戏连接,最可信。可在服务器侧按玩家查看 |
| 游戏内 ping | 游戏用自己的消息测得的往返时间 | 在游戏循环里测量时会混入帧、tick 等待。即使线路正常,服务器或电脑忙时也会升高 |
ss -ti 或 eBPF 工具采集每条连接的 RTT、重传,按 ASN 查看。pidstat -t)、运行队列延迟、只靠启动参数就能开启的 GC 日志。mtr。这里汇总了两种常见情况的排查顺序,以及原开发商、运营方自己公开的真实故障案例。每个步骤和案例都链接到相关的原因卡片。
某次版本更新或发布之后卡顿反馈变多时使用。“这次更新以后就不对劲”的反馈集中出现,或监控图从某一时刻起台阶式上升并一直停在高位时,都按这个流程排查。
新开服务国家/地区,或新增区域、数据中心时使用。上线前的检查,以及判断“国内没问题,只有新国家的玩家卡”这类反馈时,都可以用这个流程。
只收录了游戏公司和基础设施公司自己公开的故障复盘(postmortem)。摘要只写原文披露的内容,详细经过请看原文。
据 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 搜索目标的开销。放慢游戏时间的设计无法消除过载,但能让所有人以同样的速度变慢,避免只有部分操作被无限期推迟。 原文
这是 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、选择服务器位置。运营商侧的路由策略需要与外部(运营商)协商。这个案例也说明,仅仅把服务器移到靠近用户分布中心的位置,效果就很明显。 原文
2020 年 2 月下旬,League of Legends 的 EUW、EUNE、BR 服务器多次发生故障,新开局数大幅减少。匹配、游戏服务器等后端服务的状态全部正常,但几乎没有流量进来。为避免在可能不稳定的集群上开放锦标赛模式(Clash),Riot 把日程推迟了一周。复盘文章没有写明每次故障持续了多久。 三个因素叠加在一起。发往某个服务的请求构造有误,在特定情况下持续失败、不断重试,导致请求量暴增。容器系统与 OS 版本之间存在已知的兼容问题,OS 内部内存一直在泄漏;升级只在 Riot 全部容器环境的约 60% 上完成,欧洲和拉丁美洲集群还在升级中。负责接收互联网流量、过滤后转发给后端的边缘容器,在同一个分片(服务器组)内会分散部署,但不同分片之间没有这种限制,每次故障时都至少有三个分片的边缘容器挤在同一台主机上。暴增的重试压到这台主机上,内存泄漏又让它停摆。
后端服务都回答“状态正常,但没有流量进来”时,就去看它们前面的一层(边缘、网关、负载均衡器)。确认信号是各主机入站连接数是否倾斜,以及特定请求的失败率、重试率。主责是研发团队(服务器:错误的请求和重试方式),容器调度规则、OS 升级、倾斜告警由运维团队(服务器/OS)负责。Riot 修复了请求代码,改成不会让重试激增的方式,并在实现跨分片分散部署之前设置了倾斜告警。 原文
2021 年 1 月 22 日,League of Legends EUW 服务器有 5 个多小时无法正常运行。已登录玩家数和游戏中玩家数两项指标同时中断;两次重启之间,登录人数在增加,却几乎没有对局开始。 负责非关键功能的一个 DB,其主服务器发生硬件故障,而这个 DB 没有配置自动切换到备用服务器。每个 DB 的连接池是分开的,但所有连接池共用同一个线程池;发往故障 DB 的任务迟迟不结束、一直占着线程,整个系统可用的线程被耗光。告警铺天盖地,团队先怀疑最近遭遇过的恶意网络攻击和其他地区的硬件作业,故障 DB 的告警约 1 小时后才被注意到。所有系统都跑在同一个 JVM 里,重启后在重连负载下,GC 每次让进程停顿几秒,指标采集也因此出现大段空白。登录排队也没有遵守设定的上限,涌入量忽高忽低。
即使是被认为不重要的一个辅助 DB,也可能通过线程池这类共享资源让整个系统停摆。确认信号是各 DB 排队中的请求数、线程池使用率,以及开局数与登录数之比明显偏低。负责方是研发团队(服务器:线程池隔离、超时)和运维团队(DB:自动切换)。告警铺天盖地时,人很容易先怀疑最近遇到过的问题(攻击等),所以要按判定顺序(范围 → 时间点 → 层级)逐一排除。重启后还要看登录排队是否按设定限制了涌入量。 原文
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%。 原文
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 服务器的扩容由运维团队配合。把重连保留时间留得宽裕一些,可以减少玩家线路的短暂中断演变成丢失排队位置的情况。 原文
很多游戏把网站、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),并调整了优先级,让一个节点无法把其他节点的流量吸走。 原文
很多游戏通过 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,或直接从源站获取。 原文
这类基础设施故障同样可能发生在游戏公司的自有网络和 DNS 上。2021 年 10 月 4 日,Facebook(现 Meta)的服务在全球范围无法访问。连接各数据中心的骨干网全部中断,互联网上也找不到 Facebook 的 DNS 服务器。复盘文章没有写明故障持续了多久。 例行维护中,为评估全球骨干网容量而下发的一条命令,意外断开了骨干网的所有连接;本该拦下这类命令的审计工具因为 bug 没能拦住。小型节点的 DNS 服务器按设计在无法与数据中心通信时,会判定自身异常并撤回 BGP 通告,于是 DNS 服务器虽然还在运行,互联网上却访问不到。平时的访问通道和带外(out-of-band)访问全部中断,内部工具也失去了 DNS,只能派工程师亲赴数据中心,安全流程又耽误了更多时间。恢复时,各数据中心的用电量都下降了几十 MW,团队判断一下子全部恢复可能危及从电力设施到缓存的各个环节,于是逐步提升负载。
所有地区、所有运营商同时出现连不上/无限加载时,先看 DNS 和 BGP 路由,再看游戏服务器。通过外部 DNS 查询和公开的 BGP 路由信息,在公司外部也能确认。主责是运维团队(网络)。要事先检查故障时使用的带外访问通道和内部工具是否依赖同一套 DNS 和网络;恢复时逐步提升负载,避免重连一下子涌入。 原文
很多游戏把服务器、登录和数据放在公有云上,这类基础设施故障会连带影响游戏。2021 年 12 月 7 日上午 7:30(太平洋标准时间),北弗吉尼亚区域(us-east-1)的内部网络发生拥塞。从 7:33 起,EC2 API 错误和延迟增加,难以启动新实例(实例启动在下午 2:40 恢复),随后又出现控制台登录失败、无法修改 Route 53 配置、CloudWatch 指标延迟及部分丢失。网络设备在下午 2:22 完全恢复。已在运行的 EC2 实例和现有 DNS 应答没有受到影响。 一项为主网络上某个服务扩容的自动化任务,在内部网络的大量客户端上引发了意料之外的行为,连接尝试暴增。连接内部网络与主网络的设备不堪重负,通信出现延迟,延迟又进一步增加了连接尝试和重试,拥塞持续不退。客户端本来有在这种拥塞时拉长请求间隔的退避机制,但因为一个潜在缺陷没能正常工作。内部监控也依赖同一网络,AWS 的运维人员只能在没有实时指标的情况下靠日志应对。
重试如果不能拉长间隔,短暂的拥塞就会变成几个小时的故障。从游戏侧看,已在运行的游戏服务器即使正常,新服务器扩容(弹性伸缩)、依赖云 API 的登录、匹配、支付,以及监控都可能一起受阻。确认信号是云厂商的状态页、云 API 错误率和实例启动失败。主责是外部(云厂商);研发团队要给所有重试加上带随机间隔的指数退避和次数上限,运维团队要准备好扩容受阻时也能撑住的富余容量,以及其他区域的备选方案。 原文
这是一起公共 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 运营方、运营商);如果研发团队(客户端)把域名解析失败和其他错误区分开来提示,客服就能当场判定。 原文
很多游戏把服务器、登录和数据放在公有云上,这类基础设施故障会连带影响游戏。从 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 错误率、实例启动失败,以及负载均衡器的健康目标数。主责是外部(云厂商);运维团队要限制因健康检查失败而同时被摘除的服务器数量,并准备其他区域的备选方案。 原文
研发团队和运维团队查原因时,最花时间的是弄清“何时、何地、谁”。填好下面的项目,就能在日志和监控图里直接找到那个时刻。
这些是和研发团队、运维团队交流时常出现的词。可以在搜索框里输入中文或英文试试。
这里是本白皮书中数值、默认值和行为说明的依据。只收录了权威资料:标准文档(RFC),内核与操作系统文档,云、引擎和数据库的官方文档,演讲与论文等。原因卡片和各章末尾的“出处”也链接到同样的资料。版本更新后默认值也可能变化,实际应用前请查阅所用版本的文档。
共 616 条资料,来自 83 家发布方。完整列表见纯文本版的参考文献。