上一篇 下一篇 分享链接 返回 返回顶部

海外游戏服务器磁盘空间持续增长,如何区分日志膨胀、inode耗尽与I/O延迟

发布人:Minchunlin 发布时间:2026-10-02 17:46 阅读量:7

磁盘空间持续增长时,不要把所有 No space left on device 都归为“容量不够”。在 Linux 游戏服务器上,容量、inode 和 I/O 延迟是三个不同维度:日志或补丁文件会消耗磁盘块,海量小文件会耗尽 inode,磁盘队列拥堵则可能让游戏进程卡顿,即使剩余空间仍然很多。很多团队会同时问“海外服务器哪个地区做游戏服务器好?”,但当故障已经表现为磁盘增长或写入异常时,地区不是第一判断变量,应先确认服务器本地文件系统到底是哪一类资源先达到瓶颈。

建立容量、inode、I/O 延迟三类故障的基础认知边界。

现场排查建议按这个顺序进行:先看 df -hT 和 df -ih,确认是容量还是 inode;再用 du 和文件时间、类型定位增长目录;如果 df 与 du 对不上,检查已删除但仍被进程打开的文件;最后用 iostat、vmstat 和内核日志确认是否存在 I/O 延迟或文件系统错误。不要一开始就删除日志或重启游戏进程,否则可能掩盖增长来源。

先把三种异常分开

现象容量指标inode 指标I/O 指标常见根因
日志或文件持续变大df -h 持续上升通常正常写入高峰时可能升高游戏日志、调试日志、崩溃转储、补丁缓存
inode 耗尽可能还有很多 GB 空间df -i 接近 100%未必异常大量小日志、临时文件、分片文件、空文件
I/O 延迟升高可能正常通常正常await、队列长度、%util 持续偏高并发写入、日志轮转、补丁解压、文件系统异常
df 很满但 du 加总不出来高通常正常可能正常或偏高已删除但仍被进程打开的文件、挂载点边界、容器文件系统
磁盘变成只读空间不一定满不一定异常可能伴随 I/O error文件系统错误、底层存储异常、内核保护动作

这些指标不能单独下结论。例如,日志集中写入可能同时造成空间增长和 I/O 延迟;磁盘快满时,文件系统元数据操作也可能变慢。需要看指标的时间关系和具体路径。

第一步:建立文件系统基线

以下命令以常见 Linux 发行版为例,假设游戏数据目录为 /srv/game。请先替换为真实路径。命令主要是只读检查,但 du 和 find 需要遍历大量文件,不建议在磁盘已经严重拥堵的高峰期反复执行。

GAME_DIR=/srv/game

sudo findmnt -T "$GAME_DIR" -o TARGET,SOURCE,FSTYPE,OPTIONS
sudo df -hT "$GAME_DIR"
sudo df -ih "$GAME_DIR"
sudo du -xhd1 "$GAME_DIR" 2>/dev/null | sort -h

重点看四项:

  1. df -hT 中的 Use% 是否持续上升,以及文件系统类型是 ext4、XFS 还是其他类型。
  2. df -ih 中的 inode 使用率是否已经接近上限。
  3. findmnt 返回的实际挂载点、块设备和挂载参数,避免检查了错误目录。
  4. du -xhd1 中哪个一级目录占用增长最快。

du -x 只在当前文件系统内统计。如果游戏目录下挂载了独立磁盘,或者目录实际位于容器的另一层文件系统,du 和宿主机上的目录统计可能不一致。因此,必须在产生问题的同一运行环境中执行检查。

可以用下面的命令列出当前目录下最大的文件:

sudo find "$GAME_DIR" -xdev -type f -printf '%s\t%p\n' 2>/dev/null \
  | sort -nr | head -n 20

输出中的第一列是字节数。将其换算成 GB 时,可用:

GB ≈ 字节数 ÷ 1024 ÷ 1024 ÷ 1024

如果最大的文件集中在 logs、crash、dump、replay、cache 或补丁临时目录,优先沿着这些目录继续查,而不是直接清理整个游戏目录。

第二步:确认是不是日志膨胀

日志膨胀通常具备三个特征:

  • df -h 的容量使用率随时间增加;
  • df -i 没有同步接近 100%,因为少量大日志只消耗少量 inode;
  • du 能明确指出日志目录或少数几个日志文件在增长。

