游戏卡顿白皮书
简体中文

游戏卡顿
白皮书

本白皮书从自己的游戏画面一直到服务器的数据库,分 13 层讲解画面一卡一卡、角色瞬移、掉线的原因。每个原因都写明了确认方法和负责团队,还可以在实验中调整条件亲自验证。内容以 MMO 案例为主,但大部分与游戏类型无关,适用于各类网络游戏。

00入门

造成卡顿的四个因素

原因有一百多个,但造成卡顿的因素大致可以归为四类:数据包来得晚、时快时慢、根本没到,或者某个环节停止了计算。游戏会用各种技术掩盖这些因素,掩盖失败时留下的痕迹,就是我们看到的卡顿的“样子”。

在 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、缓冲区膨胀、线路拥塞等)和这里介绍的原因相同。

01全局地图

数据包的传输路径:从输入到服务器数据库

按下技能键后,这个信号会经过自己的电脑、家里的网络、运营商、数据中心,最后到达服务器。在服务器内部经过多个层处理的结果,再反向经过同样这些层,绘制到画面上。一共 13 层,任何一层堵住都会卡顿。点击下方地图中的某一层,即可跳转到对应章节。

02动手试试

卡顿实验室

这是一个由一台服务器、一条线路、一台电脑组成的小型模拟。逐个破坏条件,看看一卡一卡、瞬移、拉回、快进、慢动作、操作延迟、卡住、掉线分别是怎么产生的。数据包时间线用线条表示每个包何时发出、何时到达。线越斜,说明用时越长;× 表示丢失的包。

03按看到的现象查找

症状词典

玩家通常只会说“卡了”,但卡顿的表现形式能透露不少原因。每个症状旁的小图,是画面中角色走过的轨迹。点重叠表示停住了,点拉开表示变快了或跳过去了。

一卡一卡

动作不流畅,反复短暂停住又继续动。 ping 值正常,多半是自己电脑的帧问题(客户端、操作系统);ping 忽高忽低,多半是 Wi-Fi 或线路的抖动。不过游戏内显示的 ping 通常是在每帧运行的游戏循环里测的,帧耗时一跳,ping 数字也可能跟着跳。

瞬移

角色没有移动过程,一下子出现在很远的位置。 通常说明数据包中断了一段时间。排查丢包、线路短暂中断、服务器停顿、外推失败。别人都正常、只有一个人在跳,先怀疑这个人的线路。

拉回

自己的角色往前走着,又被拽回刚走过的位置。 自己画面上的预测和服务器判定对不上了。可能是自己的输入没到服务器(丢包),可能是服务器的移动校验把这段移动砍掉了,也可能是双方的移动计算结果不同。

快进

停住的画面恢复后,积压的移动、打击和伤害一下子快速放完。 数据包在某处积压,然后一次性放出来了。典型原因有 TCP 等待重传、服务器追赶进度、客户端处理积压。

慢动作

所有东西都动得很慢,技能施放和怪物移动像被拉长了一样。视服务器设计而定,也可能速度不变,表现为一卡一卡或瞬移。 服务器没能按时跑完 tick。线路没问题,所以在游戏外测的 ping 不变;游戏内的 ping 如果包含服务器处理的排队时间,可能会略有上升。排查人数暴增、视野计算、广播、内存不足。

操作延迟

按下之后要过一会儿才看到结果。画面本身可能很流畅。 往返时间(ping)长,或者某处的队列在堆积。排查距离、路由器队列、Nagle(把小数据包攒起来再发的 TCP 功能)、服务器队列。ping 很低却总是反应慢,就看自己电脑这边(垂直同步、低 FPS 等),或者每个操作都要等服务器确认的设计(见“同步方式”一章)。

卡住

画面里的一切短暂停住(0.5 秒到数秒),然后又动起来。 服务器整个停住了(GC、死锁、同步调用),或者线路短暂中断,或者自己的电脑停住了。

吞操作/回档

明明做了的操作像没发生过,或者结果过了好一阵又被推翻。 要么请求丢了(丢包、队列溢出),要么服务器的判定和自己画面上的不一样(判定时间点不同、预表现后被拒绝),要么保存时失败了(DB 锁、DB 故障、服务器崩溃)。

掉线

游戏中途连接断开,退回登录界面或弹出重连窗口。 超时时间内一个数据包都没收到。排查线路长时间中断、空闲超时、服务器崩溃或重启,以及服务器或自己的电脑停住的时间超过超时时间(长时间加载)。如果没有任何提示、游戏直接关掉了,先查客户端闪退(崩溃、内存不足),再查连接。

连不上/无限加载

进不了游戏,或者停在加载、进场界面。 接收新连接的环节(服务器的连接队列、防火墙、登录服务器、DB)满了。维护刚结束时尤其多见。

隐身/幽灵实体

本该在的 NPC、怪物、玩家只在自己画面上看不到,或者已经消失的实体只在自己画面上还留着。 多半是少了某个数据包或者绘制失败,和快慢关系不大。排查分线或位面不同、出现和消失通知丢失、加载中被丢弃、资源加载失败。离开视野再回来能不能看到,是关键线索。

04同步设计

同步方式与体感

有的游戏 ping 150 ms 也毫无感觉,有的游戏 60 ms 就显得迟钝。不只是动作游戏如此。线路相同时,这种差异通常来自客户端和服务器事先约定“由谁、在什么时候、决定什么”的方式,也就是同步设计。其中有些是有意的取舍,有些则确实是没做好。

所有网络游戏都在解决同一个问题。服务器和自己的电脑之间一定有时间差,双方总得有一方决定怎样处理“还没确定的事”。可选的做法大致有四种。

  • 等待:服务器确定之前什么都不显示。结果准确,但 ping 直接变成了反应速度。
  • 先显示,之后再修正:自己的动作立即表现出来,服务器结果不同就纠正。速度快,但偶尔会看到拉回或动作被取消。
  • 提前约定时间:像“1.5 秒后砸地”这样,连同未来的时间点一起通知。表现时长比 ping 长时,完全感觉不到 ping。
  • 大家做同样的计算:只交换输入,各自做完全相同的计算(帧同步、回滚)。传输量很小,但一个人的延迟会波及所有人。

所以对 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 机制用定时事件,交易用请求-响应。

ping 150 ms 也很流畅的游戏有什么共同点

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 敏感,有时是有意为之的设计,有时是没做好造成的。

可能是有意的取舍
  • 判定窗口短:像 0.2 秒弹反、完美闪避这样,短判定窗口本身就是乐趣所在的游戏。反应时间被 ping 吃掉一部分、抖动打乱时机,这些都无法避免,所以会用延迟补偿或就近的地区服务器来减轻。
  • 为防作弊由服务器确认:货币、物品、排名这类绝不能被伪造的结果,等服务器确认才是正确做法。
  • 帧同步:要只靠输入同步几百个单位,这是最现实的架构。代价是要根据 ping 调整输入延迟。
  • 公平性:也有游戏故意把延迟补偿调弱,免得被打的一方因为别人 ping 高而吃亏。
很可能没做好的信号
  • 用键盘、手柄直接操控的游戏,却连移动和普通攻击都要等服务器确认。不做预测,ping 就直接成了手感。点击移动这类下指令的操作,即使等服务器确认也不太明显,所以 MOBA 等游戏有时会有意这样选。
  • 一次 UI 操作有多次往返:打开窗口 → 获取列表 → 确认 → 购买,如果每一步都是一次往返,ping 150 ms 时要花 0.7~0.8 秒。这些可以合并成一次。
  • 线路 ping 很低,却始终有固定的迟钝感:可以怀疑 Nagle(把小包攒起来再发的 TCP 默认行为,用 TCP_NODELAY 关闭)、请求攒到下一个 tick 处理而结果又等到再下一个 tick 才发出的双重等待,以及每个操作都要等 DB 保存完才响应的设计。自己电脑的垂直同步(V-Sync)、FPS 过低也会带来同样的感觉。
  • 没有技能队列,“确认后才接受下一个输入”:每段连招之间都夹着往返时间,ping 越高,DPS 损失越多。
  • 一到就播放:如果没有插值缓冲或服务器时间,按收到的顺序直接表现,抖动就会直接变成动画的一卡一卡。

同步设计中导致卡顿的原因

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

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

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

症状: 操作延迟 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

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

症状: 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

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

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

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

症状: 操作延迟, 吞操作/回档 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

起因: BOSS 攻击预警 0.5 秒、弹反判定 0.2 秒这样的短判定窗口 → 结果: 预警看到得晚(下行延迟 + 插值),自己的输入也到得晚(上行延迟 + tick 等待) → 画面表现: 明明躲开了却被打中,弹反被吞

症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

没有延迟补偿的判定 Server-now hit validation

服务器只按“当前服务器上的位置”判定命中时,自己看到的画面就会与判定结果对不上。

起因: 自己画面上的对手是约 0.2 秒前的位置(ping 150 ms、插值 100 ms 时) → 结果: 服务器按当前位置判定,自己瞄准的地方对手已经不在了 → 画面表现: 明明打中了却判定未命中。打移动目标必须打提前量

症状: 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

延迟补偿过度 Excessive lag compensation

按攻击者的视角回溯得太远时,被打的一方明明已经躲好了,还是会被打中。

起因: 为照顾 ping 高的攻击者,服务器大幅回溯后判定 → 结果: 在被打者的画面上,自己早已躲到掩体后 → 画面表现: “躲到墙后还被打中”,ping 高的人反而占优

症状: 吞操作/回档 · 主责 研发团队·服务器开发

客户端权威 Client-authoritative results

各自决定自己的结果,自己的画面很流畅,但结果会与别人的画面对不上,也容易被外挂利用。

起因: 位置、命中由客户端决定,服务器只负责转发 → 结果: 两个人都说自己先打中了对方,服务器无法验证 → 画面表现: 对手瞬移、穿墙,“我打中了却没算”

症状: 瞬移, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

帧同步中等待最慢的玩家 Lockstep waits for the slowest peer

所有人一起计算同一个逻辑帧的结构下,只要有一个人的输入晚到,所有人都得等。

起因: 每个逻辑帧都要集齐所有玩家的输入才能计算 → 结果: 某个人的输入因抖动、丢包而晚到 → 画面表现: 所有人同时顿一下,严重时弹出“正在等待玩家”窗口

症状: 卡住, 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

回滚网络代码预测失败 Rollback misprediction

先预测对手的输入并显示出来,猜错了就回滚重新计算。ping 越高,回滚的幅度越大。

起因: 对手改变了输入(与预测不同) → 结果: 实际输入晚到约半个 ping,就要回滚这么长时间重新计算 → 画面表现: 对手的动作跳过几帧或突然改变

症状: 瞬移 · 主责 研发团队·客户端开发

没有时间戳、一到就播放 Events played on arrival (no timestamps)

服务器事件不附带发生时刻、一收到就播放时,网络抖动会原封不动地让表现时机忽快忽慢。

起因: “开始攻击”“播放特效”事件一到就执行 → 结果: 每个数据包到达时间不同,间隔忽长忽短 → 画面表现: 连续攻击动作时快时慢,BOSS 技能时机每次都不一样

症状: 一卡一卡, 快进 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

双重 tick 等待 Double tick quantization

请求攒到下一个 tick 才处理,结果又在再下一个 tick 才发出,tick 间隔就被叠加了两次。

起因: 收到的请求在下一个 tick 处理 → 结果: 处理结果也攒到下一个发送 tick 统一发出 → 画面表现: 线路 ping 很低,反应却稳定地慢约 1.5 倍 tick 间隔。10 tick 服务器平均 0.15 秒,最差 0.2 秒

症状: 操作延迟 · 主责 研发团队·服务器开发

过于严格的服务器校验 Over-strict server validation

服务器对移动速度、冷却、射程检查得过于严格时,连因抖动扎堆到达的正常输入也会被拒绝。

起因: “一个 tick 内可移动的距离”“冷却容差 0 ms”这类严格标准 → 结果: 因抖动,两条指令挤在同一个 tick 到达,被判为违规 → 画面表现: 拉回,冷却好了技能却被拒绝

症状: 拉回, 吞操作/回档 · 主责 研发团队·服务器开发

主机(房主)结构 Listen server / host advantage

由某个玩家的电脑充当服务器时,这个人的线路和电脑性能决定了所有人的体验。

起因: 房主的电脑充当服务器(P2P、Listen Server) → 结果: 房主线路或电脑慢,就会波及所有人;房主自己的 ping 为 0 → 画面表现: 只有房主占优,房主一退出所有人都卡住或掉线

症状: 一卡一卡, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

预表现后被服务器拒绝 Client-side feedback rejected by server

在自己画面上先显示出来的命中、技能,服务器事后不认可时,明明看到的结果就当没发生过。

起因: 命中特效、技能动作在服务器确认前先播放(预表现) → 结果: 服务器重新核对射程、目标位置、冷却、资源后拒绝 → 画面表现: 血溅出来了却没有伤害,技能动作放出去了却没效果,只有冷却在转

症状: 吞操作/回档, 拉回 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

指令同步的路径计算不一致 Command sync with divergent pathing

只互传“去这里”,路径由双方各自计算时,计算稍有差异,角色或怪物就会走上另一条路,然后被拽回原位。

