OPcache 编译缓存的工作原理是什么?
OPcache 将首次编译产生的 Opcodes 存入共享内存,后续请求跳过编译直接执行缓存。
原理:PHP 的编译流程
PHP 在执行每个脚本前须经历完整编译链路:
- .php 源码
- 词法分析 Tokenization
- Token 流
- 语法分析 Parsing
- AST 抽象语法树
- 编译 Compilation
- Opcodes 操作码序列
- Zend VM 执行
编译是纯 CPU 密集型操作。对于不会变化的文件(生产环境的框架代码、业务代码),每次请求都重新编译是完全没有意义的浪费。
OPcache 的介入点
OPcache 在「生成 Opcodes」后、「Zend VM 执行前」拦截,将 Opcodes 序列化存入共享内存(shared memory)。
后续请求到达时,OPcache 先检查共享内存中是否有有效缓存:
- 命中:直接从共享内存取出 Opcodes,交由 Zend VM 执行,完全跳过编译链路。
- 未命中:重新编译,写入共享内存。
共享内存架构与 PHP-FPM 的关系
PHP-FPM 运行时存在一个 master 进程和多个 worker 进程。OPcache 将共享内存映射到所有 worker 进程的地址空间中,使得: 同一份 Opcodes 只编译一次(通常由第一个处理该文件的 worker 完成) 其余所有 worker 共享读取同一块物理内存,不需要各自维护副本 在内存层面,100 个 worker 的 OPcache 内存消耗与 1 个 worker 相近。
-
生产环境部署新代码后,OPcache 缓存如何清除?
简答:有三种方式。① 重启 PHP-FPM(最彻底,但会中断正在处理的请求);② 调用
opcache_reset()(通过一个 Web 请求执行,清除所有缓存);③ 调用opcache_invalidate($file, true)(只清除指定文件的缓存,精细化控制)。CI/CD 流程中常见做法是部署完成后发一个 HTTP 请求触发opcache_reset(),或使用 PHP-FPM 的 graceful reload(USR2信号)。 -
为什么生产环境推荐将
validate_timestamps设为0?有什么风险?简答:
validate_timestamps=0避免每次请求检查 mtime,消除文件系统 I/O 开销,OPcache 命中率接近 100%。风险在于:部署新代码后如果忘记清除 OPcache,线上仍会执行旧代码。因此必须在部署流程中明确包含缓存清除步骤。 -
Preload 与普通 OPcache 缓存有什么本质区别?
简答:普通 OPcache 缓存由「第一次被请求执行的 worker」触发编译并写入,有冷启动问题(重启 FPM 后首批请求需要重新编译)。Preload 在 FPM master 启动时执行,不需要等待请求触发,且 preload 的内容不会因
opcache_reset()失效(只能重启 FPM 清除)。代价是 preload 文件列表是静态的,框架升级时需要修改 preload 脚本。 -
opcache_get_status()能观察到哪些关键信息?简答:返回数组包含:
opcache_enabled(是否启用)、memory_usage(已用/空闲/浪费内存,浪费比例过高意味着需要opcache_reset())、opcache_statistics(命中次数、未命中次数、命中率、缓存的脚本数量)。生产环境应定期监控命中率,通常健康值 > 99%。
-
以为重启 Nginx 能清除 OPcache:OPcache 是 PHP-FPM 的扩展,存储在 FPM 进程的共享内存中。重启 Nginx 只影响反向代理层,PHP-FPM 进程和其共享内存不受影响。清除 OPcache 必须针对 PHP-FPM(重启或调用
opcache_reset())。 -
误以为
validate_timestamps=1是每次请求都重新编译:validate_timestamps=1配合revalidate_freq=N时,只在上次验证超过 N 秒后才检查文件 mtime,并非每次请求都检查。设为revalidate_freq=0才是每次请求都验证(完全不缓存效果,通常不建议)。 -
忽略
opcache.memory_consumption与opcache.interned_strings_buffer的关系:interned strings 的内存从memory_consumption总量中划拨,而非独立分配。若两者设置不合理(如memory_consumption=64M但interned_strings_buffer=32M),留给 Opcodes 缓存的内存只剩 32 MB,大型框架可能因内存不足而频繁驱逐缓存(来源:php.net 官方文档用户评注)。