OPcache 更新代码后仍命中旧脚本,该检查哪些配置
来源:17golang原创
时间:2026-10-08 19:42:09 382浏览 收藏
PHP 代码已经更新,页面却仍像在执行旧脚本,优先检查的不是浏览器缓存,而是实际承载请求的 PHP 进程、时间戳校验策略、发布绝对路径,以及 file cache 或 preload。最常见的误区是:在命令行执行了 opcache_reset(),但线上请求由 PHP-FPM 提供;CLI 与 Web SAPI 的配置和 OPcache 实例并不是同一个。
官方配置文档:https://www.php.net/manual/en/opcache.configuration.php
官方函数文档:https://www.php.net/manual/en/ref.opcache.php
- 从真实 Web 请求确认 SAPI、进程池和实际加载的
php.ini。 - 检查
opcache.validate_timestamps与opcache.revalidate_freq。 - 核对请求命中的容器、节点、发布目录和脚本绝对路径。
- 少量文件用
opcache_invalidate(),大范围变更用 Web SAPI 内的opcache_reset()或平滑重启。 - 继续检查
opcache.file_cache、opcache.preload、opcache.use_cwd和opcache.revalidate_path。
先确认:你查的是哪个 PHP 进程
同一台机器可以同时存在 CLI、多个 PHP-FPM Pool、不同 PHP 版本或多个容器。它们可能加载不同的配置文件,也可能拥有不同的 OPcache 共享内存。PHP 官方文档特别说明,命令行进程的 opcode cache 与 Web 服务器或 PHP-FPM 的缓存是分开的,因此在 CLI 中调用 opcache_reset(),不会替线上 FPM 清掉缓存。
不要只看 php -i。应该临时建立一个只允许部署系统访问的内部诊断入口,在真实域名、真实负载均衡路径下读取有效配置。下面示例只返回定位所需字段;实际使用时仍应放在内网并接入现有部署鉴权,排查结束后删除。
PHP_SAPI,
'pid' => getmypid(),
'loaded_ini' => php_ini_loaded_file(),
'opcache_enabled' => $status['opcache_enabled'] ?? false,
'restart_pending' => $status['restart_pending'] ?? false,
'validate_timestamps' => $directives['opcache.validate_timestamps'] ?? null,
'revalidate_freq' => $directives['opcache.revalidate_freq'] ?? null,
'file_update_protection' => $directives['opcache.file_update_protection'] ?? null,
'file_cache' => $directives['opcache.file_cache'] ?? null,
'preload' => $directives['opcache.preload'] ?? null,
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
如果请求轮询几次后出现不同 PID、不同配置值或不同应用版本,问题已经不只是单个 OPcache:负载均衡后可能存在多个未同步的 FPM Pool、Pod 或主机。此时要让每个实例都执行同一部署动作,或先从流量中摘除旧实例。

最关键的配置组合:validate_timestamps 与 revalidate_freq
opcache.validate_timestamps 决定 OPcache 是否检查脚本文件的修改时间。启用时,检查间隔由 opcache.revalidate_freq 控制;设为 0 表示每次请求都检查时间戳。关闭时间戳校验后,revalidate_freq 不再起作用,代码变化必须通过单文件失效、缓存重置或 Web 进程重启才能被加载。
| 配置 | 实际行为 | 发布要求 |
|---|---|---|
validate_timestamps=1revalidate_freq=0 | 每次请求检查脚本时间戳 | 适合开发或更新频繁环境,运行开销相对更高 |
validate_timestamps=1revalidate_freq=2 | 最多存在约一个检查间隔的旧代码窗口 | 发布后短暂旧结果可能是预期行为 |
validate_timestamps=0 | 文件改变不会被自动发现 | 部署流程必须显式失效、重置或重启 |
因此,看到“偶尔两秒后恢复”时先看 revalidate_freq;看到“永远不恢复,重启才好”时优先看 validate_timestamps=0、preload 或错误的刷新进程。
更新了文件,为什么路径仍可能不对
现代部署经常把新版本写入新目录,再把 current 软链接切到新 release。对 OPcache 来说,绝对路径、解析后的真实路径和进程已经缓存的脚本键都很重要。只对旧路径调用失效,新的请求却命中新路径;或者多个实例仍挂载旧 release,都可能表现为“明明更新了还在跑旧代码”。
先在业务响应中临时输出一个无敏感信息的构建号,再把以下内容一起核对:
- 每个实例的构建号是否一致;
- 入口脚本的
realpath()是否指向当前 release; - PHP-FPM 工作目录、DocumentRoot 和容器挂载是否一致;
- 代码是否以原子替换方式落盘,还是原地覆盖正在读取的文件;
opcache.file_update_protection是否让刚写入的文件暂时不进入缓存。
opcache.file_update_protection 的默认值为 2 秒,用于避免缓存尚未写完的新文件。如果发布采用完整目录加原子切换,可以评估把它设为 0;如果仍是原地写文件,则不应为了“立即生效”贸然关闭保护。
单文件失效、全量重置和进程重启怎么选
变更文件少、绝对路径明确时,优先用 opcache_invalidate($filename, true)。第二个参数为 true 时,不依赖 mtime 是否变新,适合原子替换或保留时间戳的部署流程。
如果一次发布修改了大量 PHP 文件,可从实际承载 Web 请求的 SAPI 调用 opcache_reset()。它会重置当前内存 opcode cache,脚本在下一次请求时重新载入,但不会清理 opcache.file_cache 指向的二级文件缓存。
以下情况更适合平滑重载或重启 PHP 进程:启用了 preload;无法保证所有 FPM Pool/Pod 都收到失效请求;发布涉及大量文件并要求版本强一致;或者需要同时切换 PHP 扩展和配置。刷新接口本身具有高影响力,只能作为部署系统内部能力,不能留成公开 URL。