起因: 点击移动、怪物追击时只发送目的地,路径由客户端另行计算 → 结果: 地形数据差异、与其他角色碰撞、计算顺序不同,导致走了和服务器不同的路径 → 画面表现: 怪物穿墙走着走着突然被挪走,点击移动的角色像打滑一样转向

症状: 瞬移, 拉回 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

快照发送频率低 Low snapshot / update rate

服务器每秒只发几次位置更新(快照)时,插值缓冲就得相应拉长,看到的其他角色也就是更久以前的状态。

起因: 为节省流量,位置更新每秒只发 5~10 次 → 结果: 要画得流畅,缓冲需设为包间隔的 2 倍(200~400 ms);设得短,只要漏掉一个包就会卡住 → 画面表现: 对手转向看起来很晚,与判定对不上。缓冲短时一卡一卡,丢包时瞬移

症状: 一卡一卡, 瞬移, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

05影响范围

只有一个人卡、只有一边异常时

卡顿反馈中最让人困惑的,是只有几个人或只有一边遇到的情况。慢的那个人在别人眼里是什么样子、会不会连带拖慢别人,完全取决于服务器处理输入的方式和同步方式。同一台电脑上开的两个客户端中只有一个看不到 NPC 的现象,也在本章讨论。

只有特定玩家、特定线路慢时

现在大多数 MMO 都是服务器权威架构。服务器决定所有结果,客户端绘制收到的结果。在这种架构下,卡顿大多只出现在慢的那个人身上。

  • 慢的本人会遇到操作延迟:技能、拾取这类需要服务器确认的操作,会晚上一个 ping 的时间。移动靠预测能立即显示,但抖动(到达间隔的波动)大时,还会遇到拉回,并看到其他人瞬移。
  • 其他人只会看到慢的人的角色顿一下又快进,或者瞬移。他们自己的操作和怪物的动作都正常。如果 ping 高但没有抖动和丢包,看起来只是位置稍微靠后,动作依然流畅。在别人眼里显得卡,比起 ping,更多是抖动和丢包造成的。
  • 如果只有特定运营商、地区的线路不好,这些玩家会同时出现上述症状。在服务器看来,只有这些人的输入到达得忽快忽慢,所以移动校验或外挂检测的误判也会集中在他们身上。
  • 如果只有某个角色卡,比起线路,更该怀疑这个角色的数据。物品、邮件堆了几千个的角色,每次登录和保存都要比别人多读写好几倍。用同一个角色在别的电脑、别的线路上登录,看是否同样慢,就能区分。

但也有一个慢的人会拖慢所有人的架构。共同点是“有人在等那个人”。

  • 所有人等同一个逻辑帧或回合的架构:帧同步(RTS)、按回合推进的合作玩法。一个人的输入晚到,所有人都会卡住。即使没有抖动、只是单纯延迟,所有人的输入也都要按最慢那个人的 ping 延后生效。
  • 服务器为了给慢的人发数据而等待的架构:阻塞发送(停下来一直等到发送缓冲区有空间才发出去)、同步处理。这个服务器线程负责的所有人都会变慢。通常的做法是给每个人单独设一个发送队列,不去等待。这时卡顿只出现在慢的人身上,队列太长时也只有这个人掉线。
  • 慢的人担任核心角色的架构:由这个人的电脑当主机(房主)的 P2P(不经服务器,玩家之间直接连接)或 Listen Server(玩家的电脑兼做服务器),以及只能靠队长权限推进的活动。如果游戏为了减轻服务器负载,把怪物移动计算交给附近玩家的客户端,这个人负责的怪物在所有人的画面上都会一卡一卡。
  • 判定以慢的人为准回溯的架构:延迟补偿。慢的人能公平地命中,但被打的一方会觉得冤:“明明已经躲起来了还是被打中”。所以会给回溯幅度设上限。上限因游戏而异,大约在 0.2~1 秒之间(Source 引擎默认值是 1 秒)。

服务器处理输入的方式不同,表现也不同

服务器处理输入的方式慢的本人遇到的情况慢的人在别人眼里的样子其他人自己的游戏
每个 tick 集中处理
固定 tick,收到的输入一次性处理
技能结果要晚 ping 加上等待 tick 的时间(操作延迟)。移动校验严格时会拉回顿一下后一次走好几步(快进、瞬移)。只是 ping 高而没有抖动时很流畅没有影响
到达即处理
事件驱动,收到立即应用并发送
操作延迟约等于 ping。只省下了等待 tick 的那段时间移动忽快忽慢(轻微快进)。一起涌来的多个技能在一瞬间全部执行没有影响
每个玩家单独的输入缓冲
每人各自暂存,一个 tick 处理一个
确认会晚一个缓冲的长度相对流畅。缓冲空了会暂时停在原地没有影响
延迟补偿判定
回溯到攻击者看到的时间点
瞄哪儿打哪儿(在回溯上限内)躲起来之后仍会被这个人的攻击打中冤枉被打中(波及)
帧同步、回合等待操作延迟。输入晚到时卡住所有人卡住卡住。只是晚到也会操作延迟(波及全员)
阻塞发送、同步处理
服务器等待这个人
卡住后快进这个服务器线程负责的所有人都变慢慢动作、卡住(波及这个线程负责的人)
慢的人是主机
P2P、Listen Server
本人的 ping 为 0所有人的画面都一卡一卡全员卡顿
由慢的人控制怪物
把怪物移动计算交给客户端
本人画面上的怪物正常这个人负责的怪物顿一下后瞬移所有和这些怪物战斗的人(波及)

同一台电脑上的两个客户端,只有一个看不到 NPC 时

同一个人在同一台电脑上开了两个客户端,却只有一个看不到 NPC 时,原因几乎不会是线路。两个客户端用的是同一个路由器、同一条线路。差异出在三个地方。

  1. 服务器没有发给这个客户端:分线、副本、任务位面(按任务进度区分能看到哪些 NPC 的功能)不同;视野注册顺序错乱;每条连接的发送量上限;把同一台电脑、同一个 IP 当成同一个人的会话 bug;多开限制。
  2. 发了,但客户端丢掉了:加载期间到达的出现通知被丢弃;刚进入时集中涌来的出现信息因接收缓冲区溢出,或因不可靠(unreliable)通道(丢了也不重发的通道)而丢失;基准快照(只发变化量时作为起点的完整信息)丢失;ID 被复用的新 NPC 被误认成旧 NPC;固定 UDP 端口冲突,数据包被另一个客户端截走;后台窗口处理被推迟,接收缓冲区溢出;服务器时间估算有偏差,显示被推迟。
  3. 收到了,但画不出来:两个客户端同时写同一个缓存文件,导致模型加载失败;显存(VRAM)不足;显示人数上限之类的选项不同;版本、数据不一致。

最有力的线索有三个:头顶名字还在,只是角色模型不见了吗(服务器发了,绘制失败),离开视野再回来能看到吗(漏了一条出现通知),以及把看不到的那个窗口切到前台会好转吗(后台窗口处理受限)。反过来,已经死了的怪物只在自己画面上还站着的“幽灵实体”,是漏了离开通知。

只有部分人遇到的问题

网络差的玩家在别人画面上快进移动 Laggy player seen by others (bursty inputs)

线路差的人,输入会忽多忽少地扎堆到达服务器。服务器每个 tick 收到多少就应用多少时,在别人眼里,这个角色会顿一下,然后一下子走出好几步。

起因: 网络差的人的移动指令,有的 tick 到 0 条,有的 tick 一次到 2~3 条 → 结果: 服务器在收到的那个 tick 一次性全部应用,该角色的位置呈台阶式变化 → 画面表现: 在别人画面上只有这个角色顿一下,然后一下子快进移动。其他都正常

症状: 快进, 瞬移 · 主责 研发团队·服务器开发 · 配合 外部·外部

到达即处理的服务器造成的快进 Event-driven processing of bursty inputs

在包一到就立即处理并广播的服务器上,网络差的人扎堆到达的动作会被接连立即执行。

起因: 网络差的人的技能、移动请求扎堆到达 → 结果: 服务器一收到就按顺序执行,并立即通知所有人 → 画面表现: 在别人眼里,这个人一瞬间放出好几个技能,或像快进一样移动

症状: 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

每个玩家的输入缓冲大小 Per-player server input buffer (jitter buffer)

服务器为每个人先攒一点输入,每个 tick 取出一个使用时,别人看起来很流畅,但本人动作在服务器上确定的时间也会相应推迟。

起因: 服务器把网络差的人的输入攒在缓冲里,每个 tick 应用一个 → 结果: 缓冲小,就经常被取空,该角色原地站住,或由服务器按最后一个输入推测移动;缓冲大,本人的输入就确定得晚 → 画面表现: 缓冲小,别人看到他顿一下;缓冲大,本人的技能结果出得晚(操作延迟)

症状: 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

集中在特定运营商玩家身上的校验误判 Anti-cheat / movement validation false positives on bad ISPs

使用抖动大的线路的人,输入会扎堆到达,经常触发服务器的速度、冷却检查。

起因: 特定运营商、地区的线路晚上抖动变大 → 结果: 服务器把扎堆到达的正常输入判为超速、冷却违规 → 画面表现: 只有这家运营商的玩家出现拉回、技能被拒,严重时被服务器踢出而掉线

症状: 拉回, 吞操作/回档, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维

一个网络差的队友与 BOSS 机制 One laggy member in a synchronized mechanic

在需要所有人在规定时刻一起反应的团本机制中,一个网络差的人反应晚了,就会让整个队伍失败。

起因: “所有人同时散开”“一个人按按钮”这类团队机制 → 结果: 网络差的人预警看到得晚,输入也到得晚 → 画面表现: 因为这一个人而团灭,其他队友觉得“都怪那个卡的人”

症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

怪物控制权在慢的客户端上 Monster movement delegated to a player client

有的游戏为减轻服务器负载,把怪物的移动计算交给附近某个玩家的客户端。这个人的线路差时,这只怪物在所有人的画面上都会动得很怪。

起因: 服务器把怪物的移动计算交给最近(或最先到)的玩家的客户端 → 结果: 负责的人上报结果晚到或扎堆到达服务器 → 画面表现: 只有这只怪物在周围所有人的画面上顿一下然后瞬移。负责的人自己的画面上正常

症状: 瞬移, 一卡一卡, 快进 · 主责 研发团队·服务器开发

特定角色数据过于庞大 One character with oversized data (inventory, mail, buffs)

物品、邮件堆了几千个,或者好友列表、黑名单、buff 特别多的角色,登录、存盘、向周围广播的数据量是别人的好几倍。与线路无关,只有用这个角色时才慢。

起因: 长期培养的角色或活动奖励在背包、邮箱里堆了几千个 → 结果: 每次登录、切换区域、存盘都要读写对应数量的 DB 数据,要发给周围的装备、buff 信息也很大 → 画面表现: 只有这个角色进入时加载很久,打开背包、邮件时顿一下。如果服务器在游戏线程里等待存盘完成,周围的人也会跟着出现短暂停顿

症状: 连不上/无限加载, 操作延迟, 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

分线/副本/位面不同 Different channel / instance / phase

两个角色在不同的分线或副本里,或者处在按任务进度显示不同 NPC 的不同“位面”时,看到的就是不同的世界。

起因: 第二个角色被分到了别的分线,或任务阶段不同 → 结果: 服务器不给这个角色发送该 NPC(正常) → 画面表现: 只有一边没有 NPC。看起来像 bug,其实是按设计运行

症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

加载中到达的出现通知被丢弃 Spawn messages dropped before the client is ready

刚进入场景,服务器就发来周围 NPC 的出现通知,而客户端还在加载地图,就把这些通知丢掉了。

起因: 服务器在完成进入处理后立即发送周围实体的出现通知 → 结果: 客户端正在加载,消息处理函数还没注册,于是丢弃通知 → 画面表现: 服务器认为已经发过了,不会再发。在离开视野再回来之前,NPC 一直看不到

症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

视野注册顺序错乱 Interest-management race on enter/leave

角色注册到视野网格的时刻与 NPC 跨网格移动的时刻重合时,这个 NPC 的出现通知可能会漏掉。

起因: 进入、换线、传送的处理与 NPC 移动在同一时刻重合 → 结果: 计算“新进入视野的实体”时漏掉了这个 NPC → 画面表现: 只有特定几只 NPC 看不到,或已经离开的 NPC 还留在原地

症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发

基准快照丢失 Lost baseline for delta compression

在服务器只发“与上次相比变化的部分”的方式下,丢了最初那一份完整信息(基准),之后的变化量就没法应用。

起因: 实体的完整信息(基准)包丢失,或在处理前被丢弃 → 结果: 客户端没有可以应用后续变化量的对象,于是忽略 → 画面表现: 这个实体看不到,或过了很久突然出现

症状: 隐身/幽灵实体, 瞬移 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

离开通知丢失(幽灵实体) Missed despawn (ghost entity)

反过来,漏掉“已消失”的通知时,已经死亡或离开的 NPC、玩家只会留在自己的画面上。

起因: 死亡、离开、离开视野的通知丢失或顺序错乱 → 结果: 客户端认为这个实体还在 → 画面表现: 怎么打都没反应的怪物,早已离开的玩家还站在那里

