登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

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

按优先级从高到低依次排查即可
  1. 从真实 Web 请求确认 SAPI、进程池和实际加载的 php.ini。
  2. 检查 opcache.validate_timestamps 与 opcache.revalidate_freq。
  3. 核对请求命中的容器、节点、发布目录和脚本绝对路径。
  4. 少量文件用 opcache_invalidate(),大范围变更用 Web SAPI 内的 opcache_reset() 或平滑重启。
  5. 继续检查 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 或主机。此时要让每个实例都执行同一部署动作,或先从流量中摘除旧实例。

Web 请求、PHP-FPM 进程池、CLI SAPI、配置文件与 OPcache 缓存实例的静态边界图
图1:Web 请求可能被分流到不同 PHP-FPM Pool,而 CLI 拥有独立配置与缓存边界;诊断必须落在实际承载流量的进程域。

最关键的配置组合:validate_timestamps 与 revalidate_freq

opcache.validate_timestamps 决定 OPcache 是否检查脚本文件的修改时间。启用时,检查间隔由 opcache.revalidate_freq 控制;设为 0 表示每次请求都检查时间戳。关闭时间戳校验后,revalidate_freq 不再起作用,代码变化必须通过单文件失效、缓存重置或 Web 进程重启才能被加载。

配置实际行为发布要求
validate_timestamps=1
revalidate_freq=0
每次请求检查脚本时间戳适合开发或更新频繁环境,运行开销相对更高
validate_timestamps=1
revalidate_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。

OPcache 时间戳策略、发布绝对路径、单文件失效、全量重置、二级文件缓存与预加载的静态关系图
图2:时间戳策略决定自动发现窗口,发布路径决定失效目标;内存重置、文件缓存与预加载属于不同层级。

仍未解决时,再查这四项

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。这类明细可能暴露服务器路径,只能在受保护的诊断环境使用,不应直接返回给公网用户。

一份可执行的发布排查清单

  1. 请求真实域名,确认响应的构建号与目标版本一致。
  2. 从该请求所在 FPM 读取 PHP_SAPI、PID、php_ini_loaded_file() 与 OPcache 有效指令。
  3. 连续请求多次,检查是否命中不同实例、不同 Pool 或不同版本。
  4. 若启用时间戳校验,等待一个 revalidate_freq 后复测。
  5. 若关闭时间戳校验,对真实绝对路径执行 opcache_invalidate(..., true),或在 Web SAPI 内重置。
  6. 若使用 preload,直接走 PHP 进程平滑重载或重启。
  7. 若启用 file cache,检查二级缓存目录与部署清理策略。
  8. 核对 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 的有效配置和构建号,再选择失效、重置或重启,通常比盲目反复清缓存更快。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>