仍未解决时,再查这四项
1. opcache.file_cache
配置了 opcache.file_cache 后,磁盘目录会成为二级缓存。官方说明 opcache_reset() 只重置内存缓存,而 opcache_get_status() 也不提供 file cache 状态。因此,内存重置后仍旧时,要核对 file cache 路径、目录权限与部署清理策略;单文件 opcache_invalidate() 会同时让对应的文件缓存条目失效。
2. opcache.preload
预加载脚本在服务器启动时加载。PHP 官方预加载文档明确指出,已预加载的代码不能靠普通缓存重置更新,必须重启 PHP 进程。如果旧类来自 preload 文件或其依赖链,继续反复调用 opcache_reset() 没有意义。
3. opcache.use_cwd 与 opcache.revalidate_path
opcache.use_cwd=1 会把当前工作目录加入缓存键,避免不同目录下同名脚本发生碰撞。关闭它可以节省少量键空间,但多个应用存在同名脚本时风险更高。opcache.revalidate_path=0 时,通过 include_path 找到并缓存的同名文件可能被复用,之后同名的其他文件不一定被重新搜索。遇到插件目录、共享库或多站点同名文件时,应同时检查这两项。
4. 状态数据是否证明命中了旧脚本
opcache_get_status(true) 可以返回内存缓存中的脚本信息,包括命中次数、内存消耗和时间戳等。PHP 8.3 及以后,脚本项还可能包含下次校验时间 revalidate。这类明细可能暴露服务器路径,只能在受保护的诊断环境使用,不应直接返回给公网用户。
一份可执行的发布排查清单
- 请求真实域名,确认响应的构建号与目标版本一致。
- 从该请求所在 FPM 读取
PHP_SAPI、PID、php_ini_loaded_file()与 OPcache 有效指令。 - 连续请求多次,检查是否命中不同实例、不同 Pool 或不同版本。
- 若启用时间戳校验,等待一个
revalidate_freq后复测。 - 若关闭时间戳校验,对真实绝对路径执行
opcache_invalidate(..., true),或在 Web SAPI 内重置。 - 若使用 preload,直接走 PHP 进程平滑重载或重启。
- 若启用 file cache,检查二级缓存目录与部署清理策略。
- 核对
realpath()、软链接目标、容器挂载和每个实例的 release 目录。
常见问题
把 revalidate_freq 设为 0 就一定立即生效吗?
不一定。它只有在 opcache.validate_timestamps=1 时才生效,也解决不了错误进程池、旧 Pod、file cache、preload 或发布路径不一致。
为什么 CLI 里 reset 返回 true,网页仍是旧代码?
因为 CLI 与 PHP-FPM 的 opcode cache 分离。返回 true 只说明 CLI 所属缓存执行了重置,不代表承载网页请求的 FPM 缓存被处理。
每次发布都重启 PHP-FPM 可以吗?
可以作为强一致的部署策略,尤其在关闭时间戳校验或使用 preload 时,但应使用进程管理器提供的平滑重载能力,并确认连接排空、健康检查和多实例滚动顺序。只改少量普通脚本时,精确失效通常影响更小。
归纳起来,旧脚本问题要按“请求落在哪个进程—该进程加载什么配置—脚本使用哪个绝对路径—缓存位于哪一层”来查。先拿到真实 FPM 的有效配置和构建号,再选择失效、重置或重启,通常比盲目反复清缓存更快。
-
371 收藏
-
347 收藏
-
112 收藏
-
387 收藏
-
142 收藏
-
117 收藏
-
167 收藏
-
448 收藏
-
341 收藏
-
305 收藏
-
284 收藏
-
413 收藏
-
135 收藏
-
246 收藏
-
162 收藏
-
417 收藏
-
104 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习