症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

刚进入时集中到达的出现信息丢失 Initial spawn burst lost (unreliable channel, receive buffer, fragmentation)

进入场景的那一刻,服务器会一次性发出周围几十到几百个实体的出现信息。如果用不可靠(unreliable)通道发送,或者客户端在加载中读不了 socket、接收缓冲区溢出,就会丢掉一部分,而且不会再来。

起因: 刚进入时出现信息在极短时间内集中到达 → 结果: 正在加载的客户端读 socket 读得晚,OS 接收缓冲区溢出;或者大的 UDP 包被分片,只丢一个分片整个包就没了。不可靠通道也不会重发 → 画面表现: 只有加载慢的那个客户端缺了几只 NPC。离开视野再回来就能看到

症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

实体 ID 重用混淆 Entity ID reused without a generation counter

死掉的 NPC 重新出现时,如果服务器重用同一个实体 ID,期间漏掉离开通知的客户端会把新 NPC 误认成旧 NPC。

起因: NPC 死亡后以同一个实体 ID 重新出现 → 结果: 漏掉离开通知的客户端认为是“已知实体”,忽略出现通知,或保留死亡状态不变 → 画面表现: 只有一边画面上没有 NPC,或 NPC 倒地不起,有时还显示成别的 NPC 的样子

症状: 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

固定 UDP 端口冲突 Two clients bound to the same local UDP port

客户端被做成使用固定的本地端口时,同一台电脑上的第二个客户端要么用不了这个端口,要么和第一个客户端分着收包。

起因: 两个客户端要打开同一个本地 UDP 端口(用重用选项强行共用) → 结果: OS 只把收到的包交给其中一个 socket,或不保证由哪一个接收。路由器和服务器也把两个客户端看成同一个地址 → 画面表现: 一边收不到世界数据包,看不到 NPC 和其他玩家,或者掉线

症状: 隐身/幽灵实体, 掉线, 连不上/无限加载 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

按 IP/设备区分会话的 bug Session keyed by IP or machine ID

服务器或中间服务器按 IP 或设备 ID 区分连接时,会把同一台电脑(同一公网 IP)上的两个客户端认成同一个人。

起因: 会话表按 IP 或 IP+设备 ID 建立 → 结果: 第二个客户端的信息覆盖或混进第一个会话 → 画面表现: 一边看不到 NPC,另一边掉线或收到别人的信息

症状: 隐身/幽灵实体, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

多开限制 Multi-client restriction policy

安全模块或服务器策略限制一台电脑上开多个客户端时,第二个客户端会无法启动或连接,或者先开的那个掉线。有些游戏只禁用额外客户端的部分功能。

起因: 安全模块检测到重复运行,或服务器限制同一设备的额外连接 → 结果: 拒绝第二次启动或连接,或断开其中一个。少数情况下只屏蔽额外客户端的部分功能 → 画面表现: 连不上,或其中一个掉线。只屏蔽功能的游戏中,只有一边看不到 NPC、商店

症状: 连不上/无限加载, 隐身/幽灵实体, 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

后台窗口处理受限 Background window throttling

处于后台窗口的客户端,游戏、引擎、OS 都会减少它的帧和处理量。收到的包不能及时处理,就会积压或溢出。

起因: 游戏选项或显卡驱动的后台帧率限制(例如 NVIDIA 驱动可在每秒 20~200 帧之间指定),省电,引擎的后台暂停设置。OS 也会优先把 CPU、GPU 分给前台窗口 → 结果: 每帧处理的包数减少,队列堆积,接收缓冲区溢出后包被丢弃 → 画面表现: 窗口切到前台时一下子全冒出来,或者有的 NPC 始终看不到

症状: 隐身/幽灵实体, 快进, 掉线 · 主责 研发团队·客户端开发 · 配合 外部·外部

缓存/资源文件并发访问冲突 Shared cache / asset file lock conflicts

两个客户端同时写同一个缓存文件夹或锁住文件时,其中一个会加载不了 NPC 模型、纹理。

起因: 两个客户端同时写同一安装目录下的缓存、补丁文件 → 结果: 文件加锁失败,或读到写了一半的文件,导致加载失败 → 画面表现: 有名字牌却没有角色模型,或 NPC 是透明的

症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发

内存/显存不足导致流式加载失败 Memory / VRAM exhaustion

两个客户端共用显存时,新需要的模型、纹理没有地方加载,有一部分就画不出来。

起因: 两个客户端共用显存和内存。OS 有时还会先削减后台窗口的显存配额 → 结果: 引擎无法加载新的模型、纹理,或反复卸载再加载 → 画面表现: NPC 出现得晚、模糊或看不到,一卡一卡

症状: 隐身/幽灵实体, 一卡一卡 · 主责 研发团队·客户端开发 · 配合 外部·外部

显示选项不同 Different display settings

显示人数限制、隐藏 NPC 名字牌或模型、低配模式这类选项在两个客户端上不同时,看到的东西就不一样。

起因: 只有一个客户端开了“周围角色显示数量限制”或低配模式 → 结果: 不绘制远处或优先级低的 NPC(正常) → 画面表现: 只有一边没有 NPC

症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发

客户端版本/数据不一致 Client version / data table mismatch

第二个客户端来自另一个安装目录,或补丁没打完时,它不认识服务器发来的新 NPC ID,就会悄悄忽略。

起因: 装在其他文件夹里的客户端,或在打补丁过程中启动的客户端 → 结果: 收到不认识的 NPC ID、模型 ID 就跳过 → 画面表现: 只有新加的 NPC 在一边看不到

症状: 隐身/幽灵实体 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

按连接分配的发送预算/优先级 Per-connection bandwidth budget and priority

服务器给每个连接的发送量设上限、按由近及远的顺序发送时,上限定得低的一方收到远处 NPC 会很晚,甚至收不到。

起因: 人多的地方,服务器在每个连接的发送量上限内按重要程度依次发送 → 结果: 带宽估算偏低的连接(例如因为是后台窗口而确认回得晚的一方),排在后面的实体会被一直往后推 → 画面表现: 远处的 NPC 只在一边显示得晚或看不到

症状: 隐身/幽灵实体, 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

时钟估算误差导致实体被搁置 Clock estimate error holds or discards entities

客户端估算的服务器时间不准时,刚到达的实体信息会被当成“还在未来”而搁置,或被当成“太旧”而丢弃。

起因: 某一个客户端对服务器时间的估算偏差很大(在加载中测量、从省电状态恢复) → 结果: 插值基准时间与实体信息的时间对不上 → 画面表现: 实体出现得晚,或看起来停在原地不动

症状: 隐身/幽灵实体, 一卡一卡 · 主责 研发团队·客户端开发

06常见原因

TCP 重传:成因与延迟变大的原因

服务器指标上的“TCP 重传(Retransmission)”增加时,卡顿反馈往往也会随之增加。重传是“数据包丢了”或者“误以为丢了”的信号。原因可能出在从 Wi-Fi 到服务器网卡这条路径上的任何一处;在游戏这种稀疏发送小包的连接上,只丢一个包也可能让画面卡住几百 ms。本章整理重传的根本原因、查找原因的方法和解决方向。

服务器发送游戏收到123×丢失456124563等待重发(取决于恢复方式)3游戏什么都收不到:卡住3、4、5、6 一起到:快进
这是每 50 ms 发送一次的连接中只丢了 3 号包的情况。TCP 只按顺序交付,所以即使 4、5、6 号到了,在重新收到 3 号之前也不会交给游戏。于是一次丢包就变成了卡住和随后的快进。重发的时间点取决于恢复方式,从一次往返左右到“往返时间 + 至少 200 ms”(重传定时器)不等(见下方“重传的种类”)。

重传拖慢游戏的四个原因

  1. 按序等待(队头阻塞,HOL blocking):TCP 在重新收到丢失的那个包之前,不会把后面到达的数据包交给游戏。丢一个,后面的全部一起停住,然后一次性放出(卡住后快进)。
  2. 重传等待时间:发送方要等重传定时器(RTO)到期才会重发。以 Linux 为例是“往返时间 + 至少 200 ms”。如果重发的包也丢了,等待时间每次翻倍(0.3 秒 → 0.6 秒 → 1.2 秒 ……)。
  3. thin stream(稀疏发送小包的连接):快速重传靠接收方通知“后面已经有 3 个包到了”的信号(3 个重复 ACK;ACK 是“收到了”的确认)触发。游戏包 50~200 ms 才发一个,常常是信号还没凑齐,RTO 就先到了。这就是大文件下载扛得住、唯独游戏会卡住的原因。新版 Linux 的 RACK 只要后面到一个包就能判断,大大缩小了这个差距,但包间隔长到 200 ms 左右时,RACK 也不比 RTO 快。
  4. 发送量缩减:TCP 把丢包视为拥塞信号,会缩小一次能发出的量(拥塞窗口)。一旦走到 RTO,就得从一次只发一个包的状态重新增长。这期间新产生的数据包都堆在服务器上等待,人多场景里的大量更新一个接一个被推迟。

重传的种类

种类何时发生恢复所需时间游戏中的表现
快速重传
Fast retransmit
后面的包先到,接收方通知“中间缺了”(重复 ACK、SACK)时往返时间 + 后面 3 个包到达所需的时间短暂顿一下。包越密集,恢复越快
RACK/TLP
按时间判断丢包,重发尾包
较晚发出的包已到达,而前面的包过了一定时间仍没到时;或者一段时间没有 ACK 时,把最后一个包再发一次后面包的确认一到就很快重发(RACK。可能只是顺序颠倒,所以会多等约往返时间的 1/4)。没有后续包时约为往返时间的 2 倍(TLP);尚未确认的包只有一个时,考虑到延迟 ACK,还要再多等 200 msthin 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. 是不是真实丢包:TCPSpuriousRTOs、DSACK 一起上升时,先怀疑把晚到的包当成丢失的虚假重传。
  3. 服务器接收环节:同一时刻网卡、softnet、云上限计数器如果有增长,就是在服务器侧丢的。
  4. 数据中心设备:查看交换机、防火墙的丢弃与 CRC 计数器,以及会话表。
  5. 外部路径:从问题玩家侧和服务器侧双向跑 mtr,找出开始丢包的那一跳。
  6. 仍然查不出来时:在两端同一时刻抓包并对比。

反馈中如果有时间(精确到秒)、玩家的运营商和地区、所在服务器、症状名称,运维团队就能立刻按这个顺序排查。

解决方向

1. 不丢包(根本解决)

  • 用有线代替 Wi-Fi,改用 5 GHz、6 GHz,在路由器上启用 SQM、ECN,减少队列溢出
  • 服务器不要把一个 tick 的更新一次性全发出去,在 tick 内分散发送。单个连接的集中发送,用平滑发送(pacing,如 fq、BBR、发送速率上限)摊平
  • 加大环形缓冲区,把中断分散到多个核心,确认云上限
  • 更换有 CRC 错误的网线、光模块,统一双工(duplex)设置
  • 用流量整形(超出部分先放进队列再慢慢发出)代替流量监管(超出部分立即丢弃),调大允许的突发量
  • 给防火墙、连接跟踪(conntrack)表和中间设备的每秒包数留出余量,让去程和回程经过同一台防火墙
  • 通过调整 MSS(一个包能装载的最大数据量)并放行包过大通知(ICMP)来防止 MTU 黑洞(MTU 探测是最后一道保险);用客户端发送的心跳维持 NAT、LB 映射

2. 快速恢复

  • 确认 SACK、时间戳没有在服务器配置中被关闭,也没有被中间设备剥掉(没有 SACK,RACK-TLP 也无法工作)
  • 使用 RACK-TLP(新版 Linux、Android 默认)。像自己的输入这类由客户端发送的方向,由客户端操作系统负责恢复,服务器配置改变不了(Windows 从 Windows 10 的 1607 版本、Windows Server 2016 起默认开启 TLP 和 RACK,连丢失的重传也能恢复的新版 RACK 则从 Windows Server 2022 起提供)
  • thin stream 可用 tcp_thin_linear_timeouts;Linux 6.15 及以上可用 TCP_RTO_MAX_MS 降低 RTO 上限
  • 游戏连接要开启 TCP_NODELAY(如果 Nagle 把新包攒着不发,RACK 可以利用的后续包就没了)
  • 内网服务器之间的连接,调低按路由设置的 RTO 最小值(ip route … rto_min)
  • 用 TCP_USER_TIMEOUT 和游戏心跳尽快断开失效连接并重连

3. 降低对重传的敏感度(架构)

  • 实时位置、战斗走 UDP,只重发需要的部分(旧位置没有重发的价值)。输入如果把最近几个叠在一起发送,丢一个时下一个包就能补上
  • 把聊天、交易这类需要顺序的数据和实时数据包分成不同的流(QUIC stream、拆分 TCP 连接等)。一边丢包不会堵住另一边
  • 如果继续用 TCP,不要在发送缓冲区里堆积旧位置,用最新状态覆盖(TCP_NOTSENT_LOWAT 等)。长时间卡住之后的快进会变短
  • 用插值缓冲和预测在画面上掩盖短暂的停顿。RTO 造成的几百 ms 卡住则很难掩盖

