本文目录导读:

PHP预加载(OPcache Preloading)是 PHP 7.4 引入的一个强大特性,它可以在服务器启动时将常用类预加载到共享内存中,从而显著提升性能。
下面从原理、配置、实战、注意事项四个维度来拆解。
核心原理
正常情况下,一个 PHP 请求处理完毕后,所有的类定义都会被销毁(或标记为可回收),下次请求时,需要重新加载、解析、编译这些文件。
预加载的思路是:在 FastCGI/PHP-FPM 启动时,一次性将指定的文件加载并常驻内存(OPcache)中,这样,后续每个请求不需要再加载和编译这些文件,直接从内存中取出opcode来执行。
环境要求
- PHP 版本:必须 ( >= 7.4 )
- OPcache 扩展:必须启用
opcache.enable=1 - SAPI:必须配合 PHP-FPM 或 mod_php 使用(CLI 无法直接使用,通常用于生命周期较长的进程管理)。
配置步骤
步骤 1: 定义预加载脚本文件
你需要创建一个 PHP 文件(preload.php),用来声明哪些文件需要被预加载。
// preload.php
<?php
// 定义项目根目录
$dir = '/var/www/html/project';
// 方法 1: 直接加载单个文件
require_once $dir . '/src/MyClass.php';
// 方法 2: 递归扫描目录(更常用)
function preloadClasses($directory) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory)
);
foreach ($iterator as $file) {
if (!$file->isFile() || $file->getExtension() !== 'php') {
continue;
}
// 跳过测试文件或接口文件(如有需要)
if (strpos($file->getPathname(), '/tests/') !== false) {
continue;
}
// 重点: 这里使用 opcache_compile_file 而不是 require_once
opcache_compile_file($file->getPathname());
}
}
// 加载核心框架目录(如 Laravel 的 vendor 或 app 的 classes)
preloadClasses($dir . '/app');
preloadClasses($dir . '/vendor/autoload.php'); // 注意:加载 composer 自动加载器
步骤 2: 配置 php.ini 指向该脚本
在 php.ini 中找到 [opcache] 配置段,添加以下内容:
[opcache] opcache.enable=1 opcache.enable_cli=1 opcache.preload=/path/to/preload.php opcache.preload_user=www-data
opcache.preload: 绝对路径,指向刚才创建的preload.php。opcache.preload_user: 指定运行预加载的用户(通常为www-data或nobody),这是为了防止权限冲突,推荐显式指定。
验证是否生效
配置完成后,重启 PHP-FPM:
sudo systemctl restart php8.1-fpm
验证方式:
- 检查进程日志: 没有报错即为成功。
- 使用 phpinfo(): 查看
opcache.preload是否指向了目标文件。 - 脚本检查(推荐): 执行
php -r 'print_r(opcache_get_status());',查看返回值中的preload_statistics数组,统计预加载的文件数量和托管的内存大小。
实战中的最佳实践(重点注意)
① 不要在 preload.php 中使用 require 加载有副作用的文件
preload.php 在服务启动时执行一次,不要在文件中执行数据库连接、写日志等操作,只做类声明。
② 注意 全局状态 问题
预加载的类,其静态变量、类常量在进程级别是共享的,如果在父进程(master)中修改了它,所有子进程(worker)都会受影响。避免在 preload.php 中实例化对象。
③ 框架集成(以 Symfony 为例)
现代主流框架(如 Symfony 3.4+、EasyAdmin)都已经内置了预加载支持。
- Symfony: 使用
composer dump-env prod后,配置config/preload.php。 - Laravel: 官方建议使用 Octane 或进行简单的目录扫描(如上述代码),但需注意 Laravel 的 Facade 和 ServiceProvider 依赖全局状态,不太适合直接预加载整个 bootstrap 目录。
常见踩坑指南
| 问题现象 | 原因及解决方案 |
|---|---|
| FPM 启动失败 | php.ini 中的 opcache.preload 路径写错,或缺少 opcache.preload_user 导致的权限问题。 |
| 类定义冲突 | 当项目代码里有使用 extends 继承预加载类,但子类文件不在预加载列表时(或相反),会导致类顺序错误。最佳实践: 确保父类一定在子类之后加载,或直接预加载整个 vendor/autoload.php 并让 Composer 处理顺序。 |
| 文件更新不生效 | 预加载的文件在进程生命周期内是黑盒,修改代码后必须重启 PHP-FPM 才能生效,部署过程需配合 php-fpm reload 或 restart。 |
| 内存飙升 | 预加载太多文件会常驻内存,建议只预加载热路径(频繁访问的核心类),不要“一刀切”预加载所有文件。 |
性能测试(要不要用?)
- 收益: 如果你的框架像 Laravel 或 ThinkPHP,有几百个类文件,预加载可以减少磁盘 I/O 和编译时间,请求吞吐量通常能提升 10% - 30%。
- 代价: 内存占用增加,且代码热更新不友好。
- 生产环境强烈推荐,开发环境建议关闭,否则改代码很麻烦。
进阶: 与 JIT(Just-In-Time)结合
如果你使用的是 PHP 8.0+,预加载还能配合 JIT 使用,开启 JIT 后,预加载的代码在运行时会被编译成机器码,性能会再上一个台阶。
opcache.jit=tracing opcache.jit_buffer_size=64M
核心总结:预加载的本质是用内存换时间,在生产环境中,创建一个预加载脚本,扫描并opcache_compile_file你的核心框架类,重启 FPM 即可生效。改完代码必须重启 FPM,这是最容易踩的坑。