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-FPM 是什么?

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

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

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

例如,假设以下配置:

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

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

Fork 进程是有开销的,但这不是世界末日。我们稍后会回到这个话题。

OPCache 是什么?

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

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

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

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

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

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

测试环境

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

  • 测试服务器: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 满负荷运行。如下图所示





启用 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 极限优化配置

PHP 性能优化实战 OPcache + FPM 极限优化配置的更多相关文章

  1. .NET-记一次架构优化实战与方案-前端优化

    目录 .NET-记一次架构优化实战与方案-梳理篇 .NET-记一次架构优化实战与方案-前端优化 .NET-记一次架构优化实战与方案-底层服务优化 前言 上一篇<.NET-记一次架构优化实战与方案 ...

  2. .NET-记一次架构优化实战与方案-底层服务优化

    目录 .NET-记一次架构优化实战与方案-梳理篇 .NET-记一次架构优化实战与方案-前端优化 .NET-记一次架构优化实战与方案-底层服务优化 前言 经过上一篇<.NET-记一次架构优化实战与 ...

  3. .NET-记一次架构优化实战与方案-目录

    前言 本系列是根据我公司的某块业务优化进行改写的,为了避免触发法律的红线,我对部分代码做了截取并打码. 因为优化方案是针对现有业务的问题情况进行的,不做任何太过过分吹牛逼.一切以基于现有的业务,优化处 ...

  4. .NET-记一次架构优化实战与方案-梳理篇

    目录 .NET-记一次架构优化实战与方案-梳理篇 .NET-记一次架构优化实战与方案-前端优化 .NET-记一次架构优化实战与方案-底层服务优化 前言 程序员输出是他敲写的代码,那么输入就是他思考好的 ...

  5. TVP思享 | 四个全新维度,极限优化HTTP性能

    导语 | 当产品的用户量不断翻番时,需求会倒逼着你优化HTTP协议.那么,要想极限优化HTTP性能,应该从哪些维度出发呢?本文将由TVP陶辉老师,为大家分享四个全新维度.「TVP思享」专栏,凝结大咖思 ...

  6. Android UI性能优化实战, 识别View中的性能问题

    出自:[张鸿洋的博客]来源:http://blog.csdn.net/lmj623565791/article/details/45556391 1.概述 2015年初google发布了Android ...

  7. Android UI性能优化实战 识别绘制中的性能问题

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/45556391: 本文出自:[张鸿洋的博客] 1.概述 2015年初google ...

  8. 网站性能优化实战——从12.67s到1.06s的故事

    文章摘自https://juejin.im/post/5b0b7d74518825158e173a0c 作为互联网项目,最重要的便是用户体验.在举国“互联网+”的热潮中,用户至上也已经被大多数企业所接 ...

  9. 小程序Canvas性能优化实战

    以下内容转载自totoro的文章<小程序Canvas性能优化实战!> 作者:totoro 链接:https://blog.totoroxiao.com/canvas-perf-mini/ ...

  10. Linux性能优化实战学习笔记:第三十七讲

    一.上节回顾 上一节,我带你一起学习了网络性能的评估方法.简单回顾一下,Linux 网络基于 TCP/IP协议栈构建,而在协议栈的不同层,我们所关注的网络性能也不尽相同. 在应用层,我们关注的是应用程 ...

随机推荐

  1. 支持向量机(SVM)分类

      支持向量机(Support Vector Machine,SVM)是一种经典的监督学习算法,主要用于分类任务,也可扩展到回归问题(称为支持向量回归,SVR).其核心思想是通过寻找一个最优超平面,最 ...

  2. 如何用三层防护体系打造坚不可摧的 API 安全堡垒?

    扫描二维码 关注或者微信搜一搜:编程智域 前端至全栈交流与成长 发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/ FastAPI 安全与认证综合实 ...

  3. Golang基础笔记九之方法与接口

    本文首发于公众号:Hunter后端 原文链接:Golang基础笔记九之方法与接口 本篇笔记介绍 Golang 里方法和接口,以下是本篇笔记目录: 方法 接口 用结构体实现类的功能 1.方法 首先介绍一 ...

  4. iis支持.apk文件下载的设置方法

    iis支持.apk文件下载的设置方法 - 知乎 (zhihu.com) IIS服务器不能下载.apk文件的解决步骤:1.打开IIS服务管理器,找到服务器,右键-属性,打开IIS服务属性:2.单击MIM ...

  5. 前端页面实现table可拖动改变列宽

    https://blog.csdn.net/Java_Long_Asus/article/details/87794445 此处实现页面的table表格可以自由拖动列宽,拖动时表格内文字不换行,超出部 ...

  6. react 的 createContext 和useContext

    创建一个上下文对象 // my-context.js import { createContext } from 'react'; export default createContext(null) ...

  7. Luogu P9588 队列 题解

    P9588 队列 考虑转化问题,将原问题转化为一个长度为 \(q\) 的序列.序列中 \(x\) 表示一段 \(1\sim x\) 的区间. 操作 \(1\) 每次增加时,输入 \(x\),在数组末尾 ...

  8. python如何将 数组文件 存储为json文件以及对于json文件的读取

    简介 最近项目中要用到PCA计算,PCA从文件中读取数据,然后再写入数据 code #encoding = utf-8 import numpy as np import json from skle ...

  9. GAMES101 作业1 基础版 答案

    简介 GAME101作业1的答案,基础版本,仅供参考,一直认为,没有标准版本和评级版本的作业没有必要做.所以希望分享我的观点. 问题 实现 MVP中的M和P矩阵的构建. get_model_matri ...

  10. LVDS屏幕驱动移植到飞思卡尔i.MX 6Quad Android 4.2.2

    http://blog.csdn.net/xnwyd/article/details/11671123 1 概述 显示屏:LG的LP101WX1-SLN2 显示屏参数:分辨率1280*800,色彩18 ...