参数名称整理:该开启的参数与容易混淆的参数

和重传恢复相关的参数大多是操作系统(内核)参数,能只对游戏连接单独开启的 socket 选项只有几个。经常因名字被混淆的 TCP_NODELAY 并不能加快恢复。不过如果不开(使用 Nagle),恢复期间新包会更晚发出。下表以 Linux 为准,Windows 的名称和支持范围不同。

参数在哪里设置改变什么注意
TCP_NODELAYsocket 选项关闭 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_mssocket 选项 / 内核参数(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_TIMEOUTsocket 选项重传持续时,放弃连接之前的时长不会加快恢复。用于尽快断开失效连接并重连。如果不设置,Linux 会重传 15 次左右、持续约 15 分钟后才断开(tcp_retries2)
SO_KEEPALIVE + TCP_KEEPIDLE 等socket 选项确认空闲连接是否还活着与重传无关。用于维持 NAT/LB 映射和检测失效连接
fq 队列 + SO_MAX_PACING_RATE、BBR队列配置 / socket 选项 / 内核参数把数据包均匀分散发送,减少突发(一次性集中发送)造成的丢包属于“预防”丢包。与恢复速度无关

TCP 重传的根本原因

无线链路丢包 Wi-Fi / cellular link loss

Wi-Fi 和移动网络会在无线链路上重传几次,仍然失败就丢弃数据包。被丢弃的包要等过好一阵,才由 TCP 重新发送。

起因: 信号弱或干扰严重,无线链路上的传输接连失败 → 结果: 超过无线设备的重试上限(通常几次到十几次)就丢弃数据包 → 画面表现: 卡住的时长等于 TCP 重传的等待时间,后面的包在接收缓冲区里等着,之后表现为快进

症状: 卡住, 快进, 瞬移 · 主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发

瓶颈队列溢出(拥塞丢包) Tail drop at a congested bottleneck

路由器、运营商之间的互联链路、数据中心线路这类最窄处的队列一旦满了,新来的数据包就会被丢弃。

起因: 视频、下载和其他用户的流量把瓶颈链路占满 → 结果: 队列满的期间,新到的数据包接连被丢弃(tail drop)。没被丢弃的包也要在满队列的末尾排队 → 画面表现: 多个包同时丢失,长时间卡住后表现为快进,晚上多发

症状: 卡住, 快进, 拉回 · 主责 运维团队·网络运维 · 配合 外部·外部, 研发团队·客户端开发

突发发送导致浅缓冲区溢出 Sender bursts overflow shallow buffers

服务器每个 tick 在一瞬间集中发出几千人份的更新,交换机的小缓冲区或云平台的瞬时上限不到 1 ms 就会溢出,一部分包被丢弃。

起因: tick 开始的瞬间,把发给所有人的数据包一次性发出 → 结果: 多台服务器流量汇聚的交换机端口缓冲区(每个端口几百 KB 到几 MB)或云实例的上限瞬间被冲破(平均利用率很低) → 画面表现: 多名玩家同时瞬移或顿一下,看平均值指标找不到原因

症状: 瞬移, 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维

流量监管丢弃超额流量 Traffic policing

运营商套餐限速、云实例上限和 DDoS 防护设备,有时会把超过规定速率的数据包直接丢弃,不放进队列。

起因: 瞬时发送量超过允许速率或允许突发量 → 结果: 超出的数据包不排队,直接丢弃(流量监管) → 画面表现: 每逢突发量大的瞬间就丢好几个包,卡住后表现为快进;平均速率看起来低于上限

症状: 卡住, 快进, 瞬移 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发

物理层错误(线缆/光模块/连接器不良) Bit errors: bad cable, optics, dirty fiber

线缆损坏、光纤连接器积灰、光模块老化都会产生误码,损坏的数据包会被设备悄无声息地丢弃。

起因: 线缆、光模块或连接器不良,导致比特翻转 → 结果: 设备丢弃校验和(CRC)不对的数据包 → 画面表现: 只有经过这条路径的玩家反复顿一下再快进,与时段无关

症状: 卡住, 快进, 瞬移 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 外部·外部

双工不匹配 Duplex mismatch

一端用自动协商,另一端把速率和双工写死,就会有一端以半双工工作,每当负载上来就因冲突丢包。

起因: 只有一端设备固定了速率和双工 → 结果: 一端以全双工、另一端以半双工工作,产生冲突和晚期冲突(late collision) → 画面表现: 平时正常,流量一上来,经过这台设备的所有人都先卡住再快进

症状: 卡住, 快进 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

接收端服务器主机丢包 Receiver host drops (ring, softirq, CPU)

数据包已经到了服务器,却因网卡的环形缓冲区(暂存到达数据包的缓冲区)溢出,或内核中负责接收处理的 CPU 核心饱和而被丢弃。

起因: 在线人数激增、中断集中在一个核心、虚拟机 CPU 窃取时间、虚拟交换机过载 → 结果: 在环形缓冲区(rx_missed_errors 等,名称因驱动而异)或内核接收队列(softnet dropped)处被丢弃 → 画面表现: 人一多,全服同时出现输入生效慢、顿一下的情况

症状: 操作延迟, 卡住, 快进, 瞬移 · 主责 运维团队·系统运维

防火墙/连接跟踪丢包 Stateful firewall / conntrack drops

防火墙或 Linux 连接跟踪(conntrack,把经过的连接记录到表里的功能)在表满了,或判断连接状态不对时,会丢弃数据包。

起因: 连接跟踪表已满(table full),或去程和回程路径不同,只有一个方向经过防火墙(非对称路径) → 结果: 防火墙把数据包当成“未知连接”或“序列号超出窗口范围”的包丢弃 → 画面表现: 表满时新连接进不来;路径不对称时,只有走这条路径的人在反复重传后掉线

症状: 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发, 研发团队·客户端开发

中间设备超出处理上限(防火墙/IPS/DDoS 防护) Inline appliance PPS / CPU overload

防火墙、入侵防御设备(IPS)、DDoS 防护设备会逐个检查经过的数据包。一旦超出检测能力,处理不过来的包就会被丢弃。

起因: 高峰时段或活动期间,体积小的游戏数据包每秒涌入几十万个以上,或检测规则太重 → 结果: 设备的 CPU 或每秒包数上限被打满,在设备上丢弃。误判时正常数据包也会被拦截 → 画面表现: 这台设备后面的所有服务器同时出现卡住、瞬移,只在人多时加重

症状: 卡住, 快进, 瞬移, 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

MTU 黑洞(只有大包反复丢失) PMTU black hole

中间链路能通过的包变小了,“包太大”的通知(ICMP)却被拦截,大包无论重发多少次都会丢失。

起因: VPN 或隧道段的最大包长变小,包过大通知被防火墙拦截 → 结果: 发送方不知道原因,一直重传同一个大包,RTO 每次翻倍 → 画面表现: 平时正常,一到打开背包、人多的地方、进场加载这类要传大数据的时刻,连后面跟着的小包也全部卡住,最终掉线或无限加载

症状: 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发

连接中途 NAT/负载均衡器映射过期 NAT / load balancer mapping expired mid-connection

中间设备删掉空闲连接的映射(记录该连接应转发到哪里的条目)后,之后发出的包就送不到了。要么反复重传后掉线,要么设备回一个拒绝连接的 RST,立刻断开。

起因: 一段时间内没有数据包往来的连接(暂离、大厅) → 结果: 路由器 NAT、运营商 CGNAT、防火墙、负载均衡器、云安全组删除空闲映射 → 画面表现: 再次操作的瞬间开始连续重传,然后掉线,或者直接掉线

症状: 掉线, 卡住 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维, 运维团队·系统运维

路径变更/ECMP 故障路径 Route change / bad ECMP member

互联网路由切换的几秒内,或者被分配到多条 ECMP 路径中某条故障路径的连接上,会出现丢包。

起因: BGP 路由重新计算,或多条路径(ECMP、LAG)中某一条的设备或线路故障 → 结果: 路径切换期间暂时丢包,或只有走这条路径的连接持续丢包 → 画面表现: 突然卡住几秒后快进,或者“重连就好了”(被分到了别的路径)

症状: 卡住, 快进, 瞬移 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部

延迟飙升导致的虚假重传 Spurious RTO from delay spikes

数据包只是一时到得特别晚,并没有丢。但只要这段延迟超过 RTO,发送方就会判定为丢包并重传。

起因: 缓冲区膨胀、Wi-Fi 省电、移动网络无线状态切换、虚拟机挂起,使瞬时延迟达到几百 ms → 结果: RTO 先到期并重传,原包随后也到达(接收方收到重复数据) → 画面表现: 卡住和快进是延迟飙升本身造成的。虚假重传几乎不会让卡住变长,只会推高重传指标,被误认为丢包

症状: 卡住, 快进, 操作延迟 · 主责 外部·外部 · 配合 运维团队·系统运维, 研发团队·客户端开发

乱序导致的虚假快速重传 Reordering triggers spurious fast retransmit

数据包经过多条路径或聚合链路时顺序被打乱,接收方会用重复 ACK 通知“有包缺失”,发送方就把好好的包又发一遍。

起因: 按包分配路径的设备、按包分摊的 LAG(链路聚合)、路径切换的瞬间,都会打乱顺序 → 结果: 后面的包先到,累积 3 个重复 ACK → 快速重传 → 画面表现: 稀疏往来的游戏包几乎不受影响。人多处的大块状态更新和更新包下载会变慢,偶尔一卡一卡

症状: 一卡一卡, 操作延迟 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

ACK 延迟或丢失(上行饱和) ACK path congestion on asymmetric links

数据已经顺利到达,但表示“已收到”的 ACK 在塞满的上行队列里被耽搁或丢掉,发送方就会判定为丢包并重传。

起因: 家里在上传视频或做云备份,上行被占满 → 结果: ACK 在路由器队列里被耽搁几百 ms,或因队列溢出被丢弃 → 画面表现: 服务器发来的游戏包大多能按时到。堆在同一上行队列里的自己的输入被耽搁,出现操作延迟、拉回,偶尔有虚假重传

症状: 操作延迟, 拉回 · 主责 外部·外部 · 配合 研发团队·客户端开发

RTO 设置与环境不匹配 RTO min too low or too high

RTO 最小值调得太低,稍微晚一点就会产生虚假重传;默认值(200 ms)对游戏来说又太长,每丢一次包都要停顿很久。

起因: 为数据中心场景把 RTO 最小值调得很低,或者在公网链路上原样使用默认值 → 结果: 太低时瞬间的延迟也会引发大量重传,太高时每次丢包都要等很久 → 画面表现: 用默认值时,丢一次包就卡住几百 ms 再快进;调得太低,卡住会缩短,但虚假重传激增,浪费线路

症状: 卡住, 快进, 操作延迟 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

thin stream 恢复慢 Thin streams fall back to RTO

像游戏这样稀疏地发小包时,还没等到“后续 3 个包”,RTO 就先到期了。同样的丢包,停顿时间比大流量传输长得多。

起因: 包间隔在 100 ms 左右,已发出未确认的数据包(in-flight)只有寥寥几个 → 结果: 攒够 3 个重复 ACK 要 300 ms 以上,RTO(ping + 200 ms)先触发;连续丢包则每次翻倍 → 画面表现: 丢一次包卡住 0.3 秒左右,重传的包也丢了就卡住将近 1 秒,然后快进

症状: 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发

中间设备剥离 TCP 选项 Middlebox strips TCP options

部分防火墙或加速设备删除或改写 TCP 选项后,丢多个包时每个往返只能恢复一个,或者窗口(一次能发送的量)变小,速度变慢。

起因: 防火墙的“TCP 规范化”、老旧的加速设备剥离 SACK、时间戳、窗口缩放选项 → 结果: 丢了多个包时每个往返只恢复一个,窗口被限制在 64 KB → 画面表现: 每次丢包卡住的时间长得多(没有 SACK 就用不了 RACK-TLP),恢复后表现为快进。下载更新包这类大流量传输也很慢

症状: 卡住, 快进 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

零窗口(看起来像重传的停顿) Zero window, often mistaken for retransmission

接收方程序没有及时读取 socket,缓冲区满了之后,发送方就会停止发送,只发零窗口探测。这与线路无关。

起因: 客户端帧停住或服务器线程阻塞,读不了 socket → 结果: 接收窗口变为 0,发送方停止发送,只发探测(间隔越来越长) → 画面表现: 卡住后快进。抓包中能看到“ZeroWindow”,没有丢包

症状: 卡住, 快进 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·系统运维

连接请求(SYN)重传 SYN retransmission on connect

连接请求因连接队列(backlog)溢出或被防火墙拦截而丢失时,客户端操作系统会从 1 秒后开始,以逐渐拉长的间隔重发。

起因: 维护结束后连接涌入,服务器连接队列溢出,或防火墙、DDoS 防护丢弃 SYN → 结果: 客户端操作系统从 1 秒后开始按固定间隔重传 SYN(旧版 Linux 为 1 秒 → 2 秒 → 4 秒) → 画面表现: 点击连接后延迟正好是 1 秒、3 秒这样的整秒数,一直失败就会连不上/无限加载

症状: 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·网络运维, 研发团队·客户端开发

07分工

研发团队与运维团队的职责

同样是卡顿,修的地方也不同。客户端、服务器代码和同步设计由研发团队负责,线路、网络设备、服务器硬件、DB 服务器由运维团队负责。玩家的电脑和家庭网络、运营商链路、云厂商一侧的问题,两个团队都无法直接修复,只能引导玩家、向对方提需求或绕行。每张原因卡片都标出了主责和配合方,展开卡片上的“数值参考·确认方法·各团队应对”,就能看到各团队分别要做的事。

  1. 反馈/告警症状、精确到秒的时间、服务器/分线
  2. 谁会遇到一个人、一户 / 特定运营商、地区 / 特定服务器、分线 / 全服
  3. 先找谁候选原因卡片的主责。诊断助手中的“优先排查”
  4. 转交的信息IP 与运营商、断线原因、相关监控图、最近的变更
  5. 配合工作按卡片上各团队要做的事分工
这是工单进来后,直到拆分成各团队待办事项的流程。“谁会遇到”对确定负责方影响最大,详细标准见下表和用观测数据判定。
负责方负责范围常用解决手段
研发团队客户端游戏客户端代码:帧、GC、加载,插值、外推、预测,客户端的网络处理(包括发送心跳和自动重连)修改代码、调整插值缓冲与预测、改变加载方式、心跳间隔与重连流程、客户端更新
研发团队服务器游戏服务器代码:tick、线程、锁,同步设计,连接处理(accept 循环、listen 参数),心跳响应与断开连接的清理,socket 选项,查询与事务设计逻辑优化、异步调用、分散 tick 与区域负载、登录排队、用会话令牌接续会话、socket 选项(TCP_NODELAY 等)、查询与索引设计、服务器更新
运维团队网络线路与 IDC 网络设备(交换机、路由器、防火墙、负载均衡器、DDoS 防护),云上的网络 ACL、VPC 路由、负载均衡器,运营商与对等互联设备配置与更换、线路与对等互联扩容、路由调整、向运营商升级处理、调整负载均衡器和防火墙的空闲超时与会话上限
运维团队服务器/OS服务器硬件与云实例(包括安全组和连接跟踪),操作系统与内核配置,网卡,发布与监控环境扩容、更换实例、内核参数(sysctl:somaxconn、conntrack 等)、安全组配置与连接跟踪超时、网卡环形缓冲区与中断分散、调整 cron 和备份时间
运维团队DB 服务器DB 服务器与存储,DB 配置、复制、备份,缓存服务器DB 扩容、保障存储 IOPS、DB 参数与复制配置、调整备份与检查点
外部玩家/运营商/云厂商玩家的电脑与家庭网络、运营商链路(不在我方合同范围内)、云厂商引导玩家(改用有线等)、向运营商和云厂商提需求、游戏侧绕行与缓解

各层/专题负责方一览

粗体数字是该负责方担任主责的原因数,+数字是配合的原因数。点击格子,下方会显示这些原因和该团队要做的事。

边界不清时:原因所在的团队为主责,其他团队负责缓解和确认

主责是根本原因所在之处,或者能消除根本原因的一方。即使原因在线路或设备,研发团队在此期间也要靠降低影响的设计(插值缓冲、输入重复发送、重连)撑住;原因在服务器代码时,运维团队加设备也只是暂时推迟问题。经常混淆的边界规定如下。

  • 挂机后掉线:玩家路由器和运营商设备的空闲超时我方改不了,而这些映射只有靠从内部往外发的数据包才能可靠地维持。所以由客户端发送心跳,断开时自动重连;服务器响应心跳,收不到时先清理连接,再用会话令牌接续。运维团队告知我方设备的超时值,必要时调大。
  • 连接队列(backlog)溢出:实际的上限在服务器代码的 listen 参数和 accept 循环,所以服务器开发是主责;服务器/OS 负责内核上限(somaxconn)和 SYN cookie。
  • 云:安全组和实例的连接跟踪由服务器/OS 负责,网络 ACL、VPC 路由、云负载均衡器由网络负责。

表中的“先找”是第一个要找的负责方,按符合这种现象的原因卡片的主责计数决定。列出两个时,前一个是担任主责的卡片最多的一方,后一个是一开始就要一起找来的一方。

现象研发团队要做的事运维团队要做的事优先查看的指标
特定运营商、地区的丢包、抖动大
先找运维团队网络
自适应插值缓冲、输入叠加发送、抗丢包的 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 利用率、建议改用有线等提示文案无法直接修复。同一运营商、地区的反馈集中时,重新归类为线路问题反馈中的线路与设备信息、同一运营商与地区的占比

转交时要附上的信息

研发团队 → 运维团队

  • 精确时间(到秒,注明时区)和持续时长,现在是否仍在发生
  • 服务器、分线 ID,影响范围(只有我、特定运营商、全服)和受影响人数(与同时在线人数相比)
  • 症状名称和表现:掉线时给出掉线前的空闲时长,卡住时给出时长和重复周期
  • 受影响者的 IP、端口、运营商、地区(多条路径中有一条不良时,要有端口才能分辨),游戏使用的协议(TCP、UDP)和服务器端口
  • 游戏侧指标:tick 处理时间、ping 与丢包分布、重传增加的连接数、断线原因(心跳超时、RST 连接重置等)
  • 当前的心跳间隔、服务器的无响应判定时长、重试方式
  • 最近有无发布、配置变更,已确认并排除的原因

运维团队 → 研发团队

  • 同一时刻的设备与线路指标(利用率、丢弃与错误计数器、会话数)和服务器 OS 指标(ListenOverflows、conntrack 使用率、CPU steal)
  • 路径上设备的超时与上限值:负载均衡器和防火墙的空闲超时、安全组的连接跟踪超时、会话数与每秒包数上限
  • 设备与线路的变更记录和计划中的操作(更换、配置变更、备份与 cron、运营商维护公告)
  • 向运营商、云厂商提交的工单号和预计回复时间
  • 临时措施(绕行、放宽上限)和恢复原状的时间点
  • 原因所在环节和结论、防止复发的计划
  • 游戏侧需要做的调整(心跳间隔、重试方式、连接数限制等)

两个团队共同:指定一名故障处理负责人,在同一个频道里按时间顺序记录,并提前告知下一次同步进展的时间。主责转到另一个团队时,把这份记录一并转交,避免重复做同样的确认。结束后,用同一份记录修正相应原因卡片的负责方和要做的事。

客户端游戏进程

在玩家电脑或手机上运行的游戏程序本身。即使网络完美,这里的帧一旦延迟,画面就会一卡一卡。网络不好时能把问题掩盖得多好,也是在这里决定的。

游戏每秒大约重复 60 次同样的工作:读取输入、处理收到的数据包、把游戏状态推进一步、绘制画面。这个循环跑一次就是一帧,60 FPS 时每帧有 16.7 ms(以 30 FPS 运行的手游是 33.3 ms)。一帧延迟多久,画面就停住多久,然后在下一帧把落后的进度一次性补上。

在网络方面,客户端要做的是“补足缺失的信息”。其他玩家的位置是服务器断断续续发来的,所以要把中间连起来画(插值);数据包中断时要靠推测继续移动(外推);自己的角色则不等服务器确认,先动起来显示(预测)。这些技术失败时的样子,就是瞬移、拉回、一卡一卡。

打个比方

游戏客户端就像制作直播画面的电视台导播间。现场(服务器)的照片断断续续地传来,导播间把中间自然地接起来,看起来就像视频。照片晚到时没有照片可接,画面就会停住;导播间自己太忙,直播也会中断。

这一层导致卡顿的原因

帧耗时尖峰 Frame hitch

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·客户端开发

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

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

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

症状: 卡住, 一卡一卡 · 主责 研发团队·客户端开发

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

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

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

症状: 隐身/幽灵实体, 一卡一卡, 卡住 · 主责 研发团队·客户端开发 · 配合 外部·外部

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

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

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

症状: 一卡一卡, 操作延迟 · 主责 研发团队·客户端开发

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

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

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

症状: 快进, 操作延迟 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

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

症状: 一卡一卡 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

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

症状: 瞬移, 一卡一卡 · 主责 研发团队·客户端开发

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

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

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

症状: 拉回 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

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

症状: 一卡一卡, 快进, 慢动作 · 主责 研发团队·客户端开发

时钟同步误差 Clock sync error

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

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

症状: 一卡一卡, 吞操作/回档 · 主责 研发团队·客户端开发

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

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

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

症状: 一卡一卡 · 主责 研发团队·客户端开发

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

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

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

症状: 操作延迟, 一卡一卡 · 主责 研发团队·客户端开发 · 配合 外部·外部

客户端内存泄漏 Client memory leak

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

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

症状: 一卡一卡, 掉线 · 主责 研发团队·客户端开发

客户端崩溃 Client crash

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

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

症状: 掉线 · 主责 研发团队·客户端开发 · 配合 外部·外部

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

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

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

症状: 一卡一卡, 卡住, 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

客户端操作系统与设备

游戏运行在 Windows、Android、iOS 上,与其他程序共用 CPU、内存、网络。操作系统给游戏分配 CPU 太晚、为了省电降低速度,或者把后台应用挂起时,就会卡顿。

操作系统(OS)的调度器(决定轮到谁使用 CPU 的功能)把 CPU 时间分给多个程序。游戏、杀毒软件、浏览器、更新程序都在等“轮到自己”,OS 以几 ms 到几十 ms 为单位(时间片)轮流分配核心。OS 会给前台的游戏稍高一点的优先级,但要做的事比核心多时,游戏也得等,这段等待就会拖慢帧。

网络也要经过 OS。网卡、Wi-Fi 芯片收到的数据包,先放进驱动和 OS 的接收缓冲区,等游戏来取。游戏太忙、取得太晚,缓冲区就会溢出;一次全部取出,就会出现快进。在手机上特别要注意的是,OS 为了省电会让无线连接进入省电状态,也会频繁挂起应用本身。

打个比方

OS 就像让多位厨师共用唯一一间厨房的主厨。即使游戏正在做急菜,只要名叫“杀毒扫描”的厨师占住了灶眼,游戏就得等。厨房太热时(发热),主厨还会把火调小。

这一层导致卡顿的原因

后台进程占用 CPU Background CPU contention

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

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

症状: 一卡一卡, 快进 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 操作延迟 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 一卡一卡 · 主责 研发团队·客户端开发

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

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

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

症状: 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

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

症状: 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维

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

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

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

症状: 一卡一卡, 连不上/无限加载 · 主责 外部·外部 · 配合 研发团队·客户端开发

接收缓冲区溢出 Socket receive buffer overflow

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

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

症状: 瞬移, 快进 · 主责 研发团队·客户端开发

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

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

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

症状: 卡住, 一卡一卡 · 主责 外部·外部 · 配合 研发团队·客户端开发

显存(VRAM)不足 VRAM over-commit

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·客户端开发 · 配合 外部·外部

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

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

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

症状: 一卡一卡, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 卡住 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 操作延迟, 快进 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 快进, 一卡一卡, 掉线 · 主责 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 卡住, 掉线 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 操作延迟 · 主责 外部·外部 · 配合 研发团队·客户端开发

家庭网络:Wi-Fi、路由器、移动网络

数据包离开家之前的最后几米。距离虽短,但相当一部分卡顿反馈都出在这里。因为 Wi-Fi 是多台设备共用同一个无线信道,路由器则把全家人的流量排进同一个队列发出去。

Wi-Fi 要和邻居的路由器共用同一个无线信道(频段),2.4 GHz 频段还会和蓝牙、微波炉重叠。发送时发生冲突就稍等一下再重发,这种重传累积起来,数据包就会忽快忽慢地到达。ping 的平均值看着没问题,却时不时跳一下,是 Wi-Fi 的典型表现。

路由器是家里所有设备上网时都必须经过的设备。发出的量超过宽带线路能承接的速度时,路由器或调制解调器(猫)里就会形成队列;没有队列管理功能(SQM)的设备,会让这个队列一直堆到几百 ms 的长度。昂贵的路由器如果关掉了这个功能也一样。弟弟妹妹上传视频的那一刻,游戏数据包也得排在那个队列的最后面等。这种现象叫缓冲区膨胀(bufferbloat)。

路由器还会把“内部设备 ↔ 外部服务器”的连接记录在 NAT 表里,一段时间没有数据包往来就会从表中删除。这是挂机后掉线的常见原因。移动网络还要再加上基站切换、无线省电状态、信号弱等因素。

打个比方

路由器就像小区唯一的出入口。搬家卡车(视频上传)排成长队时,送急件的摩托车快递(游戏数据包)也得在卡车后面等。聪明的路由器(SQM)会单独开一条快递专用道。

这一层导致卡顿的原因

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

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

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

症状: 一卡一卡, 瞬移, 拉回 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 操作延迟 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 操作延迟, 快进, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发

NAT 映射过期 NAT mapping timeout

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

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

症状: 掉线 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发

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

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

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

症状: 一卡一卡, 连不上/无限加载, 掉线 · 主责 外部·外部

基站切换(移动中) Cellular handover

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

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

症状: 卡住, 瞬移, 掉线 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发

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

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

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

症状: 操作延迟 · 主责 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 瞬移, 掉线 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 一卡一卡, 瞬移, 卡住 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 连不上/无限加载 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发

公网链路:运营商网络与长途链路

离开家的数据包,要经过运营商网络、多家运营商之间的互联链路,有时还要经过海底光缆,才能到达服务器所在的数据中心。这一段的延迟大多由距离和路径选择(路由)决定,很多时候游戏公司无法直接解决。

光在光纤中每秒约走 20 万 km。服务器在 1,000 km 外时,往返至少需要 10 ms,只要走的是光纤,这个数字无论怎么升级服务器或设备都无法缩短。实际的数据包要沿着运营商之间的互联点(对等互联)绕路走,所以通常要花理论值的 1.5~2 倍。像韩国到欧洲这种直线方向上几乎没有大型光缆的线路,要绕经东南亚和苏伊士或美国,达到 2.5~3 倍(往返约 230~270 ms)。

问题在于这条路径会随时间和情况变化。晚上 9~11 点前后大家都在看视频,运营商之间的互联链路容易拥塞;路由信息(BGP)变化时,数据包会有几秒到几十秒(少数情况下几分钟)到不了目的地;海底光缆断了,就会连续几周绕远路。如果卡顿是“只有特定运营商的用户”“只在晚上”“只在海外”,就先怀疑这一层。

打个比方

运营商网络就像高速公路网。从首尔到釜山的路即使不堵,距离本身也要花时间;下班高峰的收费站(对等互联点)会堵;出了事故,导航会让你绕一大段远路。

这一层导致卡顿的原因

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

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

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

症状: 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·网络运维, 研发团队·服务器开发

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

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

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

症状: 操作延迟, 一卡一卡, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发

路由绕行 Suboptimal routing

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

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

症状: 操作延迟 · 主责 运维团队·网络运维 · 配合 外部·外部

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

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

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

症状: 一卡一卡, 瞬移, 拉回 · 主责 运维团队·网络运维 · 配合 外部·外部

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

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

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

症状: 操作延迟, 瞬移 · 主责 外部·外部 · 配合 运维团队·网络运维

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

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

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

症状: 卡住, 瞬移 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部

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

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

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

症状: 瞬移, 拉回, 一卡一卡 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 外部·外部

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

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

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

症状: 操作延迟, 瞬移 · 主责 外部·外部 · 配合 研发团队·服务器开发, 运维团队·网络运维

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

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

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

症状: 连不上/无限加载, 掉线, 瞬移 · 主责 外部·外部 · 配合 研发团队·客户端开发, 研发团队·服务器开发, 运维团队·网络运维

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

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

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

症状: 瞬移, 卡住, 掉线 · 主责 外部·外部

DNS 故障/延迟 DNS failure / slowness

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

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

症状: 连不上/无限加载 · 主责 外部·外部 · 配合 研发团队·客户端开发

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

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

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

症状: 瞬移, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 外部·外部

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

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

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

症状: 掉线, 连不上/无限加载 · 主责 研发团队·客户端开发 · 配合 研发团队·服务器开发, 运维团队·网络运维

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

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

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

症状: 操作延迟, 瞬移, 连不上/无限加载 · 主责 外部·外部 · 配合 运维团队·网络运维, 研发团队·服务器开发

数据中心网络设备

在到达服务器之前,数据包要依次经过路由器、DDoS 防护设备、防火墙、负载均衡器、交换机。平时这一段连 1 ms 都用不了,但只要有一台设备容量满了或出了故障,全服几千名玩家都会同时受影响。

每种设备的职责不同。路由器决定路径,DDoS 防护设备过滤攻击流量,防火墙只放行允许的连接,并用会话表跟踪所有连接。负载均衡器把进来的连接分给多台服务器,交换机把服务器彼此连接起来。

这些设备的共同弱点是表的大小和缓冲区大小。防火墙会话表满了就接不了新连接;负载均衡器会在一段时间后删掉空闲连接;交换机的小缓冲区在多台服务器同一时刻向几千人集中发包时(世界 BOSS 刷新),不到 1 ms 就会溢出。另外,一台设备故障、切换到备用设备(故障切换)的那几秒里,所有人都会卡住。

打个比方

数据中心入口就像机场的安检口和登机口。安检口(防火墙)只放名单上的人通过,名单写满了就不能再收人。登机口工作人员(负载均衡器)会把长时间安静坐着的乘客当作“已经离开的人”,从名单上划掉。

这一层导致卡顿的原因

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

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

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

症状: 连不上/无限加载, 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发

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

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

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

症状: 操作延迟, 连不上/无限加载, 瞬移 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

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

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

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

症状: 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发

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

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

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

症状: 掉线 · 主责 运维团队·系统运维 · 配合 研发团队·客户端开发, 研发团队·服务器开发

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

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

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

症状: 连不上/无限加载, 吞操作/回档 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

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

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

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

症状: 慢动作, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发

交换机微突发 Switch microburst drops

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

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

症状: 瞬移, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维, 运维团队·系统运维

数据中心线路饱和 Uplink saturation

版本更新包分发、日志传输、备份与游戏共用同一条线路时,线路会被占满。

起因: 大流量传输占用同一条线路 → 结果: 线路排队和丢包增加 → 画面表现: 全服 ping 升高、出现瞬移

症状: 操作延迟, 瞬移 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维

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

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

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

症状: 卡住, 掉线 · 主责 运维团队·网络运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发

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

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

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

症状: 瞬移, 拉回 · 主责 运维团队·网络运维

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

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

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

症状: 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发

服务器网卡(NIC)

插在服务器上的网卡每秒要接收几十万到几百万个数据包,再交给 CPU。这里处理一旦积压,服务器程序连包来过都不知道,包就丢了。

网卡把到达的数据包依次放进环形缓冲区(循环使用固定数量槽位的接收缓冲区),再通知 CPU“包到了”(中断)。CPU 从环形缓冲区取出数据包交给 OS。进来的速度比 CPU 取的速度快,槽位就会全部占满,之后来的包都会被丢弃。只有网卡统计(ethtool -S)里的数字悄悄上涨,游戏服务器日志里不会留下任何错误,所以这种卡顿很难查。

现在的网卡有多个接收队列(环形缓冲区),可以把通知分散到多个 CPU 核心(RSS),但如果没配置好,或者流量都集中到一个队列,就只有一个核心跑到 100%,成为瓶颈。云服务器在网卡前面另有每秒包数、带宽、连接数上限,超出的部分在到达服务器之前就被丢弃。这在 CPU、环形缓冲区这类常规指标上看不出来,AWS 上只记录在 ENA 驱动统计里(ethtool -S 的 pps_allowance_exceeded 等)。

打个比方

网卡是小区的信报箱,环形缓冲区是信箱的格数,中断是邮递员按门铃。信件大量涌来而取信的只有一个人时,格子就会塞满,信掉到地上。RSS 就是安排多个人取信。

这一层导致卡顿的原因

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

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

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

症状: 瞬移, 拉回, 操作延迟 · 主责 运维团队·系统运维

环形缓冲区不足 RX ring buffer overflow

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

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

症状: 瞬移, 吞操作/回档 · 主责 运维团队·系统运维

中断合并过度 Interrupt coalescing

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

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

症状: 操作延迟 · 主责 运维团队·系统运维

超出云 PPS 上限 Cloud PPS / bandwidth allowance

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

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

症状: 瞬移, 吞操作/回档 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部

网卡带宽饱和 NIC bandwidth saturation

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

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

症状: 操作延迟, 瞬移 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 一卡一卡 · 主责 运维团队·系统运维 · 配合 外部·外部

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

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

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

症状: 卡住, 快进, 瞬移, 掉线 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 外部·外部

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

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

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

症状: 卡住, 掉线 · 主责 运维团队·系统运维

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

这是把多个数据包合并成一个、以减轻 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)溢出 Listen backlog / SYN queue overflow

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

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

