PHP OPcache 加速实战:命中率查看、缓存预热与常见踩坑

很多站长把网站提速的重点放在了 Nginx 压缩、CDN 和数据库优化上,却忽略了 PHP 自身的一个免费加速器——OPcache。它是 PHP 官方内置的字节码缓存扩展,开启之后通常能把 PHP 脚本的执行效率提升 2 到 5 倍,对 WordPress、Typecho 这类每次请求都要加载大量 PHP 文件的程序效果尤其明显。更关键的是,它几乎不花一分钱,只要在服务器上改几行配置就能生效。这篇文章会从原理讲到实战配置,再到命中率查看和常见踩坑,帮你把 OPcache 真正用起来。

一、OPcache 到底做了什么

PHP 是解释型语言,一次请求的处理过程大致是这样的:读取磁盘上的 .php 文件 → 词法分析 → 语法分析 → 编译成 opcode(操作码)→ Zend 虚拟机执行 opcode。默认情况下,每次请求都要把上面这套流程重跑一遍。一个 WordPress 首页可能加载上百个 PHP 文件,这些重复的编译动作会消耗可观的 CPU 时间。

OPcache 的思路很简单:把编译好的 opcode 存到共享内存里,下次请求同一个文件时直接取出 opcode 执行,跳过"读文件 + 编译"两步。只有在源文件被修改、内存不足或者缓存被显式清空时,才会重新编译。

它的收益主要体现在三个地方:CPU 使用率下降、单请求响应时间缩短、PHP-FPM 能承载的并发连接数上升。对于低配的 1 核 1G VPS 来说,这种提升几乎是"免费的扩容"。

二、确认 OPcache 是否已安装

PHP 5.5 之后 OPcache 是内置扩展,但很多发行版把它拆成了独立包,默认并未启用。先确认状态:

php -m | grep -i opcache
php -i | grep -i opcache.enable

如果第一条命令没有任何输出,说明扩展没装或者没加载。Debian/Ubuntu 下安装:

apt install php8.2-opcache
systemctl restart php8.2-fpm

CentOS/RHEL 系列:

dnf install php-opcache
systemctl restart php-fpm

装好之后再执行 php -m,应该能看到 opcache 已经在扩展列表里。如果用的是宝塔面板,PHP 扩展页面里勾选 OPcache 即可,本质上也是改同一份 ini 文件。

三、生产环境推荐配置

OPcache 的配置全部写在 php.ini 或者 /etc/php/8.2/fpm/conf.d/10-opcache.ini 这类独立文件里。下面是一份在 2G 内存 VPS 上验证过的生产级配置:

[opcache]
opcache.enable=1
opcache.enable_cli=0

; 共享内存大小,单位 MB
opcache.memory_consumption=128

; 最多缓存多少个 PHP 文件
opcache.max_accelerated_files=10000

; 保留 20% 的内存给"重启后 20 分钟内被访问过的文件",
; 避免缓存刚清空就被大量冷文件挤满
opcache.interned_strings_buffer=16

; 文件修改后多久检查一次时间戳(秒)
opcache.revalidate_freq=60

; 生产环境用 0,表示不检查文件时间戳,性能最高
opcache.validate_timestamps=1

; 保留多少空闲内存百分比再触发缓存淘汰
opcache.max_wasted_percentage=5

; 保存注释信息,很多框架和 ORM 依赖注解
opcache.save_comments=1

几个参数需要重点解释:

  • memory_consumption:WordPress 这类程序缓存全部文件大约需要 64–128MB。设置过小会导致频繁淘汰,命中率上不去。可以先用 128,观察一段时间再调整。
  • max_accelerated_files:建议设置成比实际 PHP 文件数略大。WordPress + 插件 + 主题通常有 3000–6000 个文件,设成 10000 比较保险。
  • validate_timestamps:这是最容易被误配的一项。如果设为 0,你修改 PHP 文件后必须手动重启 PHP-FPM 或者调用 opcache_reset() 才会生效,很多站长改了代码发现"没变化",就是踩了这个坑。开发环境一定要用 1;生产环境如果想追求极限性能,可以配合部署脚本在发布后自动 reload PHP-FPM。
  • revalidate_freq:值越大,检查文件时间戳的 IO 开销越小。60 秒是性能和便利性的平衡点。

四、查看命中率,判断配置是否有效

光开起来不够,还要能验证它真的在起作用。最直接的办法是写一个探针脚本,放到网站目录下临时访问:

<?php
$s = opcache_get_status();
echo "缓存使用: " . round($s['memory_usage']['used_memory']/1048576, 1) . " MB\n";
echo "空闲内存: " . round($s['memory_usage']['free_memory']/1048576, 1) . " MB\n";
echo "缓存文件数: " . $s['opcache_statistics']['num_cached_scripts'] . "\n";
echo "命中次数: " . $s['opcache_statistics']['hits'] . "\n";
echo "未命中次数: " . $s['opcache_statistics']['misses'] . "\n";
echo "命中率: " . round($s['opcache_statistics']['opcache_hit_rate'], 2) . "%\n";
echo "缓存被淘汰: " . $s['opcache_statistics']['oom_restarts'] . "\n";

