hyperf高并发下磁盘io卡住,是因为fopen等同步文件操作不被swoole协程接管,仍走libc阻塞路径,直接阻塞worker进程;必须启用swoole aio并改用system::readfile/writefile等协程api,同时禁用opcache.file_cache等隐性io源。

Hyperf高并发下磁盘IO为什么卡住?
不是PHP代码慢,是 fopen、file_get_contents、file_put_contents 这类同步文件操作在协程中会直接阻塞整个 worker 进程。Swoole 默认不接管这些系统调用,它们走的是标准 libc 阻塞路径,哪怕你开了 enable_coroutine => true,也照卡不误。
典型现象:QPS 上不去,swoole_server->stats() 显示 request_count 增长缓慢,但 worker_request_count 差距大;strace -p [worker_pid] -e trace=open,read,write 能看到大量阻塞的 read() 系统调用。
必须开启 Swoole 的 aio 并重写文件操作逻辑
Swoole 的 aio(异步 I/O)模块基于 Linux 的 io_uring(5.1+)或线程池模拟,能把文件读写转为非阻塞协程友好的调用。但它不会自动替换 PHP 原生函数——你得主动用 Swoole\Coroutine\System::readFile 和 Swoole\Coroutine\System::writeFile。
- 确保 Swoole 编译时启用了
--enable-async-redis --enable-swoole-aio(检查php --ri swoole输出含aio => enabled) - Hyperf 3.x 默认未启用
aio,需在config/autoload/server.php中显式开启:'aio' => true - 禁用所有
fopen/file_get_contents,改用:use Swoole\Coroutine\System; $content = System::readFile('/path/to/log.txt'); // 返回 string 或 null System::writeFile('/path/to/cache.bin', $data); // 返回 bool - 注意:
System::readFile不支持流式读取大文件,超 2MB 容易 OOM;大文件请用Swoole\Coroutine\Stream+ 分块读
日志、缓存、临时文件这三类最容易踩坑
Hyperf 默认日志驱动(Monolog/Handler/StreamHandler)底层仍是 fopen,必须替换成协程安全方案:
- 用
Hyperf\Logger\Handler\StdoutHandler+ 日志采集器(如 Loki/Fluent Bit),绕过本地磁盘 - 若必须落盘,改用
Swoole\Coroutine\Channel做缓冲,由 task_worker 统一刷盘(避免协程间竞争) - Redis 缓存比
file驱动快一个数量级,hyperf/cache默认已支持redis,别用file驱动存 session 或高频 key - 上传临时文件(
$_FILES)默认存在/tmp,且move_uploaded_file是阻塞的——应立刻用Swoole\Http\Request->getUploadedFiles()获取句柄,再用System::writeFile写目标路径
为什么只开 aio 还不够?
因为 aio 只解决「文件读写」,但真实瓶颈常藏在更上游:比如你用 file_put_contents('log.txt', $msg, FILE_APPEND),即使改成 System::writeFile,频繁小写仍触发大量磁盘 seek;又比如 opcache.file_cache 指向 SSD 目录,但 PHP-FPM 时代遗留的 opcache.revalidate_freq=2 在常驻进程中会导致每两秒扫一次目录,产生隐性 IO。
真正要动的配置不止一处:opcache.file_cache 必须关掉(Hyperf 不依赖它),realpath_cache_size 设为 4M 以上,apc.enable_cli 关闭(CLI 模式下 APC 无效还抢资源)。这些细节不处理,aio 开了也白开。