症状: 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发

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

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

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

症状: 连不上/无限加载 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

内核 socket 缓冲区不足 Small socket buffers

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

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

症状: 瞬移, 快进 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

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

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

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

症状: 一卡一卡, 慢动作 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 一卡一卡, 卡住 · 主责 运维团队·系统运维 · 配合 外部·外部

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

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

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

症状: 一卡一卡, 慢动作 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

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

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

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

症状: 操作延迟, 慢动作 · 主责 运维团队·系统运维

OOM Killer Out-of-memory killer

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

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

症状: 掉线, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 卡住, 一卡一卡 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

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

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

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

症状: 快进, 掉线, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 一卡一卡, 慢动作 · 主责 运维团队·系统运维

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

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

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

症状: 操作延迟, 一卡一卡, 慢动作 · 主责 运维团队·系统运维

服务器 conntrack 表耗尽 conntrack table full

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

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

症状: 连不上/无限加载, 瞬移 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 研发团队·客户端开发

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

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

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

症状: 吞操作/回档, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

Socket 与协议:TCP、UDP、socket 选项

这部分决定游戏怎样通过网络收发数据。同样的线路,用什么协议、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 队头阻塞 Head-of-line blocking

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

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

症状: 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

TCP RTO 与指数退避 RTO and exponential backoff

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

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

