PHP 性能优化实战 OPcache + FPM 极限优化配置

AI摘要
通过启用OPCache和优化php-fpm配置,PHP应用性能显著提升:服务器从5台减至2台,CPU负载从30%降至2%,响应时间从150ms缩短至23ms。核心优化包括启用OPCache缓存编译代码,调整php-fpm进程池参数以匹配服务器资源。建议先启用OPCache再调整FPM,通过压力测试监控资源使用,找到最佳配置。

PHP 性能优化实战 OPcache + FPM 极限优化配置

先说下背景:这是个运行在 5 台云服务器(8 核 CPU,32GB 内存)上的老 PHP 应用。这些机器配置很强,对这个应用来说完全是过度配置了。

这事一直没有优先级,所以我从来没处理过——直到现在。

监控显示服务器使用了约 15% 的 CPU,流量增加时最高到 30%,内存使用率也很低。我知道原因:php-fpm 从来没有为这些机器正确配置过,而且 OPCache 是禁用的。

优化前后对比

优化前

  • 集群:5 台云服务器
  • 总 CPU:40 核
  • 总内存:320GB
  • 白天平均 CPU 负载:15-20%,峰值 30%
  • 平均内存使用:约 2GB
  • 平均 PHP 执行时间:150ms
  • OPCache:关闭
  • php-fpm 配置:
pm.max_children = 100
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 8

优化后

  • 集群:2 台云服务器
  • 总 CPU:16 核
  • 总内存:64GB
  • 白天平均 CPU 负载:约 2%
  • 平均内存使用:约 7GB
  • 平均 PHP 执行时间:23ms
  • OPCache:开启
  • php-fpm 配置:
pm.max_children = 300
pm.start_servers = 100
pm.min_spare_servers = 60
pm.max_spare_servers = 150

PHP 性能优化实战

PHP-FPM 是什么?

PHP-FPM 是最广泛使用的 PHP 应用服务方式,本质上是一个进程管理器。大多数请求遵循这个流程:

请求 -> NGINX -> php-fpm -> (选择或创建 PHP 进程)-> 执行代码 -> 响应

NGINX 作为反向代理通过 socket 与 fpm 通信——FPM 负责从进程池中选择一个进程,或者在没有空闲进程时创建新进程(如果低于定义的 max_children 值)。

例如,假设以下配置:

  • 最大进程数:10
  • 最大池大小:8

如果收到 8 个并发请求,php-fpm 会简单地从池中选择空闲进程。如果收到 10 个请求,它会选择 8 个空闲进程并 fork 2 个额外的进程。

Fork 进程是有开销的,但这不是世界末日。我们稍后会回到这个话题。
PHP 性能优化实战

OPCache 是什么?

简单来说,OPCache 是一个操作码缓存。

那么什么是操作码?操作码是低级机器指令,它告诉处理器要执行什么操作。我们不需要深入这个兔子洞。当 PHP 脚本执行时会发生以下过程:

  1. 解释器加载脚本
  2. 脚本解析成语法树
  3. 语法树转成 Zend 引擎能懂的操作码
  4. Zend 引擎执行这些操作码
  5. 输出结果

当启用 OPCache 时,步骤 2 和 3 被跳过:

  1. 解释器加载脚本
  2. Zend 引擎执行缓存好的操作码
  3. 输出结果

显然,如果缓存未命中,所有步骤都必须执行。可以想象,缓存这些昂贵的操作可以提供巨大的性能改进,需要更少的 CPU 周期并减少整体内存消耗。
PHP 性能优化实战

测试环境

我在云厂商上设置了几台机器进行测试:

  • 测试服务器:4 核 CPU,8GB 内存,运行一个简单的 Laravel 应用,进行数据库读写操作
  • 压力测试服务器:用于发送 HTTP 请求的简单服务器
  • 数据库:2 核 CPU,4GB 内存,MySQL

Laravel 应用运行的代码如下:

<?php

namespace App\Http\Controllers;

use App\Models\Visit;

class FooController
{
  public function __invoke()
  {
      Visit::query()->create();

      return response()->json([
          'visits' => Visit::query()->count(),
      ]);
  }
}

服务器是用自动化部署工具部署的。

测试结果

让我们从结果开始。

初始基准测试

我运行了一个简单的测试(ab -n 1000 -c 10),得到的结果是:33 reqs/s,服务器 CPU 满负荷运行。如下图所示
PHP 性能优化实战
PHP 性能优化实战
启用 OPCache 后


119 reqs/s,而且没有拖垮服务器。好多了。

调整 php-fpm 后

直接飙到了 310 reqs/s,憾的是,没办法将 CPU 调整到接近 100% 使用率。

启用 OPCache

在调整 FPM 之前,我建议启用 OPCache——否则你需要重新调整,因为 CPU 负载和内存使用会发生变化。OPCache 作为扩展分发,请确保已安装。首先,运行 php -v 查看是否已安装:

