在高并发场景下,日志写入往往是制约 PHP-FPM 性能的隐形杀手。本指南从底层的服务器硬件到上层的应用框架,提供全链路的日志性能优化策略。
最底层的物理限制往往决定了性能上限。
磁盘 I/O (Disk I/O):
/var/log 或应用 runtime)挂载到独立的物理磁盘,避免与数据库或系统交换分区争抢 I/O。文件系统 (File System):
noatime 选项(mount -o noatime ...),避免每次读取文件都更新访问时间,减少元数据写入。内存缓冲区 (Page Cache):
尽管 Nginx 不直接处理 PHP 日志,但它的相关配置会影响整体 I/O。
关闭非必要日志:
access_log off;: 如果不需要 Nginx 访问日志(例如已经有了完善的 PHP 业务日志或 CDN 日志),直接关闭。Buffer 写入: 如果必须开启,使用 buffer 模式:
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
这会在内存积攒 32k 或每隔 5 秒才写一次盘,极大减少 IOPS。
慢日志 (Slow Log):
request_slowlog_timeout。生产环境若无性能问题,建议关闭,因为记录堆栈信息开销巨大。Catch Workers Output:
catch_workers_output = no (默认): 除非你要用 stdout 方案,否则保持关闭。让 PHP 进程直接写文件比通过管道传给 FPM 主进程再写文件效率更高(少一次上下文切换和数据拷贝)。应用层的优化往往能带来数量级的提升。
不要让 PHP 进程等待磁盘 I/O。
方案 A: 队列缓冲 (Redis/Kafka)
Yii::info() -> 推送 Redis List (内存操作, <1ms) -> 结束请求。RedisTarget 继承 yii\log\Target。方案 B: 内存缓冲队列 (ZeroMQ)
⚠️ ZeroMQ php版本很久没有维护了。
如果你坚持直接写文件,必须调整缓冲策略。
增大 exportInterval:
Target::export()。Logger 对象的分发次数。合并写入 (Batch Write):
FileTarget 默认是一次性 file_put_contents 写入所有积攒的消息。messages 数组积累得足够多再写。但受限于 PHP 请求声明周期,请求结束时必须强制刷盘。按需记录: 不要记录 DEBUG 或过于详细的 INFO。
采样记录: 对于高频接口(如心跳检测、健康检查),仅记录 1% 的流量。
if (rand(1, 100) === 1) {
Yii::info('Heartbeat', 'system');
}
'logVars' => []: 这是一个常见的性能杀手。默认情况下 Yii 会记录 $_SERVER 等所有全局变量。这些数据在序列化时不仅耗 CPU(特别是内容很大时),而且占用大量磁盘 I/O。生产环境必须置空。如果一次请求不仅写 app.log,还要同时写 payment.log, trace.log等,性能损耗不是简单的线性叠加,而是倍增。
真正的大型高并发项目都是有独立的日志服务器。
推荐配置 (PHP-FPM + Yii2)
硬件: SSD + noatime 挂载。
Web Server: Nginx access_log 使用 buffer。
Yii2 配置:
'log' => [
'flushInterval' => 1000,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'exportInterval' => 1000,
'logVars' => [], // 关键:关闭上下文 dump
'prefix' => function() { return ''; }, // 简化前缀
// ...
]
]
]
进阶: 如果上述仍无法满足,升级为 Redis 队列异步写。
本文由 best 创作,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。