症状: 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

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

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

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

症状: 操作延迟 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

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

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

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

症状: 卡住, 慢动作 · 主责 研发团队·服务器开发

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

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

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

症状: 瞬移, 掉线 · 主责 研发团队·服务器开发

keepalive 默认 2 小时 TCP keepalive defaults

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

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

症状: 连不上/无限加载, 隐身/幽灵实体 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

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

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

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

症状: 瞬移 · 主责 研发团队·服务器开发

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

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

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

症状: 吞操作/回档, 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

空闲后的慢启动 Slow start after idle

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

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

症状: 操作延迟, 隐身/幽灵实体 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

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

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

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

症状: 快进, 操作延迟 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

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

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

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

症状: 掉线 · 主责 研发团队·服务器开发

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

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

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

症状: 慢动作, 操作延迟 · 主责 研发团队·服务器开发

SO_REUSEPORT 分配不均 SO_REUSEPORT imbalance, stuck worker

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

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

症状: 连不上/无限加载, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 掉线, 卡住 · 主责 研发团队·服务器开发

服务器游戏进程:tick 与线程

这是实际计算游戏逻辑的程序。移动、战斗、怪物 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 overrun

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

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

症状: 慢动作, 操作延迟, 一卡一卡 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 慢动作, 一卡一卡 · 主责 研发团队·服务器开发

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

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

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

症状: 操作延迟, 瞬移, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 慢动作, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

锁竞争 Lock contention

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

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

症状: 操作延迟, 卡住 · 主责 研发团队·服务器开发

死锁 Deadlock

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

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

症状: 卡住, 掉线 · 主责 研发团队·服务器开发

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

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

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

症状: 卡住, 一卡一卡 · 主责 研发团队·服务器开发

消息队列积压 Mailbox / job queue backlog

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

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

症状: 操作延迟, 吞操作/回档 · 主责 研发团队·服务器开发

定时器集中触发 Synchronized timers

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

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

症状: 卡住, 一卡一卡 · 主责 研发团队·服务器开发

寻路计算激增 Pathfinding storms

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

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

症状: 慢动作 · 主责 研发团队·服务器开发

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

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

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

症状: 操作延迟 · 主责 研发团队·服务器开发

服务器崩溃 Server process crash

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

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

症状: 掉线, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

线程池耗尽 Thread pool starvation

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

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

症状: 连不上/无限加载, 操作延迟, 卡住 · 主责 研发团队·服务器开发

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

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

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

症状: 卡住, 掉线 · 主责 研发团队·服务器开发

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

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

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

症状: 操作延迟, 快进, 慢动作 · 主责 研发团队·服务器开发

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

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

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

症状: 卡住, 操作延迟, 快进 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发

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

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

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

症状: 慢动作, 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发

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

新内容、特效、同步项增加了数据包的大小和频率,原本正常的服务器在版本更新后开始碰到 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)的时候,就得停下手里的菜。

这一层导致卡顿的原因

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

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

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

症状: 卡住, 快进 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发

内存分配暴增 Allocation storms

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发

内存泄漏 Memory leak

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

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

症状: 慢动作, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 慢动作, 卡住, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

swap Swapping

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

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

症状: 慢动作, 卡住 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

缓存未命中 CPU cache misses

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

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

症状: 慢动作 · 主责 研发团队·服务器开发

内存碎片 Heap fragmentation

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

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

症状: 慢动作, 掉线 · 主责 研发团队·服务器开发

NUMA 远端内存 Remote NUMA access

在装有两颗 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 是仓库门的数量。门少了,存取货物的人就得排队。突发积分是短时间冲刺的体力,用完了就只能恢复到步行速度。

这一层导致卡顿的原因

同步写日志 Synchronous logging

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

fsync 风暴 fsync storms

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

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

症状: 一卡一卡, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 运维团队·数据库运维

云盘突发积分耗尽 Burst credit depletion

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

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

症状: 一卡一卡, 慢动作, 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维

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

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

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

症状: 操作延迟, 卡住 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发, 运维团队·数据库运维

磁盘写满 Disk full

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

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

