VPS上PHP高并发翻车实录:从100并发打满到5000并发稳如老狗的调优手册
1. 默认PHP-FPM配置就是定时炸弹
刚入坑时,我在一台1核1G的VPS上直接用了php-fpm默认配置,上线业务刚200并发就炸了——CPU打满、内存溢出、502满天飞。新手最常犯的错误:不做压力测试直接就当生产用。
罪魁祸首清单
pm = dynamic+ 默认pm.max_children = 5(实际上是5个动态子进程,但动态模式会在突发流量下疯狂fork,直接拉爆内存)pm.max_requests = 0(永不回收进程,内存泄漏累积)request_terminate_timeout = 0(死循环不超时,进程挂死)
实战调优方案(1核1G VPS示例)
# /etc/php/8.1/fpm/pool.d/www.conf
pm = static
pm.max_children = 4
pm.max_requests = 500
request_terminate_timeout = 30s
pm.process_idle_timeout = 10s
为什么用static?对于VPS这种固定资源,static模式避免动态fork的抖动。4个进程刚好占满1G内存(每个PHP进程约200MB,含业务代码库)。加上OPcache后单进程内存降到80MB,可以升到8个进程。
踩坑提醒:pm.max_children = 总内存 / 每个进程平均内存,用ps aux | grep php-fpm观察实际RSS值,别拍脑袋设。
2. OPcache不开启等于自残
新手常以为“小VPS没必要开OPcache”,真相是不开OPcache,PHP每次请求都要解析编译所有文件(包括vendor),1核CPU瞬间打爆。开启后CPU降低70%以上。
必配参数(在php.ini中)
opcache.enable=1
opcache.memory_consumption=64
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=0
opcache.fast_shutdown=1
revalidate_freq=0 是关键——生产环境不检查文件修改时间,避免每次请求都stat。部署代码后手动sudo systemctl reload php8.1-fpm或调接口清空缓存即可。
验证效果
php -r "print_r(opcache_get_status(true));" | grep hits
若hits远大于misses,说明工作正常。misses暴增则需检查opcache.max_wasted_percentage。
3. 数据库连接不复用,秒变雪崩
新手写PHP直连MySQL,每次请求new PDO,高并发下连接数迅速打满。VPS的MySQL默认max_connections=151,超过就报Too many connections。
快速止损方案
- 用持久连接:
PDO::ATTR_PERSISTENT => true,但注意要手动检查连接有效性(mysql_ping),否则会出现“2006 MySQL server has gone away”。 - 更推荐:配合Redis做查询缓存,扛住读多写少的场景。
class Db {
private static $pdo = null;
public static function getInstance() {
if (self::$pdo === null) {
self::$pdo = new PDO('mysql:host=127.0.0.1;dbname=xxx', 'user', 'pass', [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
}
// 检查连接是否有效
try {
self::$pdo->query('SELECT 1');
} catch (PDOException $e) {
self::$pdo = new PDO(...); // 重连
}
return self::$pdo;
}
}
如果是轻云互联的VPS(比如香港BGP节点),自带MySQL 8.0优化版,建议启用performance_schema监控慢查询,及时加索引。
4. 慢查询堆死PHP-FPM池
高并发下,一个慢SQL耗时5秒,PHP-FPM进程就被占死,池子迅速耗尽。新手常忽略慢查询日志,直到502才查。
必开慢查询日志
# /etc/mysql/mysql.conf.d/mysqld.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1
之后用pt-query-digest分析瓶颈:
pt-query-digest /var/log/mysql/slow.log | head -20
常见优化:无索引的LIKE %xxx%改成全文索引或搜索引擎;ORDER BY RAND()改用JOIN取随机结果。
5. 进程数不对:多核VPS的误区
很多教程说“CPU核心数 * 2”设置pm.max_children,但实测在VPS上会翻车。例如2核4G的VPS,设pm.max_children = 4(2*2),结果CPU不到50%内存却溢出了。
正确公式(面向VPS)
# 先测单进程内存
ps aux | grep php-fpm | awk '{print $6}' | head -1 # RSS in KB
# 假设平均180MB,4GB内存留给PHP 3GB(预留1GB给OS/MySQL)
max_children = 3GB * 1024 / 180 ≈ 17
考虑到内存碎片和突发,取12比较安全。同时配合pm.max_requests = 500防止内存泄漏累积。
6. 排错三板斧
当高并发系统突然变慢,新手别急着重启。按顺序检查:
php-fpm slow log:设置request_slowlog_timeout = 5s,快速定位哪个API慢。netstat -anp | grep :80看连接队列是否溢出(Recv-Q列)。ab -n 1000 -c 100 http://your-site/本地压测,观察Failed requests和Requests per second。
真实案例
有一次线上502,查slow log发现一个file_get_contents超时等待上游API,直接阻塞了所有PHP进程。最终加上了stream_context_create设置超时参数:
$opts = [
'http' => ['timeout' => 3, 'method' => 'GET'],
];
$context = stream_context_create($opts);
$result = file_get_contents('xxx', false, $context);
别怪VPS不行,很多问题都是代码里埋的雷。
7. 终极复盘:从100并发到5000并发的关键几步
- 第一层:OPcache + 静态FPM进程 (杀CPU/内存)
- 第二层:MySQL慢查询 + 连接池 (杀数据库)
- 第三层:Redis缓存 + 异步队列 (降数据库压力)
- 第四层:Nginx开启FastCGI缓存 (静态化扛流量)
如果你用的是轻云互联的VPS,他们默认预装了OPcache和MySQL参数调优,省去第一步的适配时间。但记住:没有任何一键配置能替代压测和日志分析。