不能在Worker进程里同步写文件日志,必须用Redis中转+独立消费者落盘;否则QPS超200即排队,超500易触发max_execution_time超时或Too many open files错误——根源是flock阻塞卡死事件循环。

直接结论:不能在 Worker 进程里同步写文件日志,必须用 Redis 中转 + 独立消费者落盘。 否则 QPS 超 200 就开始排队,500 以上大概率触发 max_execution_time 超时或 Too many open files 错误——这不是磁盘慢,是 flock 阻塞把整个事件循环卡死了。
为什么 file_put_contents(FILE_APPEND) 在 Workerman 里会崩
Workerman 是常驻进程,但 file_put_contents($path, $log, FILE_APPEND) 底层仍走 fopen + flock + fwrite + fflush 同步路径。Linux 对同一文件的 flock 默认阻塞,所有 Worker 进程争抢同一个文件锁,本质是串行化写入。
- 即使开了
FILE_APPEND,PHP 仍会先flock再写,不是原子追加 - 日志量大时,
flock等待时间远超单次请求耗时,拖垮吞吐 - 在
onWorkerStart里反复fopen同一个文件句柄,反而加剧锁冲突
Redis 异步中转的最小可行配置
这是目前最稳、最易落地的方案,不依赖 pcntl 或协程 fork,兼容 Swoole 和 FPM 模式。
- 在
config/bootstrap.php中注册 Monolog 实例,handler 用Monolog\Handler\RedisHandler -
RedisHandler构造时必须传['timeout' => 0.1, 'read_write_timeout' => 0.1],否则 Redis 偶发延迟会卡住主线程 - 消费者启动命令:
php webman queue:work --tries=3 --delay=100,避免单条坏日志拖垮队列 - 消费者代码里必须
try {} catch (Throwable $e) { file_put_contents(RUNTIME_PATH.'/log/fallback.log', ...); },Redis 故障时日志不能静默消失
ClickHouse 落盘时 DateTime 字段总为 0 怎么办
不是连接问题,也不是权限问题,是类型错配导致的静默失败:ClickHouse 的 DateTime 字段只接受秒级时间戳整数(如 1747901520),传字符串 '2026-05-22 08:12:00' 或毫秒戳 1747901520123 都会转成 0(即 1970-01-01)。
- 写入前统一用
strtotime($datetime_string)或date_timestamp_get($datetime_obj)转成整数 - 确认 ClickHouse 客户端是单例复用,不能每次写都 new 一个新连接
- 定时器内禁用任何同步写操作,包括
file_put_contents和echo到 stdout
真正容易被忽略的是:Redis 中转只是“第一跳”,落地端(比如 ClickHouse 写入消费者)本身也得异步、批处理、单例复用连接。否则日志在队列里不丢,一到落盘环节又卡死,等于白搭。

