症状: 掉线, 吞操作/回档 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发

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

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

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

症状: 一卡一卡, 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维

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

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

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

症状: 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

写入 core dump Core dump writing

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

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

症状: 连不上/无限加载 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

HDD 寻道延迟 HDD seek latency

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

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

症状: 操作延迟 · 主责 运维团队·系统运维 · 配合 运维团队·数据库运维, 研发团队·服务器开发

数据库

角色、物品、货币、交易记录这类绝对不能丢的东西都存在这里。DB 一慢,战斗还正常,物品却迟迟不到账,交易失败,登录一直完成不了。如果游戏服务器的设计是要等 DB,整个野外场景都会卡住。

游戏服务器会预先和 DB 建好几个连接(连接池),轮流使用。一条查询(发给 DB 的请求)耗时太长,这个连接就一直被占用;池里的连接都被占用时,其余请求只能在队列里等。查询变慢的常见原因有两个:没有索引(相当于书的目录),只能读整张表(全表扫描);或者多个请求同时要改同一行,在等锁。

DB 为了可靠、也为了承接大量请求,会用到多种机制:分担读请求的从库、故障时切过去的备用库、定期把变更集中写入磁盘的检查点。检查点集中时会短暂变慢。从库延迟时会出现“刚买的物品看不到”;在复制延迟的状态下切换到备用库,则会出现“一上线发现回到了不久前的状态”这类吞操作/回档症状。如果游戏服务器几分钟才保存一次角色,服务器崩溃时就会变成“回到了 10 分钟前”。

打个比方

DB 就像银行柜台。柜台数量(连接池)是固定的,一个业务要翻遍整本账(全表扫描)时,后面的业务都得等。所有人都要开同一个保险柜(热点行)时,只能一个一个进去。

这一层导致卡顿的原因

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

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

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

症状: 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

热点行锁竞争 Hot row lock contention

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

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

症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

DB 死锁 Database deadlock

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

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

症状: 吞操作/回档, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

连接池耗尽 Connection pool exhaustion

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

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

症状: 连不上/无限加载, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

复制延迟 Replication lag

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

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

症状: 吞操作/回档 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

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

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

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

症状: 操作延迟, 一卡一卡 · 主责 运维团队·数据库运维

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

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

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

症状: 连不上/无限加载, 操作延迟 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

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

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

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

症状: 连不上/无限加载, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

大型批处理任务 Batch jobs during service

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

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

症状: 操作延迟, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

DB 故障切换 Database failover

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

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

症状: 吞操作/回档, 卡住, 掉线, 连不上/无限加载 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

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

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

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

症状: 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

缓存雪崩 Cache stampede / thundering herd

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

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

症状: 操作延迟, 卡住, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

长事务 Long-running transaction / MVCC purge lag

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

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

症状: 操作延迟, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

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

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

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

症状: 卡住, 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维

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

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

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

症状: 操作延迟, 连不上/无限加载 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

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

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

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

症状: 操作延迟, 吞操作/回档, 连不上/无限加载 · 主责 运维团队·数据库运维 · 配合 研发团队·服务器开发

服务器架构与运维

现在的 MMO 通常由登录、网关、野外、副本、聊天、组队、拍卖行、缓存、DB 等服务器相互调用来运转。一处出故障就会蔓延到相连的地方,发布、扩容、维护这类运维操作也会造成卡顿。

把服务器拆开,可以防止一处故障蔓延到全局,但代价是会产生调用链(服务器依次调用服务器的链路)。比如游戏服务器调用拍卖行服务器,拍卖行服务器再调用缓存和 DB。链路末端的服务器一变慢,前面的服务器就会一边等响应一边持续占着线程和连接,最终连看似无关的功能也停住。这叫级联故障,靠超时和熔断器(暂时切断持续失败的调用的机制)来阻止蔓延。

运维操作也是卡顿的原因。发布更新时的重启、人多时自动扩容所需的几分钟、切换场景时把角色迁到另一台服务器的过程、机器人(bot)和宏造成的隐形负载,在玩家看来都是“卡”。

打个比方

服务器架构就像多个部门互相审批的公司。审批链末端的某个部门(DB)一慢,前面的部门都得拿着文件排队,最后整个公司的工作都停下来。超时是“超过 10 分钟没回复就先驳回”的规则,熔断器是“连续驳回时,一段时间内不再把文件送到那个部门,直接退回”的规则。

这一层导致卡顿的原因

经由网关/代理 Gateway / proxy hop

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

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

症状: 操作延迟, 掉线 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维, 研发团队·客户端开发

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

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

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

症状: 连不上/无限加载, 卡住, 掉线, 拉回 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

级联故障 Cascading failure

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

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

症状: 卡住, 操作延迟, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维

辅助服务器故障 Auxiliary service outage

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

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

症状: 吞操作/回档, 连不上/无限加载 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

发布/重启 Deploy / rolling restart

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

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

症状: 掉线, 连不上/无限加载, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

弹性伸缩延迟 Autoscaling lag

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

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

症状: 慢动作, 连不上/无限加载 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

日志/监控过载 Logging / monitoring overhead

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

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

症状: 一卡一卡, 卡住 · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维

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

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

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

症状: 吞操作/回档 · 主责 运维团队·系统运维 · 配合 研发团队·服务器开发

脚本/机器人过多 Bots and macros

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

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

症状: 慢动作, 操作延迟 · 主责 研发团队·服务器开发 · 配合 运维团队·网络运维

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

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

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

症状: 连不上/无限加载, 吞操作/回档 · 主责 外部·外部 · 配合 研发团队·服务器开发

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

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

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

症状: 操作延迟, 拉回, 吞操作/回档 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·网络运维, 外部·外部

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

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

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

症状: 连不上/无限加载, 吞操作/回档 · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·客户端开发

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

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

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

症状: 连不上/无限加载, 掉线 · 主责 研发团队·服务器开发 · 配合 研发团队·客户端开发, 运维团队·系统运维

T1工具

诊断助手

收到卡顿反馈时,只要选出“谁、何时、什么表现”这三项,就会从本白皮书收录的原因中,按得分顺序列出最匹配的候选。虽然不能据此确诊,但足以决定先问哪个团队。

T2工具

用观测数据判定

收到反馈或告警时,按范围 → 时间点 → 层级的顺序缩小范围。异常集中在哪里,对确定负责方影响最大;和什么同时发生,可以缩小原因范围;哪一层的指标异常,用来最终确认。结合每张原因卡片上的“监控图上”和“确认方法”,就能按监控图形态挑出候选,并直接找到查看位置。

判定流程

1 范围

异常集中在哪里

  • 特定国家/地区、运营商(ASN) → 运维团队网络 外部运营商
  • 特定服务器、分线、场景 → 主机指标正常则找研发团队服务器,异常则找运维团队服务器/OS
  • 特定操作系统、设备、构建版本 → 研发团队客户端
  • 一个人、一户 → 外部玩家环境(多人表现相同时找研发团队客户端)
  • 所有人同时出现 → 公共资源(DB、负载均衡器、网关)或刚上线的发布
2 时间点

与什么同时发生

3 层级

哪一层的指标异常

  1. 网络:RTT、丢包、重传率,接口错误与丢弃
  2. 主机:各核心 CPU、CPU 窃取时间、softirq、网卡丢弃、内存压力
  3. 游戏服务器:tick 耗时、socket 接收队列(Recv-Q)、各线程 CPU、GC 日志
  4. DB:查询延迟、锁等待、复制延迟
  5. 客户端:帧耗时、网络状态面板、崩溃报告

判定信号表

查看什么看到这种情况先找谁
服务器 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 种形态,每种都汇总了会产生这种形态的原因。原因卡片上的小图也是同样的形态。实线是主要查看的指标,虚线是一起看的指标(人数、等待、错误等),浅色虚线是平时水平。

偶发随机尖峰

没有固定间隔,不规则地冲高后很快回落。

帧耗时尖峰, 过度外推(航位推测), 客户端预测不一致, 固定时间步长追赶失控, 后台进程占用 CPU, 客户端内存不足/swap, 网卡省电/驱动问题, Wi-Fi 干扰/信号弱, 5G↔4G 频繁切换(5G 覆盖边缘), 线路质量差, 环形缓冲区不足, 虚拟化开销/邻居干扰, 内核 socket 缓冲区不足, CPU 窃取时间(虚拟机), 内存回收/规整导致的停顿, 系统时钟跳变(NTP step), 慢客户端导致的阻塞发送, 可靠 UDP 重传配置, RST 强制关闭导致最后的数据丢失, 游戏线程中的同步调用, 同步写日志, 服务器端懒加载, DB 死锁, Redis 慢命令, 日志/监控过载, 帧同步中等待最慢的玩家, 回滚网络代码预测失败, 没有时间戳、一到就播放, 过于严格的服务器校验, 指令同步的路径计算不一致, 视野注册顺序错乱, 基准快照丢失, 离开通知丢失(幽灵实体), 实体 ID 重用混淆, 延迟飙升导致的虚假重传

随人数/负载上升

同时在线人数或聚在一处的人数一增加,它就以更陡的斜率跟着上涨。

大规模同屏渲染负载, 主线程数据包处理瓶颈, 接收缓冲区溢出, 同一设备上其他应用占用带宽, 缓冲区膨胀(路由器队列), 交换机微突发, 线程过多与上下文切换, 容器 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 延迟或丢失(上行饱和)

连接成批断开

在线连接数骤降,或者断线数瞬间飙升。

移动应用切到后台, Wi-Fi ↔ 4G/5G 切换, NAT 映射过期, 运营商共享 IP(CGNAT), 负载均衡器空闲超时, 云安全组连接跟踪过期, 网络设备故障切换(主备切换), OOM Killer, Windows UDP socket 的 WSAECONNRESET 错误, 死锁, 服务器崩溃, 死循环/逻辑失控, 写入 core dump, DB 故障切换, 存盘间隔过长导致进度丢失, 辅助服务器故障, 发布/重启, TLS 证书过期/配置错误, 连接中途 NAT/负载均衡器映射过期

不改游戏代码能确认多少