可以先查看常见日志文件的大小、修改时间和路径:

sudo find "$GAME_DIR" -xdev -type f \
  \( -iname '*.log' -o -iname '*.out' -o -iname '*.err' -o -iname '*.txt' \) \
  -printf '%s\t%TY-%Tm-%Td %TH:%TM\t%p\n' 2>/dev/null \
  | sort -nr | head -n 30

如果要判断增长速度,不要只看一次结果。分别记录两次相隔一段时间的目录大小:

sudo du -xsh /srv/game/logs

例如,某日志目录第一次为 8 GB,60 分钟后为 10 GB,那么近似增长速度就是 2 GB/小时,按当前速度计算,一天可能增加约 48 GB。这个数值只是根据当前观察窗口的估算,发生版本更新、玩家数量变化或错误风暴时,增长速度可能完全不同。

还要检查日志内容是否突然重复输出。常见诱因包括:

  • 日志级别被临时调整为 debug;
  • 某个资源加载失败,进程每帧或每次请求都重复记录;
  • 连接失败后进入快速重试;
  • 崩溃转储或错误堆栈不断生成;
  • 补丁下载、解压或校验产生了大量临时日志。

使用日志轮转限制增长

对于支持重新打开日志文件的游戏服务,优先使用日志轮转,并在轮转后让进程重新打开文件。下面是一个参考配置,路径和服务名必须替换为实际值:

