PHP是最常用的Web开发语言之一很多网站都是用PHP开发的但是PHP的性能一直被很多人诟病觉得PHP慢性能差不适合高并发其实只要优化得当PHP也能跑得很快也能支撑高并发。

我做过很多PHP项目的性能优化从慢接口到高并发踩了很多坑也总结了很多经验今天就来分享一下PHP性能优化的实战经验从慢接口到高并发全面分享希望能帮大家把PHP项目优化得更快更稳。

一、性能优化的思路

和MySQL性能优化一样PHP性能优化也要有正确的思路不能盲目优化。

我一般按照以下步骤来做:

  1. 发现问题: 首先要发现性能问题比如哪个接口慢哪个页面加载慢系统响应时间长CPU内存使用率高等等通过监控告警用户反馈等方式发现问题。
  1. 定位瓶颈: 发现问题之后要定位瓶颈在哪里是PHP代码执行慢?还是数据库查询慢?还是缓存没命中?还是外部接口调用慢?还是网络IO慢?要通过性能分析工具比如XdebugxhprofBlackfire等等定位具体的瓶颈。
  1. 分析原因: 定位瓶颈之后要分析为什么会有这个瓶颈比如数据库慢是因为没索引?还是SQL写得不好?还是数据量太大?代码慢是因为循环里查数据库?还是重复计算?还是算法效率低?要找到根本原因。
  1. 解决问题: 找到原因之后就针对性地解决问题比如没索引加索引SQL不好优化SQL循环里查数据库改成批量查询或者缓存等等。
  1. 验证效果: 解决问题之后要验证优化的效果接口的响应时间是不是缩短了系统的吞吐量是不是提升了CPU内存使用率是不是降下来了等等要确认优化确实有效。
  1. 预防问题: 最后要总结经验建立规范预防类似的问题再次发生比如代码规范SQL规范上线前性能检查慢接口监控等等从流程上避免性能问题。

这个思路很重要不要上来就盲目优化要先定位瓶颈再针对性地优化不然可能花了很多时间优化了不重要的地方效果却不明显。

二、PHP层面的优化

先说说PHP本身层面的优化。

1. 开启OPcache

OPcache是PHP的字节码缓存开启后PHP脚本第一次执行的时候会把PHP代码编译成字节码缓存起来后续执行就不用重新编译了直接从缓存读取执行能大大提升PHP的性能一般能提升50%-100%甚至更多。

OPcache在PHP 5.5之后是内置的不需要额外安装只需要在php.ini里开启就行:

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.validate_timestamps=1
opcache.save_comments=1
opcache.fast_shutdown=1

解释一下主要参数:

  • opcache.enable=1: 开启OPcache。
  • opcache.memory_consumption=128: OPcache使用的内存大小单位MB一般128MB就够了项目大的话可以调大比如256MB。
  • opcache.maxacceleratedfiles=10000: 最多缓存多少个PHP文件一般10000就够了项目大的话可以调大。
  • opcache.revalidatefreq=60: 检查文件是否修改的频率单位秒60秒检查一次生产环境可以调大比如300秒减少检查的开销但是代码更新后要等一会儿才生效或者手动重启PHP或者调用opcachereset()。
  • opcache.validate_timestamps=1: 是否检查文件修改时间如果关闭OPcache就不会检查文件是否修改代码更新后不会自动生效需要手动重启PHP或者重置OPcache生产环境可以关闭性能更好但是部署要注意更新代码后重置OPcache。
  • opcache.fast_shutdown=1: 开启快速关闭能提升性能。

开启OPcache是PHP性能优化最基本也是最有效的手段一定要开启很多性能问题开启OPcache就能解决一大半。

2. 使用最新的PHP版本

PHP的每个新版本都会有性能优化比如PHP 7.0比PHP 5.6性能提升了一倍PHP 7.17.27.37.4也都有性能优化所以尽量使用最新的稳定版PHP能获得更好的性能。

当然升级PHP版本要注意兼容性有些旧的扩展或者代码可能在新版本不兼容要先测试没问题再升级。

3. 关闭不需要的扩展

PHP有很多扩展有些扩展是项目不需要的可以关闭减少内存占用和启动时间提升性能。

比如如果你不用mysql只用mysqli或者pdo_mysql就可以关闭mysql扩展不用gd就关闭gd扩展等等。

可以在php.ini里注释掉不需要的扩展或者在conf.d里删除对应的配置文件。

4. 合理配置php.ini

php.ini里有很多参数合理配置也能提升性能:

  • memory_limit: 每个PHP脚本最大内存使用量不要设置太小不然脚本会内存不足报错也不要设置太大不然会浪费内存一般128MB就够了有些特殊的脚本比如导出图片处理可能需要更大比如256MB512MB。
  • maxexecutiontime: 每个PHP脚本最大执行时间一般30秒就够了不要设置太大不然慢脚本会一直占用资源影响其他请求有些特殊的脚本比如导入导出可能需要更大或者设置为0不限制但是要注意不要让慢脚本影响系统。
  • uploadmaxfilesizepostmaxsize: 上传文件的最大大小根据需求设置不要太大不然会占用很多资源。
  • displayerrors: 生产环境一定要关闭displayerrors不要把错误信息显示在页面上既不安全又影响性能错误要记录到日志里log_errors=On。
  • errorreporting: 生产环境不要设置为EALL不然会记录很多noticewarning影响性能一般设置为EALL & ~ENOTICE & ~EWARNING或者EERROR只记录致命错误当然开发环境要开启所有错误方便调试。