这里统计了每个原因最容易的确认手段。运维工具指用操作系统、网络、云、数据库工具和运行时启动参数(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 等待。即使线路正常,服务器或电脑忙时也会升高

马上能做的事,以及要加进游戏代码的事

不改游戏代码
  • 补充维度:给连接日志、负载均衡器日志中的客户端 IP 加上国家/地区、运营商(ASN),让“只有海外”“只有特定运营商”一目了然。
  • 连接质量:在服务器上用 ss -ti 或 eBPF 工具采集每条连接的 RTT、重传,按 ASN 查看。
  • 从服务器外部看服务器内部:socket 队列、各线程 CPU(pidstat -t)、运行队列延迟、只靠启动参数就能开启的 GC 日志。
  • 路径测量:从目标国家/地区、运营商一侧做拨测(RIPE Atlas、云区域中的测量服务器),并跑 mtr。
  • 变更记录:把发布、版本更新、配置、网络操作以竖线标在所有监控图上。这是判定问题是否始于“版本更新之后”的出发点。
少量改动游戏代码
  • 客户端汇总上报:每 30~60 秒上报 RTT p50/p95、抖动、丢包、FPS、帧尖峰次数、构建版本、服务器与分线。
  • 服务器 tick 指标:tick 耗时 p50/p99、tick 超出预算次数、各场景人数、每条连接的发送队列。
  • 断线原因代码:心跳超时、RST、服务器主动断开、认证失败、维护,两端使用同一套代码。
  • 会话 ID 与时间:所有日志都带上会话、角色、服务器 ID 和已同步的 UTC 时间。
  • 卡顿反馈按钮:把最近 60 秒的 RTT、FPS、tick 空档与会话 ID 一起上传。
T3工具

案例与流程

这里汇总了两种常见情况的排查顺序,以及原开发商、运营方自己公开的真实故障案例。每个步骤和案例都链接到相关的原因卡片。

分场景排查流程

版本更新后卡顿

某次版本更新或发布之后卡顿反馈变多时使用。“这次更新以后就不对劲”的反馈集中出现,或监控图从某一时刻起台阶式上升并一直停在高位时,都按这个流程排查。

  1. 确定开始时间,收集前后所有变更: 找出反馈最早集中出现的时刻和监控图台阶式上升的时刻,把这前后上线的变更一个不漏地列出来。客户端更新、服务器发布、配置变更、DB 表结构变更(DDL)与重启、网络和防火墙作业、基础设施更换(实例规格、内核、驱动)都要一起看。如果每次发布都用监控工具的标注(annotation)功能在所有监控图上留下一条竖线,这一步很快就能完成。游戏版本更新和基础设施作业在同一个维护时段上线时,两者都要留作候选。优先联系:提交变更的研发团队和运维团队双方。
  2. 划分范围:版本、设备、服务器、地区: 看异常集中在哪个维度。只有新版本客户端的用户变差,先怀疑客户端;只有特定 OS、显卡、设备变差,先怀疑客户端性能或驱动;只有特定服务器、分线、场景变差,先怀疑服务器;只有特定国家/地区、运营商变差,先怀疑网络路径;所有人同时变差,先怀疑公共资源(DB、负载均衡器、网关)或刚上线的服务器发布。如果客户端遥测带有版本号,就把旧版本和新版本的 ping、FPS、帧耗时尖峰、掉线次数并排对比。ping 没变而只有 FPS 变差,比起网络,更可能是客户端性能的问题。优先联系:集中在版本、设备上,找研发团队(客户端);集中在服务器、分线上,主机指标正常时找研发团队(服务器),异常时找运维团队(服务器/OS);集中在国家/地区、运营商上,找运维团队(网络)。
  3. 在同一时段对比新旧版本: 只对比发布前后,星期、时段、活动带来的变化会混在一起,影响判断。条件允许时,先把新版本放到部分服务器上(金丝雀),与同一时段运行旧版本的服务器(对照组)并排对比 tick 耗时 p50/p99、tick 超预算次数、CPU、内存、错误率。如果已经全量发布,就和上周同一天的同一时段对比。只看全服平均值会掩盖部分服务器、场景的问题,所以要按服务器、场景拆开看。优先联系:研发团队(服务器)。
  4. 对比更新前后的流量指纹: 即使不了解服务器代码,也能用网络侧看得到的数值确认这次更新是否改变了流量形态。对比更新前后每个玩家的每秒包数(pps)和字节数、平均和最大数据包大小、连接数,以及每个 tick 集中发出的突发发送大小。UDP 数据包开始超过路径 MTU(通常为 1,500 字节)时,就会发生 IP 分片。只要丢一个分片,整个数据包就丢了,还有些 NAT、防火墙会直接丢弃分片。途经 MTU 较小链路(隧道、VPN)的玩家,只有大包会消失。如果 pps 增加了,要看是否碰到了云实例的 PPS 上限,或防火墙、DDoS 防护设备的处理上限。优先联系:指纹变了,附上证据找研发团队(服务器);指纹没变、只有丢包和重传增加,找运维团队(网络)。
  5. 对比更新前后 DB 查询的种类和次数: DB 延迟上升时,先看查询量(QPS)是否也一起上升。PostgreSQL 的 pg_stat_statements 和 MySQL Performance Schema 的 digest 汇总,会把只有参数值不同的查询归为一类,统计执行次数和总耗时。对比更新前后的 Top 查询列表,就能找出新出现的查询、次数翻了几倍的查询(N+1),以及不走索引、读全表的查询(MySQL 看 SUM_NO_INDEX_USED 列)。优先联系:QPS 或查询形态变了,找研发团队(服务器);查询没变、只有延迟增加,找运维团队(DB:执行计划、IOPS、锁)。
  6. 用主机和服务器进程指标区分层级: 不看代码,只用 OS 上看得到的数值区分问题出在服务器进程内部还是主机。服务器 socket 的接收队列(Recv-Q)堆积,说明服务器进程没能及时读取(tick 停顿、GC、锁);只有一个线程跑到 100%,是单线程瓶颈;GC 日志里的停顿时间变长,说明内存使用模式变了。还要看是否带着调高的日志级别发布,导致日志写入增加。反过来,如果 CPU 窃取时间(steal)、CPU 限流、网卡丢包增加,就去看同一时刻变更过的基础设施(实例规格、内核、容器资源上限)。优先联系:进程内部的信号找研发团队(服务器),主机的信号找运维团队(服务器/OS)。
  7. 通过回退确认原因并记录: 只在部分服务器或部分玩家身上撤销最可疑的变更(回滚、关闭功能开关),或把配置改回旧值,看症状是否随之消失。只有回退的一侧好转,原因就确定了。回退本身也可能因重启和冷缓存短暂变慢,不着急的话放在低峰时段做。把结果连同原因 ID 记入故障记录,并把数据包大小、查询次数、tick 耗时的上限加入下次版本更新的发布前检查项。优先联系:提交变更的团队。

新增海外国家/地区

新开服务国家/地区,或新增区域、数据中心时使用。上线前的检查,以及判断“国内没问题,只有新国家的玩家卡”这类反馈时,都可以用这个流程。

  1. 上线前测量当地各运营商的线路质量: 针对目标国家的每家主要运营商(ASN),测量到候选游戏服务器位置的往返时间(RTT)分布、抖动(到达间隔的波动)和丢包。只看一个平均值会掩盖运营商之间的差异,所以要按运营商分别看中位数和第 95 百分位数,并区分晚高峰和凌晨。公开测量网络 RIPE Atlas 可以按国家、ASN 挑选全球的探针发送 ping 和 traceroute;也可以在候选区域临时开一台 VM 来测。途中的设备有时会限制 ICMP 应答,所以条件允许时,也要用和游戏相同的协议、端口测一遍。如果只有某家运营商特别绕经远方城市,就是对等互联或路由问题。运营商选路时比起延迟更看重成本,所以近处的目的地也可能绕远路。优先联系:运维团队(网络);路由问题在运营商一侧时,找外部(运营商、IX)。
  2. 把测量值与游戏设计能承受的上限对比: 把测得的 RTT、抖动与游戏的判定窗口(闪避、弹反这类反应时间)、延迟补偿上限、插值缓冲长度、输入缓冲大小对比。例如弹反判定窗口为 0.2 秒时,往返延迟加上插值缓冲超过这个值的运营商用户,即使及时反应也来不及。如果放宽延迟补偿来迁就他们,挨打的一方又会反馈“明明躲到墙后了还被打中”。超出上限的运营商较多时,运维团队要考虑把区域、边缘 PoP 部署得更近,研发团队要重新评估判定、插值、延迟补偿的参数。本白皮书的“同步方式”一章就是对照标准。优先联系:研发团队(服务器、客户端:设计上限),运维团队(网络:区域、PoP 位置)。
  3. 确认 MTU 和 UDP 能否通过: 确认游戏最大的数据包能否完整通过当地网络。发送设置了禁止分片(DF)标志、大小各不相同的 ping,测出路径 MTU,看途中是否有 PPPoE、隧道、移动网络这类小于 1,500 字节的链路。UDP 等数据报传输的标准(RFC 8899)建议 IPv4 以 1,200 字节作为大部分路径都能通过的基础大小;游戏的最大数据包超过这个值时,要和研发团队商定缩小或拆分发送的方案。还要确认公共 Wi-Fi、公司网络和部分运营商是否封锁 UDP 或游戏端口、是否限速,以及被封时有没有备用通道(TCP、443 端口)。优先联系:运维团队(网络)和研发团队(服务器:数据包大小)。
  4. 测量 NAT/CGNAT 空闲超时,调整心跳间隔: 测量当地家用路由器和移动网络(CGNAT)多久会删除空闲 UDP 连接的映射。每轮测试先让测试设备向服务器发一个数据包建立映射,之后设备不再发送任何数据,由服务器在预设的时间(30 秒、60 秒、120 秒……)过后向设备发包。设备开始收不到这个包的时间,就是该网络的空闲超时。标准(RFC 4787)规定 UDP 映射的过期时间不得短于 2 分钟,并建议默认 5 分钟以上,但各设备的取值差别很大,也有删除得更早的设备。只有从设备发出的数据包才能可靠地刷新映射,所以心跳要由客户端发送,并检查心跳间隔是否不超过测得值与负载均衡器、云安全组空闲超时中最小值的一半。优先联系:研发团队(客户端:心跳间隔;服务器:超时值),运维团队(负载均衡器、安全组配置)。
  5. 检查当地会经过的外部服务和安全设备: 确认当地平台的登录、支付、实名认证能否以正常速度响应,当地 DNS 能否正确解析登录服务器和更新服务器的地址,CDN 是否从靠近该国的节点分发更新包。检查 DDoS 防护和防火墙的国家/地区封禁规则与限速规则是否误伤新国家的 IP 段,尤其要看多个用户共用一个 IP 的 CGNAT 地址段是否被整段封禁。优先联系:运维团队(安全设备、DNS、CDN),外部(平台、支付服务商、运营商)。
  6. 上线后按国家/地区、ASN 拆分查看: 给连接日志、负载均衡器日志里的客户端 IP 打上国家和 ASN 标签,按国家/地区、运营商查看 RTT、重传、掉线次数及原因(心跳超时、RST、服务器踢出)。用 MaxMind GeoLite ASN 这类免费数据库,可以把 IP 转换成 ASN 和组织名称;按照当地的个人信息保护法规,IP 只保留到 /24 或 ASN 粒度。问题只集中在一个 ASN 上,先看该运营商的路由(运维团队、外部);新国家整体都差,先看距离和设计上限(运维团队、研发团队);只在晚上变差,先看对等互联拥塞。如果只有部分玩家 ping 一直偏高,要和研发团队(服务器)一起排查是否因 GeoIP 错误、VPN 或按队长位置分配而被分到了远处的区域。拨测正常而只有玩家侧变差,问题在玩家环境或客户端。
  7. 检查远距离玩家对其他玩家的影响: 远距离接入的玩家多了,影响不只是他们自己的画面变差。网络慢的玩家,输入会扎堆到达;在其他人的画面上,只有这个角色出现快进现象;还会触发服务器的速度、冷却检查,出现拉回或技能被拒。在需要全队配合的机制里,一个人反应慢就会导致整个队伍失败;采用帧同步时,所有人都要等最慢的那个人。新国家上线后,要看老玩家“只有某个角色看起来异常”的反馈是否增多,并与研发团队商定输入缓冲、校验容差、按地区分开匹配等方案。优先联系:研发团队(服务器)。

真实故障案例

只收录了游戏公司和基础设施公司自己公开的故障复盘(postmortem)。摘要只写原文披露的内容,详细经过请看原文。

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 搜索目标的开销。放慢游戏时间的设计无法消除过载,但能让所有人以同样的速度变慢,避免只有部分操作被无限期推迟。 原文

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 2020: League of Legends 欧洲、巴西服务器的边缘主机过载

2020 年 2 月下旬,League of Legends 的 EUW、EUNE、BR 服务器多次发生故障,新开局数大幅减少。匹配、游戏服务器等后端服务的状态全部正常,但几乎没有流量进来。为避免在可能不稳定的集群上开放锦标赛模式(Clash),Riot 把日程推迟了一周。复盘文章没有写明每次故障持续了多久。 三个因素叠加在一起。发往某个服务的请求构造有误,在特定情况下持续失败、不断重试,导致请求量暴增。容器系统与 OS 版本之间存在已知的兼容问题,OS 内部内存一直在泄漏;升级只在 Riot 全部容器环境的约 60% 上完成,欧洲和拉丁美洲集群还在升级中。负责接收互联网流量、过滤后转发给后端的边缘容器,在同一个分片(服务器组)内会分散部署,但不同分片之间没有这种限制,每次故障时都至少有三个分片的边缘容器挤在同一台主机上。暴增的重试压到这台主机上,内存泄漏又让它停摆。

后端服务都回答“状态正常,但没有流量进来”时,就去看它们前面的一层(边缘、网关、负载均衡器)。确认信号是各主机入站连接数是否倾斜,以及特定请求的失败率、重试率。主责是研发团队(服务器:错误的请求和重试方式),容器调度规则、OS 升级、倾斜告警由运维团队(服务器/OS)负责。Riot 修复了请求代码,改成不会让重试激增的方式,并在实现跨分片分散部署之前设置了倾斜告警。 原文

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:自动切换)。告警铺天盖地时,人很容易先怀疑最近遇到过的问题(攻击等),所以要按判定顺序(范围 → 时间点 → 层级)逐一排除。重启后还要看登录排队是否按设定限制了涌入量。 原文

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%。 原文

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 服务器的扩容由运维团队配合。把重连保留时间留得宽裕一些,可以减少玩家线路的短暂中断演变成丢失排队位置的情况。 原文

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),并调整了优先级,让一个节点无法把其他节点的流量吸走。 原文

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,或直接从源站获取。 原文

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 和网络;恢复时逐步提升负载,避免重连一下子涌入。 原文

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 错误率和实例启动失败。主责是外部(云厂商);研发团队要给所有重试加上带随机间隔的指数退避和次数上限,运维团队要准备好扩容受阻时也能撑住的富余容量,以及其他区域的备选方案。 原文

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 运营方、运营商);如果研发团队(客户端)把域名解析失败和其他错误区分开来提示,客服就能当场判定。 原文

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 错误率、实例启动失败,以及负载均衡器的健康目标数。主责是外部(云厂商);运维团队要限制因健康检查失败而同时被摘除的服务器数量,并准备其他区域的备选方案。 原文

T4工具

卡顿反馈指南

研发团队和运维团队查原因时,最花时间的是弄清“何时、何地、谁”。填好下面的项目,就能在日志和监控图里直接找到那个时刻。

T5工具

术语表

这些是和研发团队、运维团队交流时常出现的词。可以在搜索框里输入中文或英文试试。

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 的技术。画面更流畅,但从输入到画面的延迟可能增加。
T6工具

参考文献

这里是本白皮书中数值、默认值和行为说明的依据。只收录了权威资料:标准文档(RFC),内核与操作系统文档,云、引擎和数据库的官方文档,演讲与论文等。原因卡片和各章末尾的“出处”也链接到同样的资料。版本更新后默认值也可能变化,实际应用前请查阅所用版本的文档。

共 616 条资料,来自 83 家发布方。完整列表见纯文本版的参考文献。