引言
「PHP 慢」是多年来的刻板印象,但在 PHP 8 + OPcache + 现代部署下,性能早已不可同日而语。真正决定 PHP 应用快慢的,往往不是语言本身,而是进程模型、字节码缓存、运行时配置这三层工程决策。
本文是一套可落地的性能调优工具箱:先讲清 PHP-FPM 的进程模型与调优参数(pm、max_children、request_terminate_timeout),再看 OPcache 如何把「每次都编译一遍」变成「缓存字节码」,随后讨论 JIT 到底在哪些场景能带来量级提升、哪些场景无关紧要,最后给出从纯 FPM 到 Swoole/ReactPHP/FrankenPHP 异步常驻内存的演进路线与压测对比。
关联阅读:https://plumephp.com/php8-modern-features/ 中的强类型特性是 JIT 收益的前提之一;https://plumephp.com/php-composer-package-development/ 解释了自动加载与 Opcache 的协同。
目录
- 1. 性能全景:瓶颈定位的思路
- 2. PHP-FPM 进程模型与核心调优参数
- 3. OPcache:字节码缓存的配置与预热
- 4. JIT:何时有用、如何配置与验证
- 5. 慢查询治理:数据库与 Redis 热点
- 6. 自动加载与类加载优化
- 7. 从 FPM 到常驻内存:Swoole/ReactPHP/FrankenPHP
- 8. 压测方法论:ab / wrk / 火焰图
- 9. 总结:一份调优检查清单
- 延伸阅读
1. 性能全景:瓶颈定位的思路
1.1 三层瓶颈模型
| 层 | 典型瓶颈 | 手段 |
|---|---|---|
| 语言执行 | 字节码重复编译、解释执行 | OPcache、JIT |
| 进程调度 | FPM 进程数、等待队列 | pm 调优、异步化 |
| 外部 IO | 数据库、Redis、HTTP 调用 | 慢查询、缓存、连接池 |
1.2 先测量再优化
# 1. 压测基线
wrk -t8 -c100 -d30s http://app/api/users
# 2. 看 FPM 是否饱和
php-fpm 状态页 / fpm_php_status 插件
黄金法则:任何优化都先跑一次压测拿基线,改一个变量再对比。不要靠感觉。
2. PHP-FPM 进程模型与核心调优参数
2.1 FPM 的进程模型
master 进程
├── 监听 socket,接受请求
└── worker 进程池
└── 每个 worker 处理一个请求 → 释放 → 处理下一个
FPM 是「按需创建、常驻复用」的 worker 池,每个 worker 处理完一个请求后不退出,继续接下一个——进程本身是常驻的,开销主要在 PHP 请求的编译执行。
2.2 pm 三种模式
| 模式 | 行为 | 适用 |
|---|---|---|
static | 固定 worker 数 | 流量稳定、机器专用 |
dynamic | 随负载增减 | 流量波动 |
ondemand | 空闲回收 | 内存紧张、低流量 |
2.3 关键参数与估算
pm.max_children = 10 # 核心:worker 上限
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500 # 防内存泄漏:达到后重启 worker
request_terminate_timeout = 30 # 单请求超时,防挂死
max_children 估算公式:
可用内存(MB) / 单 worker 平均内存(MB) = max_children
例:8GB 机器,平均 worker 200MB → 约 38(留出系统/DB 余量)
2.4 常见症状对照
| 症状 | 原因 | 修法 |
|---|---|---|
| CPU 高、响应慢 | worker 全忙 | 加机器 / 减慢请求 |
| 502/504 | worker 超时或全挂 | 检查 request_terminate_timeout、死锁 |
| 内存持续上涨 | 泄漏 | 调低 pm.max_requests 强制回收 |
3. OPcache:字节码缓存的配置与预热
3.1 原理:从源码到字节码只编译一次
每次请求若不缓存,PHP 都要把 .php 编译成 opcode 再执行。OPcache 把编译结果缓存在共享内存,直接执行缓存字节码。
请求1: 源码 → 编译(慢) → 执行 → 缓存字节码
请求2+: → 直接执行缓存字节码(快)
3.2 推荐的 OPcache 配置
; php.ini / php-fpm.conf
opcache.enable = 1
opcache.memory_consumption = 128 ; 缓存容量,128-512MB
opcache.interned_strings_buffer = 16 ; 字符串驻留
opcache.max_accelerated_files = 20000 ; 加速文件数上限
opcache.validate_timestamps = 1 ; 开发用 1,生产可关 0
opcache.revalidate_freq = 2 ; 秒级重新校验
opcache.enable_cli = 1 ; CLI 也启用(脚本、队列)
opcache.jit = tracing ; 开启 JIT(见第 4 节)
3.3 生产环境:关闭校验换取极致
opcache.validate_timestamps = 0 ; 不再检查文件 mtime
代价:改代码后必须清缓存(重启 FPM 或 opcache_reset())。适合有部署流程(发版重启)的场景。
3.4 预热与监控
# 预热:让缓存从启动就命中
php -r 'opcache_compile_file(...);' # 或使用 opcache 预热脚本
# 监控命中率
opcache_get_status()['opcache_statistics']['opcache_hit_rate']
命中率应 >98%;新版本上线后会短暂下降,属正常。
4. JIT:何时有用、如何配置与验证
4.1 JIT 做什么
JIT(Just-In-Time)把「热路径」的字节码进一步编译为机器码,减少解释执行开销。PHP 8.0 引入,需 opcache.jit = tracing。
4.2 适用场景矩阵
| 场景 | JIT 收益 |
|---|---|
| 大量 CPU 计算、循环、数值运算 | 显著(1.5-3x) |
| 纯 IO 型 Web 请求(DB/Redis 为主) | 有限(瓶颈在 IO) |
| 模板渲染、JSON 编解码 | 中等 |
| 事件循环/常驻任务(Swoole worker 内) | 高(单进程长跑) |
4.3 配置示例
opcache.jit = tracing
opcache.jit_buffer_size = 64M ; JIT 机器码缓冲
opcache.jit_debug = 0
4.4 验证收益
# 写一个 CPU 密集型基准脚本
php -d opcache.jit=disable bench.php
php -d opcache.jit=tracing bench.php
# 对比耗时,判断 JIT 在你的负载里值不值
JIT 是「锦上添花」而非「救火工具」:先确认 CPU 是瓶颈,再投入。
5. 慢查询治理:数据库与 Redis 热点
5.1 找出慢查询
-- 打开慢查询日志
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 0.5;
-- MySQL 性能模式
SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
5.2 常见慢查询模式
| 模式 | 表现 | 修法 |
|---|---|---|
| 全表扫描 | EXPLAIN type=ALL | 加索引 |
| 深分页 | LIMIT 100000,20 | 用游标/keyset 分页 |
| N+1 查询 | 循环里逐条查 | 批量加载、JOIN |
| 大字段 | 选 SELECT * 大 text | 只选所需列 |
5.3 PHP 侧缓存热点
use Illuminate\Support\Facades\Cache;
// 多级缓存:本地 + Redis
$key = "product:{$id}";
$product = Cache::remember($key, 600, fn () => Product::find($id));
5.4 连接池与超时
// FPM 下连接随请求释放;异步常驻下务必用连接池
// Laravel 配置 .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_POOL_MAX_CONNECTIONS=20 // 异步扩展的池配置
6. 自动加载与类加载优化
6.1 Composer 的 classmap 权威优化
# 生产:把类名编译成 classmap,省掉 PSR-4 逐级查找
composer install --no-dev --classmap-authoritative
# 或
composer dump-autoload --classmap-authoritative
6.2 预热关键类
# 常驻进程 / 队列 worker 启动时预热框架核心类
php -r 'require "vendor/autoload.php"; class_exists("App\\Kernel");'
6.3 自动加载对性能的影响
每个未命中 classmap 的 use 都要查文件系统。--classmap-authoritative 后全部查表,IO 从文件系统降为数组查找。
7. 从 FPM 到常驻内存:Swoole/ReactPHP/FrankenPHP
7.1 为什么需要常驻内存
FPM 每次请求重建应用上下文(容器、配置、连接)。常驻进程把「加载一次、复用多次」,消除重复开销:
| 维度 | PHP-FPM | Swoole/FrankenPHP |
|---|---|---|
| 上下文重建 | 每次请求 | 启动时一次 |
| 连接复用 | 需重建/池 | 长连接池 |
| 高并发 | 靠进程数 | 单进程事件循环 |
| 内存 | 每 worker 一份 | 进程内共享 |
7.2 三条路线对比
| 方案 | 原理 | 适配 |
|---|---|---|
| Swoole | PHP 原生协程 + 事件循环,Http\Server 直接跑 HTTP | 高性能服务、WS、TCP |
| ReactPHP | 纯 PHP 事件循环库,异步 IO | 中间层、CLI 任务 |
| FrankenPHP | 把 PHP 打进 Caddy,Workers 模式常驻 | 现代 Laravel/Symfony 部署 |
7.3 Swoole 最小 HTTP 服务
// server.php
use Swoole\Http\Server;
$server = new Server('0.0.0.0', 9501);
$server->on('request', function ($req, $res) {
$res->header('Content-Type', 'application/json');
$res->end(json_encode(['hello' => 'swoole']));
});
$server->start();
php server.php # 常驻,不退出
7.4 FrankenPHP 的 Laravel 部署
# docker-compose 片段
services:
app:
image: dunglas/frankenphp
volumes:
- .:/app
command: ["frankenphp", "run", "--config", "/app/Caddyfile"]
environment:
APP_ENV: production
WORKERS: 4
7.5 何时不该上常驻内存
- 流量不大、部署简单优先 FPM。
- 团队不熟悉协程模型,调试成本高。
- 依赖阻塞式 C 扩展的代码(不可协程化)。
推荐路径:先 FPM 调优 + OPcache + JIT 拿大头,压测不足再迁移常驻。
8. 压测方法论:ab / wrk / 火焰图
8.1 三件套
# ApacheBench:简单快速
ab -n 10000 -c 100 http://app/api/users
# wrk:更高并发、支持 Lua 脚本
wrk -t8 -c200 -d30s --latency http://app/api/users
# 火焰图:定位 CPU 热点
php -d xdebug.mode=profile run.php # 输出 profile,转火焰图
8.2 压测的纪律
- 同机/同网络环境对比,避免外部波动。
- 每次只改一个变量。
- 记录 P50/P95/P99 延迟,不只 QPS。
9. 总结:一份调优检查清单
9.1 生产上线前 checklist
| 项 | 动作 |
|---|---|
| 字节码 | OPcache 开启,validate_timestamps=0(配合发版重启) |
| 类加载 | --classmap-authoritative |
| 进程 | FPM max_children 按内存估算,max_requests 防泄漏 |
| 慢查询 | 慢日志开启、索引检查、缓存热点 |
| JIT | CPU 瓶颈场景开启,否则保守 |
| 异步 | 压测不足再上 Swoole/FrankenPHP |
9.2 一句话心法
先把「不编译、不重建、不重复查库」做到极致,再考虑换语言或上协程。 大多数 PHP 应用的性能问题,根源是配置不当与查询浪费,而不是 PHP 本身。
延伸阅读
- https://plumephp.com/php8-modern-features/ — 强类型特性让 JIT 更有效
- https://plumephp.com/php-composer-package-development/ — classmap 权威自动加载的出处
- OPcache 官方文档 与 Swoole 文档
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。