5. 代码层面的优化

PHP代码层面也有很多优化点:

  • 避免在循环里查数据库: 这是最常见的性能问题很多人写代码喜欢在循环里查数据库比如遍历一个用户列表每个用户都查一次用户的详情或者订单这样如果有100个用户就查100次数据库性能很差要改成批量查询先把所有用户ID拿出来一次查出所有用户的详情或者订单然后在循环里匹配这样只查一次数据库性能好很多。
  • 避免重复计算和重复查询: 同一个计算或者查询不要重复做比如一个函数里多次调用同一个查询要把结果缓存起来用静态变量或者成员变量保存第一次查询的结果后续直接用不用再查。
  • 使用合适的数据结构: PHP的数组很强大但是不同的使用方式性能差别很大比如查找用isset()比inarray()快很多因为isset()是O(1)inarray()是O(n)所以需要频繁查找的数据可以用键值对的数组把要查找的值作为键这样用isset()查找很快。
  • 避免使用正则表达式: 正则表达式性能比较差能不用就不用比如简单的字符串替换用strreplace()不用pregreplace()简单的字符串匹配用strpos()strstr()不用preg_match()除非必须用正则才用。
  • 引用传递大变量: 大的变量比如大数组字符串传递给函数的时候用引用传递&避免复制节省内存和时间但是要注意引用会修改原变量要谨慎使用。
  • 提前退出循环: 循环里满足条件了就break提前退出不要继续遍历剩下的元素节省时间。
  • 使用内置函数: PHP的内置函数都是用C写的性能很高能用内置函数解决的就不要自己写PHP代码实现比如数组去重用array_unique()不用自己遍历数组去重字符串截取用substr()不用自己写循环截取。
  • 关闭数据库连接等资源: 不用的资源比如数据库连接文件句柄等等要及时关闭释放资源不要等脚本结束才释放当然PHP脚本结束会自动释放但是及时释放能提高资源利用率。

三、数据库层面的优化

PHP项目大部分性能瓶颈都在数据库所以数据库优化非常重要这部分和我之前写的MySQL性能优化类似这里简单说说PHP相关的。

1. 合理使用索引

索引是数据库优化最有效的手段经常作为WHERE条件ORDER BYGROUP BYJOIN的字段要建索引但是索引不是越多越好要合理建索引具体可以看我之前的MySQL性能优化的文章。

2. 优化SQL语句

SQL语句要写得高效避免SELECT *只查需要的字段避免在索引列上用函数计算避免子查询尽量用JOIN避免深分页等等具体也可以看MySQL性能优化的文章。

3. 使用PDO或者mysqli预处理

PHP操作数据库推荐用PDO或者mysqli并且使用预处理语句(Prepared Statement)预处理能防止SQL注入也能提升性能因为SQL语句只编译一次多次执行不用重复编译。

比如PDO的预处理:

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();

这样既安全又高效。

4. 批量查询和批量插入

前面代码优化提到了避免在循环里查数据库要批量查询同样插入更新也要批量不要循环里一条条插入要用批量INSERT一次插入多条性能好很多。

比如批量插入:

$sql = "INSERT INTO users (name, age) VALUES ";
$values = [];
foreach ($data as $item) {
    $values[] = "('{$item['name']}', {$item['age']})";
}
$sql .= implode(',', $values);
$pdo->exec($sql);

这样一次插入所有数据比循环里一条条插入快很多。

当然批量插入也不要一次插入太多比如一次插入几万条可能会锁表或者主从延迟一般一次几百条几千条比较合适。

5. 合理使用数据库连接池

PHP默认是短连接每次请求都重新建立数据库连接请求结束关闭这样频繁建立关闭连接会有开销高并发的时候性能会受影响。

可以使用长连接或者连接池复用数据库连接减少连接建立的开销比如PDO支持长连接设置PDO::ATTR_PERSISTENT => true或者使用Swoole等扩展实现连接池。

当然长连接也有问题比如连接可能会被数据库断开或者占用连接数要合理使用注意连接的管理。

四、缓存优化

缓存是提升性能最有效的手段之一大部分读多写少的场景都可以用缓存挡住大部分读请求不用查数据库大大提升性能。

1. 页面缓存

对于不经常变化的页面比如文章详情页商品详情页等等可以做页面缓存把整个页面的HTML缓存起来用户访问的时候直接返回缓存的HTML不用执行PHP不用查数据库性能非常高。