Copyright (c) The PHP Group
Zend Engine v4.2.8, Copyright (c) Zend Technologies
    with Xdebug v3.2.2, Copyright (c) 2002-2023, by Derick Rethans
    with Zend OPcache v8.2.8, Copyright (c), by Zend Technologies

看开没开,跑 php -i | grep opcacheopcache.enable

/etc/php/8.2/conf.d/10-opcache.ini,
opcache.blacklist_filename => no value => no value
opcache.consistency_checks => 0 => 0
opcache.dups_fix => Off => Off
opcache.enable => Off => Off

要开 opcache,先跑 php --ini 找到 Loaded Configuration File,定位你的 php.ini:

cd /etc/php/8.2
vim php.ini

找到 [opcache] 那块,把 opcache.enable 改成 1。

还有个重要配置 opcache.max_accelerated_files,决定缓存多少文件,vendor 目录也算。

开了 OPCache 后记得重启 php-fpm:service php-fpm reload

顺便说下:每次部署新代码都该重启 php-fpm。不想重启的话可以开 opcache.validate_timestamps,让 OPCache 自己跟踪文件变化,不过会影响性能。

光是 OPCache 就能让性能飞起来。试试就知道了。

调 PHP-FPM

理想情况是啥样

PHP-FPM 有这些配置:

  • 进程管理类型(static、dynamic、ondemand)
  • 最大工作进程(pm.max_children):php-fpm 最多能 fork 多少进程
  • 启动进程数(pm.start_servers):启动时先 fork 多少进程
  • 最小池大小(pm.min_spare_servers):池子里最少得有多少进程
  • 最大池大小(pm.max_spare_servers):池子里最多能有多少进程

理想情况下,你的配置应该:

  • 把服务器内存用起来
  • 把 CPU 也用起来

FPM 默认配置通常是这样:

pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.max_spare_servers = 3
pm.min_spare_servers = 1

这配置明显不行。意思是你最多只能处理 10 个并发请求(多了就得等),而且超过 3 个请求就得新建 7 个进程。

设置 FPM 的直观方法(大多数指南会这么告诉你)是计算每个进程消耗多少内存,加点缓冲,然后用系统可用内存除以这个值。我们测试中每个进程消耗约 16MB 内存。

可以用这个命令粗略估算:

ps --no-headers -eo rss,comm | grep php | awk '{sum+=$1; count++} END {if (count > 0) print "Average Memory Usage (KB):", sum/count; else print "No PHP processes found."}'

我们的情况下,大概是:3500 / 16 = 218,意味着可以跑最多 218 个进程。简化点,就设 200 吧。

不过,虽然这么算有道理,但进程数最多不一定性能最好:这取决于你的应用是 I/O 密集型还是 CPU 密集型,各个端点的特性等等。比如,如果 CPU 是瓶颈,fork 更多进程没用,因为调度器没法在它们之间正常切换。

有很多瓶颈光增加进程数解决不了:

  • 达到服务器最大带宽
  • 达到数据库最大连接数
  • 达到最大 TCP 连接数
  • 磁盘 I/O
  • Web 服务器配置

我的建议是用默认设置做基准测试,监控 CPU 和内存使用。

在这篇文章的测试中,我发现最佳配置是 100 个最大进程,虽然内存够跑超过 200 个进程。我用的配置:

pm = dynamic
pm.max_children = 100
pm.start_servers = 70
pm.max_spare_servers = 80
pm.min_spare_servers = 60

这让 fpm 启动时就有合理数量的进程,如果流量增加,可以 fork 新进程处理。实际使用中,你肯定要靠监控来衡量服务器负载,看是否需要更多进程。这只是个起点。

我的建议还是先玩玩这个配置,然后盯着监控进一步调整。某个时候,增加进程数可能会有反效果——记住 fork 进程有开销,进程间切换也有开销。应用还受延迟、I/O 操作等影响。基础设施方面数据是你的朋友,没数据你就是瞎子。随着流量模式/使用变化调整设置也是关键。

总结

所以,你要做的是:

  1. 启用 OPCache。这是最重要的。
  2. 跑压力测试并监控服务器。用云监控和性能监控工具监控服务器的不同资源,发现瓶颈。
  3. 调整 php-fpm 池设置,尽量在负载下最大化利用服务器;记住这不一定意味着要榨干机器上所有可用 RAM。
  4. 针对应用中不同类型的工作负载测试:I/O 密集型、CPU 密集型等。
  5. 用压测工具好好测试应用
  6. 强烈建议搭建跟生产环境一模一样的测试环境:同类型服务器、同样的数据库、同样的数据(去掉敏感信息),在那里跑测试。
  7. 玩转 FPM 设置直到找到最佳配置。没有万能配方,每个应用都不一样。

PHP 性能优化实战 OPcache + FPM 极限优化配置

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
开发 @ 家里蹲开发公司
文章
99
粉丝
78
喜欢
404
收藏
287
排名:18
访问:28.1 万
私信
所有博文
社区赞助商