核心思路是将同步文件读取移出事件循环,避免进程集体停摆;通过预加载、异步I/O、子进程或缓冲队列等方式解耦,确保事件循环持续响应。

核心思路很直接:事件循环里不能做同步读文件,否则整个 Worker 或进程就卡住,所有连接一起等——这不是慢,是“集体停摆”。解决的关键不是优化读文件本身,而是把读操作从事件循环中移出去。
识别并切断同步读文件源头
先确认是不是真在事件循环里读文件。常见陷阱包括:
-
配置热加载:比如 onMessage 中调用
file_get_contents('config.json')或json_decode(file_get_contents(...)) -
日志写入未异步:Monolog 的 StreamHandler、或直接
file_put_contents($log, $msg, FILE_APPEND)(尤其没加LOCK_EX) -
模板/资源动态加载:读取本地 HTML 片段、CSS 文件、i18n 语言包时用了
fopen + fread -
临时文件解析:上传后立刻
file_get_contents($_FILES['f']['tmp_name'])而非走流式处理
用异步或预加载替代运行时读取
不等请求来了再打开磁盘,提前把该读的读进内存:
- 配置类文件(如 config.json、routes.php)在 Worker 启动时一次性加载,存入全局变量或静态属性;改配置后 reload Worker,而非每次请求都重读
- 语言包、静态模板等可预编译为 PHP 数组或 APCu 缓存,避免重复文件 I/O
- 若必须动态读(如用户上传的规则文件),改用 内存映射(mmap) 或 异步子进程(如 proc_open + 非阻塞管道)处理,主循环只收结果
强制落地为非阻塞 I/O 模式
即使无法完全避免文件操作,也要让它不拖垮事件循环:
- 用 libevent / ev / amphp/file 等支持异步文件 I/O 的扩展(PHP 8.1+ 原生支持
stream_set_blocking($fp, false),但需配合轮询或事件驱动框架) - 对大文件读取,拆成小块 +
yield+ 定时器控制,每读一段交还控制权,防止单次耗时过长 - 日志统一走 异步队列:Monolog 配置
Handler\WhatFailureGroupHandler+Handler\BufferHandler(设足够大的$bufferSize和$flushOnOverflow = true),底层由独立线程或定时 flush
监控与验证是否真正解耦
改完别只看响应时间,要确认磁盘 I/O 已不再干扰事件循环:
- 持续运行
pidstat -d -p <worker_pid> 1</worker_pid>,观察 rd_bytes/wr_bytes 是否只在启动或配置更新时突增,请求高峰期保持平稳 - 用
strace -p <pid> -e trace=epoll_wait,read,write</pid>看epoll_wait返回间隔是否稳定(理想值 read 调用是否极少且不连续 - 检查
iostat -x 1的await(SSD 应 %util(长期

