页面缓存可以用文件缓存把HTML存到文件里也可以用RedisMemcached缓存还可以用Nginx的proxy_cache或者FastCGI缓存在Web服务器层面缓存性能更高。

2. 数据缓存

对于一些热点数据比如用户信息商品信息分类列表等等可以做数据缓存把数据缓存到Redis或者Memcached查询的时候先查缓存缓存有直接返回缓存没有再查数据库然后写入缓存这样大部分请求都能命中缓存不用查数据库。

数据缓存要注意缓存的更新和失效数据更新的时候要同步更新缓存或者删除缓存避免缓存和数据库不一致也要设置合理的过期时间避免缓存永久不更新。

3. 局部缓存

在PHP代码里也可以做局部缓存比如一个请求里多次查询同一个数据可以用静态变量或者成员变量缓存第一次查询的结果后续直接用不用再查数据库或者缓存。

比如:

function getUser($id) {
    static $cache = [];
    if (isset($cache[$id])) {
        return $cache[$id];
    }
    $user = db_query("SELECT * FROM users WHERE id = ?", [$id]);
    $cache[$id] = $user;
    return $user;
}

这样同一个请求里多次调用getUser(1)只会查一次数据库后续直接用缓存的结果。

4. 浏览器缓存

对于静态资源比如图片CSSJS等等可以设置浏览器缓存让浏览器缓存这些资源下次访问直接从浏览器缓存读取不用请求服务器减轻服务器压力也提升用户体验这部分前面Nginx优化提到过。

五、Web服务器层面的优化

Web服务器比如NginxApache的优化也很重要能提升整体的性能。

1. 使用Nginx

Nginx性能比Apache高资源消耗小高并发表现好所以推荐使用Nginx作为Web服务器或者反向代理。

2. 开启Gzip压缩

Gzip压缩能减少传输的数据量提升页面加载速度节省带宽一定要开启前面Nginx优化提到过。

3. 静态文件缓存

静态文件设置浏览器缓存前面提到过。

4. 合理配置PHP-FPM

如果用PHP-FPM要合理配置PHP-FPM的参数比如pm.maxchildrenpm.startserverspm.minspareserverspm.maxspareservers等等根据服务器的内存CPU和并发量合理设置不要设置太小不然并发上不去也不要设置太大不然内存不够会OOM。

一般maxchildren可以按每个PHP-FPM进程占用20-50MB内存来算比如服务器2GB内存给PHP-FPM1GB那maxchildren可以设置为20-50根据实际情况调整。

六、架构层面的优化

当项目大了并发高了单台服务器可能扛不住这时候就要架构层面的优化。

1. 负载均衡

多台Web服务器前面用Nginx或者LVS做负载均衡把请求分发到不同的Web服务器提升整体的并发能力和可用性。

2. 读写分离

数据库做主从复制读写分离主库负责写从库负责读读请求分发到从库减轻主库的压力提升读的并发能力。

3. 分库分表

当单表数据量太大比如几千万几亿条查询性能会下降这时候要考虑分库分表把大表拆成多个小表提升查询性能具体可以看MySQL性能优化的文章。

4. 分布式缓存

当缓存数据量大单台Redis扛不住可以用Redis集群分布式缓存提升缓存的容量和并发能力。

5. CDN

静态资源图片视频等等可以用CDN加速让用户从最近的CDN节点获取资源提升访问速度也减轻源站的压力。

七、性能监控和分析

最后性能优化不是一蹴而就的要持续监控和分析发现问题及时优化。

1. 慢接口监控

要监控每个接口的响应时间把响应时间超过阈值的接口记录下来告警及时发现慢接口优化。

可以在PHP代码里记录每个接口的执行时间存到日志或者监控系统比如PrometheusGrafana等等。

2. 慢查询监控

要开启MySQL的慢查询日志监控慢SQL及时发现优化前面MySQL优化提到过。

3. 性能分析工具

要会使用性能分析工具比如XdebugxhprofBlackfire等等当发现慢接口用这些工具分析接口的执行过程找到瓶颈在哪里哪个函数耗时哪条SQL慢然后针对性地优化。

4. 压测

优化前后要做压力测试比如用abwrkJMeter等等压测接口的吞吐量响应时间验证优化的效果也能发现系统的瓶颈和极限。

八、写在最后

PHP性能优化实战:从慢接口到高并发的经验总结。

PHP性能优化是一个系统工程要从PHP本身数据库缓存Web服务器架构等多个层面综合优化而且要先定位瓶颈再针对性地优化不要盲目优化。

其实PHP并不慢只要优化得当开启OPcache优化代码优化数据库用好缓存PHP也能跑得很快也能支撑高并发很多大型网站比如Facebook淘宝等等都在用PHP或者PHP的衍生版本所以不要觉得PHP性能差关键是会不会优化。

希望这篇分享能帮大家更好地优化PHP项目让项目更快更稳。

最后用一句话结尾:

"性能优化没有最好只有更好要持续监控分析优化才能让系统一直保持最佳状态。"

祝大家都能把PHP项目优化得又快又稳!