PHP 8.2 里用 opcache 预热,我把首页 TTFB 从 380ms 压到 90ms

环境是这样:PHP 8.2.13,php-fpm 8 个 worker,Nginx 反代,代码量不大,一个手作工作室的展示站加下单,路由和模板加起来 60 多个文件。现象是每天第一次访问首页 TTFB 380ms 左右,之后稳定在 110ms。用户不多,但早上第一个访客的体验我知道自己受不了。

我先确认不是数据库。首页只有一次商品列表查询,慢日志里没它。真正的差别在 opcache:冷启动时 worker 要把几十个文件编译一遍,第一个请求承担了全部。做法是让 fpm 启动时就把脚本预编译进共享内存,而不是等请求触发。

在 php.ini 里打开:

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.file_cache=/var/cache/php/opcache
opcache.file_cache_only=0

然后写一个预热的脚本,在部署后跑一次,把入口文件链路上的类全走一遍:

<?php
$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator(__DIR__ . '/src')
);
foreach ($files as $f) {
    if ($f->getExtension() === 'php') {
        opcache_compile_file($f->getPathname());
    }
}

注意 opcache_compile_file 只编译不执行,父类没加载时会报「无法编译」的 warning,所以顺序上我先把 vendor 里的 autoload 跑一遍,再编译 src。

为什么有效:opcache 是共享内存,fpm master 起来后 worker 共享同一份字节码,预热等于把「第一个请求的编译成本」摊到了部署阶段。代价是 validate_timestamps=0 之后改代码必须 reload fpm,我本地开发是关掉 opcache 的,线上靠部署钩子跑 systemctl reload php8.2-fpm。

哪里会失效:file_cache 目录权限不对时预热静默失败,我第一次就踩了,opcache_get_status 里 cached_scripts 一直是 0;另外用 opcache.preload 那条路更彻底,但 preload 脚本里不能有副作用,我试过在里面 require 业务配置,直接把 fpm 起崩了,后来退回现在的方案。还有一点,页面里如果有大量运行时才 include 的文件,预热覆盖不到,得单独列清单。

你们线上是走 preload 还是部署后跑一遍 compile_file?preload 里放业务代码这件事,有没有人找到过比较稳的边界?

0
赞同度 100% · 0 条评论 · 3 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。