PHP本地文件缓存易引发磁盘IO瓶颈,因小文件高频随机写入导致seek频繁、%util飙升;替代方案包括合并写入、原子rename覆盖、挂载tmpfs,同时stat调用本身亦可压垮IO队列。

会,而且非常容易。PHP 8.3 下用本地文件做缓存(比如 file_put_contents + file_get_contents),在高并发或高频更新场景下,几乎必然触发磁盘 IO 瓶颈——尤其当缓存项小、数量多、写入频繁时。
为什么小文件高频写入特别伤磁盘
操作系统对小文件的随机写入效率极低,尤其是机械硬盘;即使是 SSD,大量 fopen/fclose + 同步写入也会快速拉高 %util 和 await。PHP 8.3 默认启用更严格的内存管理,但对文件系统层无任何优化,file_put_contents($path, $data, FILE_APPEND) 每次都可能触发真实磁盘 seek + write。
- 每次写入都涉及:打开文件句柄 → 定位偏移 → 写入数据 → 刷盘(除非加
LOCK_EX或系统配置禁用缓冲)→ 关闭句柄 -
FILE_APPEND在 NFS 或某些挂载选项下会退化为“读-改-写”模式,IO 放大数倍 - APCu / OPcache 不缓存文件内容,只缓存 opcode,对这类 IO 完全无效
哪些缓存模式最容易出问题
以下写法在压测中常导致 iowait > 30%、请求超时、日志写满 /var/log:
- 用文件名哈希做缓存键,每请求生成一个新文件(如
cache/req_7a2f1b.txt) - 未加锁直接
file_put_contents('counter.txt', (int)file_get_contents('counter.txt') + 1) - 框架里自定义的
FileCache驱动,每秒被调用数十次以上 - 把 session 存到
files驱动且未配置session.gc_maxlifetime,导致/tmp积累海量过期小文件
替代方案:不改逻辑也能降 IO 的三招
无需重构成 Redis,也能显著缓解压力:
立即学习“PHP免费学习笔记(深入)”;
- 强制合并写入:用数组暂存多个缓存值,每 5–10 秒或累计 50 条后统一
file_put_contents序列化写入单个文件 - 换用原子写入:先写临时文件(
$tmp = tempnam(sys_get_temp_dir(), 'cache_')),再rename()覆盖原文件——避免FILE_APPEND锁竞争 - 切到内存文件系统:将缓存目录挂载到
tmpfs(如mount -t tmpfs -o size=512M tmpfs /var/cache/myapp),IO 变成内存操作,但需注意重启丢失
真正该警惕的隐藏陷阱
很多人以为“只是读缓存,应该没事”,却忽略了 PHP 的 file_get_contents 在文件不存在时仍会触发一次磁盘 stat;而高并发下大量 stat 请求本身就能打满 IO 队列。更隐蔽的是:OPcache 缓存了脚本,但不会缓存你代码里 file_get_contents('config.json') 的结果——每次都要走一遍 VFS 层。



