判断标准很明确:

  • 命中率稳定在 98% 以上算健康,说明绝大多数请求都直接用了缓存的 opcode。
  • 如果命中率长期低于 90%,多半是 memory_consumption 或 max_accelerated_files 太小,缓存被反复挤掉。
  • 如果出现 oom_restarts 大于 0,说明内存不足导致缓存被整体重置,必须调大 memory_consumption。
  • 如果 num_cached_scripts 停在一个固定值不再增长,检查 max_accelerated_files 是否被占满。

⚠️ 探针脚本会暴露服务器内部信息,看完记得立刻删除,或者用 IP 白名单限制访问。更正规的做法是安装 opcache-gui 这种单文件管理面板,或者用 cachetool 命令行工具通过 PHP-FPM 的 unix socket 读取状态,不经过 Web 层。

五、缓存预热:让第一个访客不再当小白鼠

OPcache 是"按需缓存"的——只有被访问过的文件才会进入缓存。这意味着每次重启 PHP-FPM(比如部署新代码)之后,缓存都是空的,前几批访客的请求会慢一些,因为要边执行边编译。对于有一定流量的站点,可以通过预热脚本主动把热点文件塞进缓存:

#!/bin/bash
# warmup.sh —— 部署后预热 OPcache
curl -s -o /dev/null "https://www.example.com/"
curl -s -o /dev/null "https://www.example.com/index.php"
curl -s -o /dev/null "https://www.example.com/sitemap.xml"
# WordPress 常用入口
curl -s -o /dev/null "https://www.example.com/wp-login.php"
sleep 1
echo "预热完成"

把这个脚本挂在部署流程的最后一步,或者用 crontab 在每天凌晨低峰期配合 systemctl reload php-fpm 一起执行。注意用 reload 而不是 restart,reload 会平滑重启 worker 进程,不会中断正在处理的请求,也避免了缓存被完全清空。

六、常见踩坑与排查

坑一:改了代码页面没变化。最常见的原因就是 opcache.validate_timestamps=0。排查顺序:先确认配置值,再手动 systemctl reload php-fpm,最后清空缓存。也可以在脚本里调用:

<?php
opcache_reset();   // 清空全部缓存,慎用,会瞬时降低性能
opcache_invalidate('/path/to/file.php', true);  // 只失效单个文件

坑二:CLI 和 FPM 的缓存是分开的。命令行执行 PHP 脚本和在 Web 里执行是两套独立的 OPcache 空间(除非开启 enable_cli)。用 php -i 看到的统计和网页里看到的不是一回事,排查时不要混淆。一般建议 CLI 保持关闭,因为命令行脚本执行一次就退出,缓存没有意义,反而浪费内存。

坑三:open_basedir 与 opcache 冲突。如果同时启用了 open_basedir 限制目录访问,某些情况下 OPcache 会因为拿不到文件的真实路径而拒绝缓存,表现为文件反复未命中。可以在 php.ini 里加 opcache.validate_permission=1 观察,或检查错误日志里是否有相关警告。

坑四:多个 PHP 版本共存时改错了文件。服务器上同时装了 PHP 7.4 和 8.2 时,两套配置在 /etc/php/7.4//etc/php/8.2/ 下各有一份。改完一定要确认 FPM 实际加载的是哪一份,用 php-fpm8.2 -i | grep opcache 或者 phpinfo 页面核实。

坑五:缓存了不该缓存的东西。少数程序会在运行时动态生成 PHP 文件(比如模板编译、缓存文件),这些文件如果被 OPcache 缓存,可能出现逻辑异常。解决办法是给这类目录配置 blacklist(旧版本)或者用 opcache.blacklist_filename 指定排除列表。

七、配合其他优化,效果叠加

OPcache 解决的是"PHP 代码编译"这一层的开销,它和以下几项可以叠加使用,互不冲突:

  • PHP-FPM 进程调优:OPcache 降低了单请求的 CPU 消耗,意味着同样的 pm.max_children 可以支撑更多并发,两者一起调效果最好。
  • 页面缓存(Redis / Nginx FastCGI Cache):命中页面缓存时压根不会执行 PHP,OPcache 只在未命中的请求上发力。
  • JIT(PHP 8 的即时编译):PHP 8.0 之后支持 JIT,可以理解为在 OPcache 基础上进一步把 opcode 编译成机器码。但对 WordPress 这类 IO 密集、逻辑分支多的程序,JIT 收益有限,反而可能增加内存占用,建议先在测试环境验证再决定是否开启。

八、小结

OPcache 是投入产出比最高的 PHP 优化手段之一:装一个扩展、改几行配置,就能让站点响应明显变快。核心记住三点——内存和文件数上限要留足、生产环境用 validate_timestamps 控制代码生效时机、装完一定要用状态脚本确认命中率。做完这三步,再回头测一次首页 TTFB,通常能看到肉眼可见的改善。对于预算有限、只能靠一台小 VPS 撑起整站的草根站长来说,这种"零成本提速"值得优先做掉。

Last modification:September 11th, 2026 at 07:56 am

Leave a Comment