游戏卡顿白皮书 › 按症状查找
卡住:67 个原因及负责方
又称:画面冻结、定住不动、无响应
在含图示的完整版症状词典中打开 →
画面里的一切短暂停住(0.5 秒到数秒),然后又动起来。
所有人都定住。只有自己的角色靠预测挪动一点,或者原地跑步。恢复后,积压的移动以快进或瞬移的形式一下子补上来。
服务器整个停住了(GC、死锁、同步调用),或者线路短暂中断,或者自己的电脑停住了。
导致此症状的原因
L1 客户端游戏进程
- 帧耗时尖峰: 某一帧的计算比平时多花了几倍时间,画面短暂停顿。 (研发团队·客户端开发)
- 客户端垃圾回收: 回收用完丢弃的内存(垃圾对象)期间,整个游戏会停住。特点是按固定间隔一卡一卡。 (研发团队·客户端开发)
- 主线程同步加载/Shader 编译: 第一次绘制没见过的区域、怪物、特效之前,要先读文件、生成 Shader,画面因此卡住。 (研发团队·客户端开发)
- 存储设备过慢导致资源流式加载滞后: 在 HDD 这类慢速存储设备上,开放世界读取纹理、模型的速度跟不上移动,实体会晚出现,游戏也会因等待读取而一卡一卡。 (研发团队·客户端开发)
- 游戏安全模块(反作弊)扫描: 为防外挂,与游戏一起运行的安全模块会定期扫描。扫描太重,或与安全服务器之间的心跳(定期发送的存活确认信号)延迟,就会一卡一卡或掉线。 (研发团队·客户端开发)
L2 客户端操作系统与设备
- Wi-Fi ↔ 4G/5G 切换: 走出家门时 Wi-Fi 断开、切换到 4G 或 5G,自己的 IP 地址会改变,原有连接随之失效。 (研发团队·服务器开发)
- 客户端内存不足/swap: 同时开着几十个浏览器标签页和游戏时,操作系统会把游戏的一部分内存换出到磁盘。 (外部·外部)
- 显存(VRAM)不足: 画质选项要求的内存超过显卡显存时,操作系统要把纹理换到系统内存再取回,画面就会一卡一卡。 (研发团队·客户端开发)
- 网卡省电/驱动问题: 有线网卡、Wi-Fi 芯片在数据包之间进入省电状态时,重新唤醒需要时间。 (外部·外部)
- 游戏内覆盖层干扰: 聊天软件、启动器、录屏软件、FPS 显示工具为了在游戏画面上叠加绘制自己的 UI,会介入游戏的渲染过程(hook)。每帧的工作量随之增加,偶尔还会与游戏冲突,导致顿一下或游戏被强制关闭。 (外部·外部)
L3 家庭网络
L4 公网链路
- BGP 路由变更/收敛: 互联网的路由信息发生变化后需要重新收敛,在这几秒到几十秒(少数情况下几分钟)里会丢包。 (运维团队·网络运维)
- 线路质量差: 接头接触不良、线路老化或调制解调器异常,会造成持续丢包和周期性的线路中断。 (外部·外部)
L5 数据中心网络设备
L6 服务器网卡
- 云宿主机维护/热迁移: 云厂商维护物理服务器(宿主机)时,会把虚拟机迁到其他宿主机(热迁移)或暂停片刻。这期间整台服务器停住,停住时间一长,连接就会断开。 (运维团队·系统运维)
- 网卡驱动/固件问题: 驱动 bug 或某项功能异常导致网卡停住、重启,这期间所有收发都会中断。 (运维团队·系统运维)
L7 服务器操作系统(内核)
- CPU 窃取时间(虚拟机): 物理服务器(hypervisor)把虚拟机的 CPU 时间暂时让给其他虚拟机期间(CPU 窃取),游戏服务器会停住。 (运维团队·系统运维)
- 内存回收/规整导致的停顿: 操作系统为了凑出大页(huge page)而做内存规整,或为补充空闲内存而回收内存时,进程会停住。 (运维团队·系统运维)
L8 Socket 与协议
- TCP 队头阻塞: TCP 为了保证顺序,在重新收到丢失的那个包之前,不会把后面已经到达的数据包交给游戏。 (研发团队·服务器开发)
- TCP RTO 与指数退避: 重传每失败一次,等待时间就翻一倍,短暂的线路中断会变成长时间停顿。 (研发团队·服务器开发)
- 慢客户端导致的阻塞发送: 某个线路慢的玩家发送缓冲区已满时,如果用阻塞方式(缓冲区腾出空间之前调用不返回的发送)发送,服务器线程就会一直等这一个人。 (研发团队·服务器开发)
- SO_REUSEPORT 分配不均: 多个进程分担同一个端口时,内核会按地址哈希给每个连接定好负责的进程,之后不再改变。负责的进程一旦停住,只有分到它那里的人在等。 (研发团队·服务器开发)
- Windows UDP socket 的 WSAECONNRESET 错误: Windows 服务器向已经离开的客户端发 UDP 时,会收到“端口不可达”(ICMP)通知。这个通知会让下一次接收调用以错误结束;如果服务器代码把这个错误当作 socket 本身坏了来处理,所有使用这个 socket 的人都会受影响。 (研发团队·服务器开发)
L9 服务器游戏进程
- 锁竞争: 多个线程为了写同一份数据而等同一把锁时,线程再多,一次也只有一个在执行。 (研发团队·服务器开发)
- 死锁: 两个线程互相等待对方持有的锁,就会永远停住。 (研发团队·服务器开发)
- 游戏线程中的同步调用: tick 中途等待 DB 响应或文件写入时,等多久,服务器上整个游戏的运行就停多久。 (研发团队·服务器开发)
- 定时器集中触发: 所有怪物刷新、所有 buff 到期、整点奖励都挤在同一个 tick 时,那个 tick 的负载就会高出几十倍。 (研发团队·服务器开发)
- 线程池耗尽: 处理任务的工作线程全被慢操作占住时,新请求只能干等。 (研发团队·服务器开发)
- 死循环/逻辑失控: bug 导致一个 tick 无法结束时,服务器会停住,然后被看门狗强制重启。 (研发团队·服务器开发)
- 进入密集区域时实体生成激增: 传送到挤满人的城镇时,服务器要把新进入视野的几百人的外观、装备、状态一次性发过来。 (研发团队·服务器开发)
L10 内存
- 服务器 GC 全局停顿: Java、C# 服务器为回收垃圾对象而暂停所有线程(stop-the-world),这段时间里整个服务器都停住。 (研发团队·服务器开发)
- 脚本引擎 GC 停顿: 即使是 C++ 服务器,如果任务、AI、技能用 Lua 等脚本来跑,脚本引擎执行 GC 期间,对应的场景也会停住。 (研发团队·服务器开发)
- 内存分配暴增: 活动期间大量创建临时对象,GC 会比平时频繁得多。 (研发团队·服务器开发)
- 内存泄漏: 没有释放的内存一点点累积,几天后引发 GC 失控、swap 或进程被强制终止。 (研发团队·服务器开发)
- GC 颠簸(堆余量不足): 存活数据接近堆上限时,GC 即使运行也几乎回收不到东西,于是不停地反复执行。 (研发团队·服务器开发)
- swap: 内存不足时,OS 会把一部分内存换出到磁盘。之后每次用到这部分内存,都要等待比内存慢 1,000 倍以上的磁盘。 (运维团队·系统运维)
L11 磁盘
- 同步写日志: 游戏线程每写一行日志都要等磁盘完成,磁盘一忙,游戏逻辑也跟着停住。 (研发团队·服务器开发)
- IOPS 上限/队列饱和: 请求超过磁盘每秒能处理的数量后,队列变长,延迟暴涨。 (运维团队·系统运维)
- 服务器端懒加载: 服务器第一次收到副本、地图数据请求时才从磁盘读取,那个 tick 期间所有人都会卡住。 (研发团队·服务器开发)
L12 数据库
- DB 故障切换: 主库宕机、切换到备库期间无法写入,最后一部分没来得及复制的数据可能会丢失。 (运维团队·数据库运维)
- 缓存雪崩: 热门数据的缓存同时过期时,几千个请求会一下子涌向 DB。 (研发团队·服务器开发)
- Redis 慢命令: Redis 一次只处理一条命令,一条慢命令就会挡住后面所有请求。 (研发团队·服务器开发)
L13 服务器架构与运维
- 场景切换(服务器间迁移): 进入其他区域或副本时,要把角色数据交给另一台服务器,这个过程中会出现延迟和失败。 (研发团队·服务器开发)
- 级联故障: 一个服务变慢,调用它的服务器都会因等待响应而被占住,连不相关的功能也会停摆。 (研发团队·服务器开发)
- 日志/监控过载: 出故障时日志会激增,同步发送日志的服务器会被日志拖得更慢。 (研发团队·服务器开发)
同步设计
- 帧同步中等待最慢的玩家: 所有人一起计算同一个逻辑帧的结构下,只要有一个人的输入晚到,所有人都得等。 (研发团队·服务器开发)
- 主机(房主)结构: 由某个玩家的电脑充当服务器时,这个人的线路和电脑性能决定了所有人的体验。 (研发团队·服务器开发)
只有部分人遇到的问题
- 特定角色数据过于庞大: 物品、邮件堆了几千个,或者好友列表、黑名单、buff 特别多的角色,登录、存盘、向周围广播的数据量是别人的好几倍。与线路无关,只有用这个角色时才慢。 (研发团队·服务器开发)
TCP 重传的根本原因
- 无线链路丢包: Wi-Fi 和移动网络会在无线链路上重传几次,仍然失败就丢弃数据包。被丢弃的包要等过好一阵,才由 TCP 重新发送。 (外部·外部)
- 瓶颈队列溢出(拥塞丢包): 路由器、运营商之间的互联链路、数据中心线路这类最窄处的队列一旦满了,新来的数据包就会被丢弃。 (运维团队·网络运维)
- 突发发送导致浅缓冲区溢出: 服务器每个 tick 在一瞬间集中发出几千人份的更新,交换机的小缓冲区或云平台的瞬时上限不到 1 ms 就会溢出,一部分包被丢弃。 (研发团队·服务器开发)
- 流量监管丢弃超额流量: 运营商套餐限速、云实例上限和 DDoS 防护设备,有时会把超过规定速率的数据包直接丢弃,不放进队列。 (运维团队·网络运维)
- 物理层错误(线缆/光模块/连接器不良): 线缆损坏、光纤连接器积灰、光模块老化都会产生误码,损坏的数据包会被设备悄无声息地丢弃。 (运维团队·网络运维)
- 双工不匹配: 一端用自动协商,另一端把速率和双工写死,就会有一端以半双工工作,每当负载上来就因冲突丢包。 (运维团队·网络运维)
- 接收端服务器主机丢包: 数据包已经到了服务器,却因网卡的环形缓冲区(暂存到达数据包的缓冲区)溢出,或内核中负责接收处理的 CPU 核心饱和而被丢弃。 (运维团队·系统运维)
- 防火墙/连接跟踪丢包: 防火墙或 Linux 连接跟踪(conntrack,把经过的连接记录到表里的功能)在表满了,或判断连接状态不对时,会丢弃数据包。 (运维团队·网络运维)
- 中间设备超出处理上限(防火墙/IPS/DDoS 防护): 防火墙、入侵防御设备(IPS)、DDoS 防护设备会逐个检查经过的数据包。一旦超出检测能力,处理不过来的包就会被丢弃。 (运维团队·网络运维)
- MTU 黑洞(只有大包反复丢失): 中间链路能通过的包变小了,“包太大”的通知(ICMP)却被拦截,大包无论重发多少次都会丢失。 (运维团队·网络运维)
- 连接中途 NAT/负载均衡器映射过期: 中间设备删掉空闲连接的映射(记录该连接应转发到哪里的条目)后,之后发出的包就送不到了。要么反复重传后掉线,要么设备回一个拒绝连接的 RST,立刻断开。 (研发团队·客户端开发)
- 路径变更/ECMP 故障路径: 互联网路由切换的几秒内,或者被分配到多条 ECMP 路径中某条故障路径的连接上,会出现丢包。 (运维团队·网络运维)
- 延迟飙升导致的虚假重传: 数据包只是一时到得特别晚,并没有丢。但只要这段延迟超过 RTO,发送方就会判定为丢包并重传。 (外部·外部)
- RTO 设置与环境不匹配: RTO 最小值调得太低,稍微晚一点就会产生虚假重传;默认值(200 ms)对游戏来说又太长,每丢一次包都要停顿很久。 (运维团队·系统运维)
- thin stream 恢复慢: 像游戏这样稀疏地发小包时,还没等到“后续 3 个包”,RTO 就先到期了。同样的丢包,停顿时间比大流量传输长得多。 (研发团队·服务器开发)
- 中间设备剥离 TCP 选项: 部分防火墙或加速设备删除或改写 TCP 选项后,丢多个包时每个往返只能恢复一个,或者窗口(一次能发送的量)变小,速度变慢。 (运维团队·网络运维)
- 零窗口(看起来像重传的停顿): 接收方程序没有及时读取 socket,缓冲区满了之后,发送方就会停止发送,只发零窗口探测。这与线路无关。 (研发团队·客户端开发)
查看含图示的完整版症状词典