/srv/game/logs/*.log {
    daily
    size 500M
    rotate 14
    compress
    delaycompress
    missingok
    notifempty

    postrotate
        systemctl reload game-server.service >/dev/null 2>&1 || true
    endscript
}

这里的 systemctl reload 只有在服务确实支持重新加载日志文件时才适用。配置前应确认服务定义和程序文档,否则可能出现轮转成功但进程仍然写入旧文件的情况。

如果程序不支持重新打开日志,只能使用 copytruncate 作为兼容方案:

/srv/game/logs/*.log {
    daily
    size 500M
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

copytruncate 会复制旧文件后截断原文件。截断和写入同时发生时,可能丢失极少量日志,因此不适合要求完整审计记录的场景。无论采用哪种方式,都应先备份配置并使用调试模式检查:

sudo cp -a /etc/logrotate.d/game-server \
  /etc/logrotate.d/game-server.bak

sudo logrotate -d /etc/logrotate.d/game-server

-d 只进行模拟,不执行实际轮转。确认路径、权限和轮转规则无误后,再安排低峰期执行实际轮转。若需要回滚配置,可恢复备份文件,再根据服务支持情况执行 reload 或重启。

systemd 日志也可能占据大量空间,可先查看其用量:

sudo journalctl --disk-usage

如果确认保留策略允许清理,再考虑按时间或容量删除已归档日志,例如:

sudo journalctl --vacuum-time=14d

或:

sudo journalctl --vacuum-size=2G

这类清理操作不可逆,执行前应确认不会影响故障追溯、合规留存或运营审计。它只处理 systemd journal,不会清理游戏程序自己写入的普通日志文件。

第三步:区分 inode 耗尽

inode 可以理解为文件系统保存文件元数据的数量上限。一个 1 KB 的小文件和一个 10 GB 的大文件,通常各占用一个 inode。因此,磁盘仍有大量可用空间时,也可能因为创建不了新文件而报 No space left on device。

典型判断方式是:

sudo df -h /srv/game
sudo df -i /srv/game

如果磁盘块使用率只有 40% 左右,而 inode 使用率已经达到 95% 或更高,优先判断为 inode 压力,而不是容量不足。具体告警阈值应结合文件系统和业务增长速度设置,90%可以作为关注点,95%以上则应尽快处理。

统计文件数量时,应明确这是可能较慢的遍历操作:

GAME_DIR=/srv/game

sudo find "$GAME_DIR" -xdev -type f 2>/dev/null | wc -l
sudo find "$GAME_DIR" -xdev -type d 2>/dev/null | wc -l

为了定位哪些目录产生了最多文件,可以统计每个文件所在的父目录:

sudo find "$GAME_DIR" -xdev -type f -printf '%h\n' 2>/dev/null \
  | sort | uniq -c | sort -nr | head -n 20

常见结果包括:

  • 每个玩家或房间生成一个很小的状态文件;
  • 临时目录中积累了大量 0 字节文件;
  • 日志被按请求、会话或时间切成数百万个小文件;
  • 补丁下载失败后留下大量分片;
  • 崩溃重试反复生成小型诊断文件。

如果需要单独确认空文件数量:

sudo find "$GAME_DIR" -xdev -type f -size 0 -printf '%p\n' 2>/dev/null \
  | wc -l

不要因为文件大小为 0 就立即全部删除。应先确认文件名模式、修改时间和业务用途,再按保留周期筛选。可以先只打印待处理对象:

sudo find /srv/game/tmp -xdev -type f \
  -name '*.tmp' -mtime +14 -print 2>/dev/null | head -n 100

确认备份和业务影响后,才考虑执行删除。删除操作不可逆,且大量删除本身会产生额外 I/O,可能短时间加重游戏卡顿。更稳妥的处理顺序是先停止产生异常文件的功能或调整其保留策略,再分批清理历史文件,并在每批后复查 inode 使用率。

第四步:判断是否真正存在 I/O 延迟

容量和 inode 正常,并不代表磁盘没有问题。游戏服务常见的 I/O 延迟表现包括:

展示游戏服务器发生周期性卡顿时的真实I/O排障现场。

  • 游戏进程周期性卡顿,但 CPU 使用率并不高;
  • 保存进度、加载地图或写入房间状态时延迟明显;
  • 日志轮转、补丁解压或批量删除期间,游戏响应变慢;
  • iostat 中设备队列持续增加;
  • vmstat 的 wa 长时间偏高。

Linux 上可以使用 sysstat 软件包提供的 iostat 和 pidstat。如果系统没有这些命令,应先按发行版的软件包管理方式安装对应工具。检查时使用实际挂载点对应的块设备:

sudo iostat -xz 1 5

重点关注:

  • await:一次 I/O 从发起到完成的平均等待时间,通常包含排队等待;
  • aqu-sz:平均请求队列长度;
  • %util:设备忙碌比例;
  • r_await、w_await:读写操作分别需要等待多久。

这些指标没有适用于所有存储类型的固定阈值。作为排查参考,低延迟固态存储在正常负载下通常不应长期出现几十到上百毫秒的 await;如果面向实时游戏的业务盘在稳定业务期间持续达到 100 毫秒以上,应优先调查。机械盘、共享存储和高并发写入环境的基线可能不同,不能只凭某个数字判定故障。

进一步确认是哪个进程产生 I/O:

sudo pidstat -d -p ALL 1 5

同时观察系统整体等待:

vmstat 1 5

在 vmstat 输出中,wa 表示 CPU 等待 I/O 的时间比例。wa 偶尔升高可能只是补丁解压或日志轮转造成的短时峰值;如果它与游戏卡顿同时发生,并且 iostat 的队列、await 也持续升高,I/O 延迟的判断才更可靠。

还可以检查内核是否报告了设备或文件系统错误:

sudo journalctl -k -b --no-pager \
  | grep -Ei 'I/O error|buffer I/O|read-only|filesystem error|ext4|xfs'

如果发现文件系统被切换为只读,或者出现反复的 I/O error,不要直接强制重新挂载为可写,也不要在业务运行期间随意执行文件系统修复。应先保护游戏存档和日志,确认挂载设备,安排维护窗口,在卸载文件系统或进入相应维护环境后再执行检查和修复。

df 与 du 不一致时怎么查

这是最容易误判的一类情况。例如:

  • df -h 显示文件系统已使用 96%;
  • du -xsh /srv/game 只统计出 60%;
  • 目录中看不到足以解释差额的大文件。

优先检查进程是否仍然打开着已经被删除的文件:

sudo lsof +L1

如果输出中出现游戏进程或日志进程,且文件名后显示 (deleted),说明目录项已经被删除,但进程仍持有文件描述符。空间不会因为执行 rm 就立即释放,只有进程关闭该描述符后才会回收。

解释 df 与 du 不一致时已删除文件仍占用空间的根本原因。

处理顺序应当是:

  1. 记录进程名、进程号、文件大小和对应服务。
  2. 确认该文件确实是可丢弃的日志,而不是存档、数据库或正在写入的数据文件。
  3. 优先让应用执行重新打开日志的 reload 操作。
  4. 如果应用不支持 reload,再安排维护窗口重启服务。
  5. 重启或 reload 后再次执行 lsof +L1 和 df -h 验证空间是否释放。

不要对仍被进程使用的游戏数据文件强行截断。对于关键存档、状态文件或数据库文件,错误处理可能导致业务数据损坏。

另外,还要排除以下文件系统因素:

挂载点边界

du 默认可能进入子挂载点,也可能因为权限问题漏统计。使用 du -x 后,统计范围限定在当前文件系统。如果游戏目录下存在独立挂载,应分别检查每个挂载点:

sudo findmnt -R /srv/game
sudo df -hT

稀疏文件

某些转储、镜像或缓存文件的“显示大小”很大,但实际占用的磁盘块较少。可以对比逻辑大小和实际占用:

sudo du -sh /srv/game
sudo du --apparent-size -sh /srv/game

两者差距很大时,不要仅根据 ls -lh 判断真实磁盘消耗。

权限导致的统计缺口

普通用户执行 du 时,无法读取的目录会被跳过,结果可能明显偏小。排查容量差异时应使用有权限的账号,并注意命令中的错误输出。不要为了“让 du 统计完整”而对整个游戏目录递归修改权限,权限变更可能破坏服务启动和存档访问。

容器或多层文件系统

如果游戏服务运行在容器或其他隔离环境中,应在服务所在的文件系统命名空间内执行 df、du 和 findmnt。宿主机看到的目录、容器内看到的挂载点以及写入层的占用可能不是同一个统计口径。

一组快速判断示例

以下数值用于说明判断方法,并非特定服务器的实测数据。

用三栏对比快速建立三类典型故障的指标画像和处理方向。

情况一:日志膨胀

df -h:使用率 92%,持续上升
df -i:使用率 18%
du:/srv/game/logs 占用 180 GB
最大文件:server-error.log,单文件 120 GB
iostat:普通时段 await 较低,错误高峰时写入量明显增加

判断:主要问题是大文件日志增长。应先查错误重复原因,再配置轮转和保留周期。仅删除当前日志,可能很快再次增长。

情况二:inode 耗尽

df -h:使用率 38%
df -i:使用率 99.6%
文件数量:数百万个
主要目录:/srv/game/tmp/session,每个文件只有几百字节

判断:是小文件数量耗尽 inode。增加磁盘容量不一定立即解决,因为新增空间不一定带来足够 inode。应清理过期小文件,并修正“每个会话生成一个永久文件”的设计或保留策略。

情况三:I/O 延迟

df -h:使用率 47%
df -i:使用率 22%
iostat:await 持续 150 ms,aqu-sz 持续升高
vmstat:wa 在业务高峰长期偏高
du:没有发现异常增长目录

判断:更接近 I/O 队列或文件系统层面的延迟问题。此时不应通过删除日志来“解决”问题,而要关联 pidstat、游戏事件时间和内核日志,确认是批量写入、轮转、解压还是文件系统错误造成的。

处理后的验证不能省略

完成清理、轮转或服务 reload 后,至少复核以下内容:

sudo df -hT /srv/game
sudo df -ih /srv/game
sudo lsof +L1
sudo iostat -xz 1 5
sudo journalctl -k -b --no-pager | tail -n 100

然后重新观察一段与正常业务相近的时间:

  • 容量使用率是否停止异常上升;
  • inode 数量是否停止快速增加;
  • 日志进程是否已经写入新文件;
  • 是否仍有 (deleted) 文件被进程持有;
  • I/O 延迟是否只在短时操作期间升高,还是持续异常;
  • 游戏进程是否能正常保存、加载和生成新日志;
  • 磁盘变满后失败的写入、存档或补丁任务是否需要重新执行。

最容易遗漏的是“源头已经停止,但历史文件仍然占满空间”。例如,修复了日志级别后,旧的 100 GB 日志仍然存在;或者清理了历史小文件,却没有关闭继续生成小文件的会话任务。复核时既要看当前占用,也要看一段时间内的增长速率,只有增长趋势恢复正常,才算真正完成排查。

目录结构
全文