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

现场排查建议按这个顺序进行:先看 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
重点看四项:
df -hT中的Use%是否持续上升,以及文件系统类型是 ext4、XFS 还是其他类型。df -ih中的 inode 使用率是否已经接近上限。findmnt返回的实际挂载点、块设备和挂载参数,避免检查了错误目录。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 延迟表现包括:

- 游戏进程周期性卡顿,但 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 就立即释放,只有进程关闭该描述符后才会回收。

处理顺序应当是:
- 记录进程名、进程号、文件大小和对应服务。
- 确认该文件确实是可丢弃的日志,而不是存档、数据库或正在写入的数据文件。
- 优先让应用执行重新打开日志的 reload 操作。
- 如果应用不支持 reload,再安排维护窗口重启服务。
- 重启或 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 日志仍然存在;或者清理了历史小文件,却没有关闭继续生成小文件的会话任务。复核时既要看当前占用,也要看一段时间内的增长速率,只有增长趋势恢复正常,才算真正完成排查。