游戏服务器CPU占用持续升高怎么排查:结合内存、磁盘I/O与进程状态定位

当游戏服务器的CPU占用持续升高时,先不要急着重启进程或直接结束高占用任务。CPU使用率升高可能来自游戏逻辑线程、系统调用、内存换页、磁盘等待,也可能是连接数增长后触发了更多会话处理。仅看任务管理器或 top 中的一个CPU百分比,通常无法判断真正原因。
在Linux环境中,建议按照“先保留现场,再看主机指标,随后定位进程和线程,最后处理配置或重启”的顺序排查。优先使用只读命令,避免在原因未明确前删除日志、修改内核参数或强制结束进程。下面的命令默认适用于常见Linux发行版;如果游戏服务器运行在容器或其他操作系统中,应同时参考对应的资源限制和监控界面。
先确认故障范围并保留现场
记录时间、负载和进程排名
排查前先记录故障开始时间、游戏服务器实例、最近一次版本或配置变更,以及当前是否有玩家集中登录、开服活动或批量存档。时间点很重要,后续需要将CPU、内存、磁盘和连接数与日志中的同一时间段对应起来。
以下命令不会修改系统状态,适合先执行:
date -Is
uptime
uname -a
top -b -n 1 | head -n 25
ps -eo pid,ppid,user,stat,%cpu,%mem,etime,cmd --sort=-%cpu | head -n 20
重点观察以下信息:
uptime中的负载平均值是否持续增长。负载高不等于CPU一定饱和,处于磁盘等待状态的进程也会提高负载。top的%Cpu(s)中,us、sy、wa、st哪一项明显增加。- 是否由单个游戏进程占用CPU,还是多个进程共同升高。
- 高占用进程的
STAT、运行时长和命令行是否发生变化。 - CPU升高是否与某个新启动的脚本、日志程序、备份程序或监控程序同时出现。
不要只采集一次结果。如果CPU占用有波动,间隔数秒重复采样,避免把瞬时峰值误判为持续故障。
检查服务状态和日志时间线
如果游戏服务器由 systemd 管理,先确认服务对应的主进程和最近日志:
systemctl status your-game.service --no-pager
systemctl show your-game.service -p MainPID,ControlGroup
journalctl -u your-game.service --since "-15 min" --no-pager
将 your-game.service 替换为实际服务名。服务名不确定时,不要直接猜,可以先查看正在运行的服务:
systemctl list-units --type=service --state=running --no-pager
如果日志中出现重复报错、频繁重连、存档失败、脚本异常或同一事件反复执行,CPU升高可能是错误重试循环,而不是正常玩家负载。先处理重复错误,再判断是否需要重启。非 systemd 管理的进程,应在游戏自身日志目录中按故障时间检索。
第一步:区分CPU计算、系统调用和等待
查看整体CPU分布
如果系统安装了 sysstat,可以使用 mpstat 查看各CPU核心;没有安装时使用 vmstat:
if command -v mpstat >/dev/null 2>&1; then
mpstat -P ALL 1 5
else
vmstat 1 5
fi
常见字段的含义如下:
us:用户态程序消耗的CPU,游戏逻辑、脚本和计算密集型任务通常会体现为这一项。sy:内核态消耗的CPU,频繁系统调用、网络处理、文件操作等可能使其升高。wa:等待磁盘或其他块设备I/O的时间。它高时,CPU本身未必在进行有效计算。st:虚拟化环境中被宿主机暂时占用的CPU时间。如果该项持续升高,需要结合主机或云平台侧指标判断。r:等待运行的任务数。它持续高于可用CPU核心数,通常说明存在运行队列压力。b:不可中断睡眠状态的任务数,常与I/O等待有关。
如果是 us 明显升高,继续定位游戏进程和线程;如果是 wa 或 b 升高,应优先转向磁盘I/O;如果 sy 明显升高,则要结合连接数、系统调用和进程线程状态分析。
找出真正占用CPU的进程和线程
先查看进程级排名:
ps -eo pid,ppid,user,stat,%cpu,%mem,etime,cmd --sort=-%cpu | head -n 20
单个进程的CPU占用较高,并不一定意味着故障。多线程程序在Linux中可能显示超过一个核心的CPU百分比,判断时应结合可用核心数、持续时间和游戏服务器的正常基线。
确定目标PID后,再看线程级别:
PID=1234
top -H -p "$PID"
将 1234 替换为实际游戏进程PID。也可以使用非交互方式列出线程:
PID=1234
ps -L -p "$PID" -o pid,tid,psr,stat,%cpu,%mem,etime,comm
结果可以按以下方式分支:
- 单个线程长期占用最高:重点检查该线程负责的游戏逻辑、脚本、定时任务或异常循环。
- 多个工作线程同时升高:可能是玩家请求、连接数、地图计算、批量存档等工作量增加。
- 游戏进程CPU不高,但系统总CPU很高:检查其他进程,不要把所有问题都归因于游戏服务。
wa高而游戏进程CPU不高:进程可能在等待数据或文件写入,继续执行磁盘I/O检查。st高:游戏进程本身未必有代码问题,应保存时间点和指标,交由运行环境维护方进一步核对。
第二步:检查内存压力,避免把换页误判为CPU问题
先看可用内存和交换区活动
执行:
free -h
vmstat 1 5
swapon --show
free -h 中应重点看 available,而不是只看 free。Linux会利用空闲内存作为文件缓存,因此 free 较小并不自动代表内存不足。真正需要警惕的是可用内存持续减少,同时 vmstat 中 si、so 持续出现,表示内存页正在进出交换区。
内存压力可能引发以下连锁反应:
- 游戏进程分配内存变慢。
- 系统频繁换页,磁盘等待增加。
wa和负载上升。- CPU时间被内核和内存管理消耗,看起来像CPU持续升高。
因此,看到CPU升高时,如果同时存在 si/so、wa 和可用内存下降,应先处理内存压力,而不是盲目增加线程或提高进程优先级。
找出内存占用进程并检查OOM记录
查看内存占用排名:
ps -eo pid,ppid,user,stat,%mem,rss,vsz,etime,cmd --sort=-%mem | head -n 20
RSS 更接近进程当前驻留在物理内存中的大小,VSZ 是虚拟地址空间大小,不能直接当作实际内存消耗。重点观察游戏进程的RSS是否随时间持续增长,以及其他进程是否在故障期间突然占用大量内存。
检查内核或服务日志中的内存不足记录:
journalctl -k --since "-30 min" --no-pager | grep -Ei 'out of memory|oom|killed process'
如果系统没有可用的 journalctl,可以在具备权限的前提下尝试:
dmesg -T | grep -Ei 'out of memory|oom|killed process'
结果含义通常如下:
- 游戏进程RSS持续增长,且回收后仍不下降:可能存在内存泄漏、缓存无上限或对象未释放,需要结合应用日志和版本变更定位。
available偏低但没有换页,且文件缓存占比较大:不一定是故障,不能仅凭“free很少”重启。si/so持续出现:优先降低内存压力,检查是否有其他进程抢占资源,或确认游戏进程的内存上限是否过低。- 出现OOM记录:系统已经主动终止过进程。先确认被终止的PID和服务状态,再恢复服务,不能只清理日志。
如果需要临时重启释放内存,应先确认游戏支持安全停服或存档,保留当前日志和进程信息,并提前通知连接中的玩家。重启只能作为缓解手段,不能替代对持续增长的内存占用进行修复。
第三步:检查磁盘I/O和文件系统状态
判断是不是I/O等待拖高了负载
如果系统安装了 iostat,执行:
iostat -xz 1 5
如果提示命令不存在,可使用前面的 vmstat 1 5 暂时判断;在生产环境安装监控工具前,应遵循发行版的软件变更流程,避免在故障期间引入不必要的变更。
重点观察:
- 设备是否存在持续读写。
await是否相对平时明显增加。%util是否长期接近设备的处理上限。- 写入量是否与存档、日志、崩溃转储或临时文件生成时间一致。
这些指标没有适用于所有磁盘和业务的固定阈值,应与正常时段对比。单次短暂写入不一定是故障,持续的高等待和进程阻塞才更有诊断价值。
针对已确定的PID,可以进一步查看进程的I/O活动:
PID=1234
if command -v pidstat >/dev/null 2>&1; then
pidstat -d -p "$PID" 1 5
else
echo "pidstat未安装,请结合iostat、vmstat和进程日志判断"
fi
检查磁盘空间、inode和异常增长目录
磁盘空间或inode耗尽,会导致日志写入、存档和临时文件操作失败,进而触发重试或异常循环:
df -h
df -ih
如果发现游戏目录或日志目录增长异常,可以在维护窗口执行目录统计。du 会遍历大量文件,可能产生额外磁盘读取,不要在高峰期反复运行:
du -xhd1 /path/to/game 2>/dev/null | sort -h
将 /path/to/game 替换为实际路径。结果分支如下:
- 磁盘空间接近耗尽:先确认哪些文件可以按现有保留策略归档,再通过日志轮换或应用配置控制增长。不要直接删除正在写入的日志或存档。
- inode耗尽但空间仍有余量:通常是大量小文件,需要定位文件来源并通过应用或轮换机制处理。
D状态进程较多,同时I/O等待升高:进程正在等待不可中断的I/O操作,继续检查设备、文件系统和写入来源。- I/O写入与日志错误同步增长:优先修复错误重试或日志级别问题,单纯清理旧日志可能很快复发。
第四步:核对连接数和连接状态
连接数增加会直接增加网络事件、会话管理和游戏逻辑处理,但连接数本身不能证明服务器过载。需要区分监听端口、已建立连接、等待握手连接和短连接残留。
先查看整体统计:
ss -s
再查看套接字状态分布:
ss -tan | awk 'NR > 1 {print $1}' | sort | uniq -c | sort -nr
如果需要查看某个游戏端口,将示例中的端口替换为实际端口:
PORT="<游戏端口>"
ss -tan | awk -v p=":$PORT" 'NR > 1 && ($4 ~ p || $5 ~ p) {print $1, $4, $5}'
判断时注意:
ESTAB持续增加,且游戏进程CPU和线程数同步增加:连接处理可能是CPU升高的重要来源。SYN-RECV较多:可能是客户端集中重连、服务端处理不及时或连接建立过程异常,应结合服务日志和连接时间线判断。TIME-WAIT较多:说明近期存在大量短连接,通常反映连接建立和关闭频繁,不能直接等同于当前活跃玩家数。- 连接数变化不大,但CPU持续升高:连接数不是主要原因,应回到线程、内存和I/O分析。
ss -tanp能显示关联进程,但部分信息需要更高权限。权限不足时,不要为了查看信息随意修改文件权限。
如果服务有明确监听端口,可以确认监听状态:
ss -lntup
监听端口消失、服务进程仍存在时,要检查服务是否卡在初始化或I/O等待;服务进程已经退出时,则应优先查看退出前日志和服务管理器记录。
根据组合现象缩小根因
单项指标容易误判,组合结果更有价值。可以按下表选择下一步:
| 观察到的组合 | 更可能的方向 | 下一步 |
|---|---|---|
us高,单个游戏线程长期占用最高 | 游戏逻辑、脚本或定时任务过度计算 | 结合线程名称、版本变更和应用日志定位具体功能 |
sy高,连接数同步增长 | 网络事件、系统调用或连接处理压力 | 对比连接状态,检查重复重连和服务端错误 |
wa高,D状态进程增加,磁盘await升高 | 磁盘或文件系统等待 | 检查存档、日志、临时文件和磁盘空间 |
可用内存下降,si/so持续出现,wa也升高 | 内存不足引发换页和I/O等待 | 查找内存增长进程,降低内存压力并检查OOM记录 |
| 负载高但CPU空闲比例仍较大 | 进程处于I/O等待或不可运行状态 | 查看vmstat的b、iostat和进程STAT |
| 多个进程同时CPU升高,游戏进程排名下降 | 主机上存在其他资源竞争 | 检查其他进程、定时任务和日志程序 |
Z状态进程增加但CPU不高 | 父进程未回收子进程 | 查找父PID并检查服务实现,不要把僵尸进程当作CPU根因 |
st持续升高 | 虚拟化环境的CPU调度受限 | 记录时间段和指标,核对运行环境侧资源状态 |
进程状态也应纳入判断。R表示正在运行或等待运行,S通常表示可中断睡眠,D通常表示不可中断等待,Z表示僵尸进程。状态字母只是线索,必须与CPU、I/O和日志结合,不能看到一个D就断定磁盘损坏,也不能看到R就认定程序死循环。
修复动作要按风险递增
优先处理可回滚的应用配置
如果已经确认是日志级别、重复任务、存档频率、脚本逻辑或连接处理配置导致资源增长,应先保存当前配置副本,再一次只改一个配置项。配置修改前记录原值,修改后保留变更时间;如果指标恶化,恢复原值并重启或重新加载对应服务。
不要在没有基线的情况下同时修改线程数、缓存大小、连接上限和内核参数,否则即使故障暂时缓解,也无法判断真正有效的改动。
必要时进行平滑重启
只有在确认需要释放内存、重新加载修复后的配置,或进程已经无法正常处理请求时,才考虑重启。重启前应完成以下检查:
- 确认游戏支持安全停服、存档或维护模式。
- 保留故障前后的日志、进程列表和资源指标。
- 备份需要保留的配置和游戏数据。
- 确认重启会影响哪些玩家连接和正在进行的对局。
- 明确回滚方式,例如恢复原配置、启动旧版本程序或停止自动发布流程。
先查看服务状态,再执行受控重启:
systemctl status your-game.service --no-pager
systemctl restart your-game.service
systemctl is-active your-game.service
journalctl -u your-game.service --since "-5 min" --no-pager
systemctl restart 会中断现有进程和连接,不能在未确认存档状态时直接执行。如果重启后异常,应停止继续修改,恢复已备份的配置或旧版本程序,再按日志判断。不要一开始使用 kill -9,它可能跳过应用清理和存档流程,造成数据丢失或下次启动需要恢复。
修复后如何验证没有复发
验证不能只看重启后瞬间的CPU百分比。应使用与故障期间相同的命令,在服务恢复后连续观察多个时间点:
date -Is
uptime
vmstat 1 5
free -h
ss -s
ps -eo pid,ppid,user,stat,%cpu,%mem,etime,cmd --sort=-%cpu | head -n 20
如果系统具备 iostat,再执行:
iostat -xz 1 5
验证内容包括:
- 游戏进程CPU占用是否回到正常波动范围,是否仍有单个线程持续异常。
- 负载平均值是否停止增长,而不是仅在重启后短暂下降。
available是否稳定,si/so是否停止持续活动。wa和D状态进程是否减少,磁盘等待是否恢复到正常水平。- 连接数是否与实际玩家和业务时段相符,是否仍有大量重复重连。
- 日志中是否继续出现同一错误、存档失败或异常重试。
- 玩家能否正常登录、创建或加入对局、完成存档,并在维护结束后保持服务稳定。
如果资源指标短暂恢复后再次上升,应记录第二次增长的起始时间,并将它与连接数、内存曲线、磁盘写入和应用日志对齐。这样的时间关联通常比再次重启更能定位根因。若CPU、内存和I/O指标均正常,但玩家仍报告卡顿,则应继续核对应用内部延迟和服务日志,不要仅凭主机CPU占用判断游戏服务器已经恢复。