游戏卡顿白皮书 › 按症状查找
操作延迟:76 个原因及负责方
又称:反应慢、迟钝、没手感
在含图示的完整版症状词典中打开 →
按下之后要过一会儿才看到结果。画面本身可能很流畅。
按下技能键 0.2~0.5 秒后才释放。拾取、对话、交易这类需要确认的操作变慢。
往返时间(ping)长,或者某处的队列在堆积。排查距离、路由器队列、Nagle(把小数据包攒起来再发的 TCP 功能)、服务器队列。ping 很低却总是反应慢,就看自己电脑这边(垂直同步、低 FPS 等),或者每个操作都要等服务器确认的设计(见“同步方式”一章)。
导致此症状的原因
L1 客户端游戏进程
- 大规模同屏渲染负载: 攻城战、世界 BOSS 这类数百人同屏的场景,光是绘制开销就承受不住。 (研发团队·客户端开发)
- 主线程数据包处理瓶颈: 收到的数据包每帧只处理固定数量时,一下子涌来的数据包会不断顺延到下一帧。 (研发团队·客户端开发)
- 垂直同步(V-Sync)与渲染队列: GPU 画好的帧先在队列里攒几张,再按显示器刷新周期送出,这段时间里输入响应就会变迟。 (研发团队·客户端开发)
L2 客户端操作系统与设备
- 省电模式/发热降频: 笔记本电池模式、手机省电模式、设备发热都会让 CPU、GPU 降速。发热的特点是一开始正常,过一阵子才变慢。 (外部·外部)
- 同一设备上其他应用占用带宽: 云同步、大文件下载、游戏更新在同一台 PC 上运行时,游戏数据包要在队列里排队。 (外部·外部)
- 显示器/输入设备/帧生成延迟: ping 正常、操作却发沉,可能是电视的画面处理、无线手柄或帧生成功能在输入和画面之间增加了延迟。 (外部·外部)
L3 家庭网络
L4 公网链路
- 传播延迟(物理距离): 光在光纤里每秒也只能走约 20 万 km。服务器离得远,条件再好也会慢。 (运维团队·系统运维)
- 卫星互联网(低轨、静止轨道): 卫星互联网的信号要在太空中往返。静止轨道卫星光是往返就超过 0.5 秒;Starlink 这类低轨卫星平时很快,但在重新分配路径的瞬间延迟会波动,还可能短暂中断。 (外部·外部)
- 路由绕行: 受运营商之间互联协议的限制,离得近的服务器也可能要绕远路才能到。 (运维团队·网络运维)
- 海底光缆/国际线路故障: 海底光缆一旦中断,修好之前的几周(长则几个月)里流量要走很远的绕行路径,剩下的线路也会拥挤。 (外部·外部)
- 运营商限速/流量管理: 套餐流量用超,或套餐对特定流量做管控时,数据包会被延后或丢弃。 (外部·外部)
- 经由 VPN/游戏加速器: 开启 VPN 或游戏加速器后,数据包会经过该公司的中转服务器。中转服务器远或拥挤时,反而会变慢。 (外部·外部)
L5 数据中心网络设备
- DDoS 防护引流/误判: 为防御攻击把流量牵引到清洗中心后,路径会变长,还可能把正常用户误判为攻击而拦截。 (运维团队·网络运维)
- 数据中心线路饱和: 版本更新包分发、日志传输、备份与游戏共用同一条线路时,线路会被占满。 (运维团队·网络运维)
L6 服务器网卡
- 网卡中断集中在单个核心: 网卡把数据包到达中断只发给一个 CPU 核心时,这个核心就会成为瓶颈。 (运维团队·系统运维)
- 中断合并过度: 为减轻 CPU 负担,网卡把数据包攒一批再一次性通知 CPU,攒包花了多少时间,就会晚多少。 (运维团队·系统运维)
- 网卡带宽饱和: 流量用到 1 Gbps、10 Gbps 网卡的极限时,发送队列会越排越长,最终数据包被丢弃。 (研发团队·服务器开发)
- GRO/LRO 合并等待延迟: 这是把多个数据包合并成一个、以减轻 CPU 负担的功能。视配置而定,较小的游戏数据包可能要短暂等待下一个可合并的包。 (运维团队·系统运维)
L7 服务器操作系统(内核)
- 服务器电源管理(C-state/调频)导致延迟跳变: 空闲的 CPU 核心为了省电会进入深度省电状态(C-state),频率也会降低。数据包或定时器到来时,唤醒和升频都需要时间,处理小数据包时就会多出一段延迟。 (运维团队·系统运维)
- OS/内核/驱动/固件更新后的性能变化: 游戏代码没变,服务器的 OS、内核、驱动、固件更新之后却开始变慢。更新可能会改变默认值、调度器、CPU 漏洞缓解措施(mitigations)和驱动行为。 (运维团队·系统运维)
L8 Socket 与协议
- Nagle 算法 + 延迟 ACK: 把小包攒起来再发的 Nagle 算法,和推迟发送 ACK 的延迟 ACK 叠加在一起,消息每拆开写一次,就会延迟 40~200 ms。 (研发团队·服务器开发)
- 空闲后的慢启动: TCP 空闲一段时间后会重新缩小拥塞窗口(一次能发送的量),突然要发大量数据时只能分几次发。 (运维团队·系统运维)
- 拥塞控制导致发送量骤降: TCP 把丢包当作拥塞信号,把发送速率降低 30~50%。遇到 Wi-Fi 丢包也会同样降速。 (运维团队·系统运维)
- 阻塞 I/O 模型: 等一个 socket 时线程就干不了别的事,这种结构下人越多,整体就越慢。 (研发团队·服务器开发)
L9 服务器游戏进程
- Tick 超出预算: 一个 tick 内要做的事超出预算时,服务器的 tick 周期会被拉长,整个区域都会变慢或一卡一卡。 (研发团队·服务器开发)
- 广播激增: 把一个人的移动发给所有能看到他的人时,要发的更新数量是聚集人数的平方。 (研发团队·服务器开发)
- 单线程区域过载(热点): 每个区域由一个线程负责的架构下,人一旦聚到一处,只有那一个核心会跑到 100%。 (研发团队·服务器开发)
- 锁竞争: 多个线程为了写同一份数据而等同一把锁时,线程再多,一次也只有一个在执行。 (研发团队·服务器开发)
- 消息队列积压: 请求进来的速度超过处理速度、在队列里堆积时,排在后面的请求要过几秒才被处理,或者被丢弃。 (研发团队·服务器开发)
- 序列化/压缩开销: 把要发送的数据转换成字节并压缩也要消耗 CPU,人多时这部分开销会暴增。 (研发团队·服务器开发)
- 线程池耗尽: 处理任务的工作线程全被慢操作占住时,新请求只能干等。 (研发团队·服务器开发)
- 战斗集中在单一目标(世界 BOSS): 几百人同时打一个 BOSS 时,这一只 BOSS 的计算全压在一处,命中信息还要发给所有看得到的人。 (研发团队·服务器开发)
- 进入密集区域时实体生成激增: 传送到挤满人的城镇时,服务器要把新进入视野的几百人的外观、装备、状态一次性发过来。 (研发团队·服务器开发)
- 实体堆积(未清理的物品/召唤物): 本该消失的地面物品、召唤物、已结束的定时器没有被清理而不断堆积,服务器开得越久,每个 tick 要做的事就越多。 (研发团队·服务器开发)
- 版本更新导致流量特征变化: 新内容、特效、同步项增加了数据包的大小和频率,原本正常的服务器在版本更新后开始碰到 MTU、带宽、包数的上限。 (研发团队·服务器开发)
L11 磁盘
- fsync 风暴: 要求把数据“真正”写进磁盘(确保落盘)时,视磁盘不同每次要花 0.1 ms 到几十 ms;请求一集中,队列就会变长。 (研发团队·服务器开发)
- 云盘突发积分耗尽: 部分云盘和小规格实例有突发积分,可以短时间超过基准性能运行。繁忙时段一长、积分耗尽,速度就会突然下降。 (运维团队·系统运维)
- IOPS 上限/队列饱和: 请求超过磁盘每秒能处理的数量后,队列变长,延迟暴涨。 (运维团队·系统运维)
- 备份/压缩/扫描任务: 凌晨的备份、日志压缩、安全扫描独占磁盘时,游戏服务器的读写会被挤到后面。 (运维团队·系统运维)
- HDD 寻道延迟: HDD 的磁头必须在盘片上移动(寻道,seek),读写分散的数据每次要花将近 10 ms。 (运维团队·系统运维)
L12 数据库
- 缺少索引的查询: 没有索引时,要找到符合条件的行,就得把整张表读一遍(全表扫描)。 (研发团队·服务器开发)
- 热点行锁竞争: 所有人都要修改同一行(公会仓库、拍卖行热门物品、全服计数器)时,同一时刻只有一个人能拿到锁。 (研发团队·服务器开发)
- DB 死锁: 两个事务(作为一个整体处理的一组 DB 操作)互相等待对方锁住的行时,DB 会强制取消其中一个。 (研发团队·服务器开发)
- 连接池耗尽: 与 DB 建立的连接数是固定的,慢查询占住连接后,其余请求只能等待。 (研发团队·服务器开发)
- 检查点/日志刷盘: DB 定期把内存中的变更集中写入磁盘,那一刻查询会变慢。 (运维团队·数据库运维)
- 冷缓存(刚重启时): DB 重启后内存缓存是空的,一段时间内所有查询都要从磁盘读取。 (运维团队·数据库运维)
- 登录激增与 N+1 查询: 加载一个角色要分别查询几十次时,数万人同时登录就会变成几百万条查询。 (研发团队·服务器开发)
- 大型批处理任务: 在运营期间跑排行榜统计、批量发邮件、清理旧数据,会占用锁和磁盘。 (研发团队·服务器开发)
- 缓存雪崩: 热门数据的缓存同时过期时,几千个请求会一下子涌向 DB。 (研发团队·服务器开发)
- 长事务: 一个事务长时间不结束,就会一直持有锁,DB 也没法清理(purge)旧版本数据,整体逐渐变慢。 (研发团队·服务器开发)
- Redis 慢命令: Redis 一次只处理一条命令,一条慢命令就会挡住后面所有请求。 (研发团队·服务器开发)
- 执行计划变化导致查询变慢: 代码没变,DB 却换了处理同一条查询的方式(执行计划),昨天 2 ms 的查询今天就变成几百 ms。 (运维团队·数据库运维)
- 线上表结构变更(DDL)锁: 在服务运行中给表加列或加索引,仅仅因为一个短暂需要的锁,使用这张表的所有请求都可能被迫等待。 (运维团队·数据库运维)
L13 服务器架构与运维
- 经由网关/代理: 在客户端和游戏服务器之间加一层中间服务器,每经过一次就多一段处理时间,这台服务器也会成为单点故障。 (研发团队·服务器开发)
- 级联故障: 一个服务变慢,调用它的服务器都会因等待响应而被占住,连不相关的功能也会停摆。 (研发团队·服务器开发)
- 发布/重启: 为更新而重启服务器时,如果不迁移连接,这台服务器上的玩家都会掉线,关机前的存盘和随后的重连也会一下子挤在一起。 (研发团队·服务器开发)
- 脚本/机器人过多: 机器人发请求的频率远高于真人,会吃掉服务器的处理能力。 (研发团队·服务器开发)
- 匹配/区域分配错误: 没有分到近的区域(region),被分到远区域的服务器,即使线路正常,也只有这个玩家 ping 一直偏高。 (研发团队·服务器开发)
同步设计
- 收到服务器响应才播放表现(请求-响应方式): 按下按钮后,在服务器回复之前既没有动画也没有声音。ping 值直接就是反应速度。 (研发团队·客户端开发)
- 顺序往返多的协议(chatty): 一次操作需要依次与服务器往返多次时,ping 就要乘以这个次数。 (研发团队·服务器开发)
- 技能不支持预输入: 必须等服务器确认上一个技能结束后才能按下一个技能时,每次衔接中间都会插入一段往返时间。 (研发团队·客户端开发)
- 被 ping 吃掉的短判定窗口: 闪避、弹反、格挡这类需要反应的时间很短时,ping 会把这段时间吃掉,出现躲不开的攻击。 (研发团队·服务器开发)
- 帧同步中等待最慢的玩家: 所有人一起计算同一个逻辑帧的结构下,只要有一个人的输入晚到,所有人都得等。 (研发团队·服务器开发)
- 双重 tick 等待: 请求攒到下一个 tick 才处理,结果又在再下一个 tick 才发出,tick 间隔就被叠加了两次。 (研发团队·服务器开发)
只有部分人遇到的问题
- 每个玩家的输入缓冲大小: 服务器为每个人先攒一点输入,每个 tick 取出一个使用时,别人看起来很流畅,但本人动作在服务器上确定的时间也会相应推迟。 (研发团队·服务器开发)
- 一个网络差的队友与 BOSS 机制: 在需要所有人在规定时刻一起反应的团本机制中,一个网络差的人反应晚了,就会让整个队伍失败。 (研发团队·服务器开发)
- 特定角色数据过于庞大: 物品、邮件堆了几千个,或者好友列表、黑名单、buff 特别多的角色,登录、存盘、向周围广播的数据量是别人的好几倍。与线路无关,只有用这个角色时才慢。 (研发团队·服务器开发)
- 按连接分配的发送预算/优先级: 服务器给每个连接的发送量设上限、按由近及远的顺序发送时,上限定得低的一方收到远处 NPC 会很晚,甚至收不到。 (研发团队·服务器开发)
TCP 重传的根本原因
- 接收端服务器主机丢包: 数据包已经到了服务器,却因网卡的环形缓冲区(暂存到达数据包的缓冲区)溢出,或内核中负责接收处理的 CPU 核心饱和而被丢弃。 (运维团队·系统运维)
- 延迟飙升导致的虚假重传: 数据包只是一时到得特别晚,并没有丢。但只要这段延迟超过 RTO,发送方就会判定为丢包并重传。 (外部·外部)
- 乱序导致的虚假快速重传: 数据包经过多条路径或聚合链路时顺序被打乱,接收方会用重复 ACK 通知“有包缺失”,发送方就把好好的包又发一遍。 (运维团队·网络运维)
- ACK 延迟或丢失(上行饱和): 数据已经顺利到达,但表示“已收到”的 ACK 在塞满的上行队列里被耽搁或丢掉,发送方就会判定为丢包并重传。 (外部·外部)
- RTO 设置与环境不匹配: RTO 最小值调得太低,稍微晚一点就会产生虚假重传;默认值(200 ms)对游戏来说又太长,每丢一次包都要停顿很久。 (运维团队·系统运维)
查看含图示的完整版症状词典