从并发20到并发120之laravel性能优化

调优成果

遇到问题

单台服务并发20,平均响应时间1124ms,通过htop观察,发现cpu占用率达到100%(包括sleep的进程),内存几乎没怎么用。

调优后

单机最大吞吐量达到120 响应时长不超过1000ms

硬件信息

操作系统: Linux 系统版本为 CentOS 8

CPU:4核 3.20GHz

内存:8GB

php 复制代码
[work@test-mapi ~]$ uname -a
Linux test-mapi 4.18.0-305.10.2.el8_4.x86_64 #1 SMP Tue Jul 20 17:25:16 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
[work@test-mapi ~]$ 
[work@test-mapi ~]$ lscpu
Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
CPU(s):              4
On-line CPU(s) list: 0-3
Thread(s) per core:  2
Core(s) per socket:  2
Socket(s):           1
NUMA node(s):        1
Vendor ID:           GenuineIntel
CPU family:          6
Model:               106
Model name:          Intel(R) Xeon(R) Platinum 8372C CPU @ 3.20GHz

[work@test-mapi ~]$ free -h
              total        used        free      shared  buff/cache   available
Mem:          7.5Gi       1.5Gi       1.7Gi        33Mi       4.2Gi       5.6Gi
Swap:            0B          0B          0B

应用环境

PHP:7.3

php 复制代码
[work@test-mapi mapi]$ php artisan 
Laravel Framework 6.20.44
php 复制代码
[work@test-mapi ~]$ php -v
PHP 7.3.30 (cli) (built: Sep 23 2021 16:03:45) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.30, Copyright (c) 1998-2018 Zend Technologies

探索方案

开启opcache

OPcache 是 PHP 的一个扩展,用于加速 PHP 脚本的执行。它通过将 PHP 脚本的编译结果(即opcode)缓存起来,从而避免每次请求都重新编译脚本,提高性能。

在php.ini内开启opcache,相关参数如下:

php 复制代码
[dba]
;dba.default_handler=

[opcache]

opcache.enable=1 ; 启用 OPCache
opcache.enable_cli=1 ; 在命令行模式下也启用 OPCache
opcache.jit=tracing ; 启用 JIT 跟踪模式,根据执行情况动态编译热点代码
opcache.jit_buffer_size=256M ; 为 JIT 编译保留的内存大小
opcache.memory_consumption=512M ; OPCache 可使用的内存大小
opcache.interned_strings_buffer=64M ; 用于存储内部字符串的缓冲区大小
opcache.max_accelerated_files=10000 ; 缓存的最大文件数量
opcache.revalidate_freq=60 ; OPcache 每隔 60 秒会检查一次脚本文件是否有修改。默认值通常为 2 秒  0则认为是每次启动都检查文件是否修改,会增加IO操作,影响性能够 ,这个参数只有在 opcache.validate_timestamps=1 的情况下才有效
opcache.validate_timestamps=1;启用文件变更检查  0禁用文件变更检查
opcache.fast_shutdown=1 ; 快速关闭,提高性能
opcache.save_comments=1 ; 保存注释,某些框架或应用可能依赖注释

开启了opcache之后,每秒的吞吐量达到了70。

php-fmp 静态模式

通过htop观察发现,内存使用率很少,说明内存并不是laravel的瓶颈,考虑增加php-fmp的工作池

php 复制代码
emergency_restart_threshold = 30;在60s内超过 30 个 PHP-FPM 进程因出现异常(如段错误)而退出,那么 PHP-FPM 主进程会自动重启
emergency_restart_interval = 60s;配合第一个选项使用
process_control_timeout = 5s;停止php-fmp的时候,如果子进程超过5s未响应,则强制终止
daemonize = yes;后台运行
process.max = 500;限制 PHP-FPM 可以生成的最大进程数。这个配置项定义了整个 PHP-FPM 服务的上限,而不是单个工作池的限制。
;;;;;;;;;;;;;;;;;;;;
; Pool Definitions ;
;;;;;;;;;;;;;;;;;;;;

[work]
pm = static;设置了 PHP-FPM 的进程管理模式为静态(static),即总是启动固定数量的子进程
pm.max_children = 200; 定义了静态模式下 PHP-FPM 工作池可以生成的最大子进程数,即始终有 200 个子进程等待处理请求。
pm.start_servers = 20;这些参数用于动态模式下,控制 PHP-FPM 启动时的初始子进程数
pm.min_spare_servers = 20;
pm.max_spare_servers = 300
pm.max_requests = 10240;每个子进程在处理完 10240 个请求后会自动重启。这有助于防止内存泄漏问题。
pm.process_idle_timeout = 5s;动态模式下,闲置进程在超过 5 秒没有处理请求后被终止。在静态模式下无效。
request_terminate_timeout = 120;强制终止执行时间超过 120 秒的请求。用于防止超长时间执行的脚本占用系统资源。
request_slowlog_timeout = 2;慢请求的阈值(2 秒)。如果请求执行时间超过这个值,PHP-FPM 会将其记录到慢日志中。
slowlog = /data/logs/php/slow.log
rlimit_files = 51200;设置了 PHP-FPM 子进程可以打开的最大文件描述符数量。这影响 PHP-FPM 可以同时处理的文件数。
rlimit_core = 0;设置 PHP-FPM 子进程可以生成的 core dump 文件的最大大小(以字节为单位)。0 表示禁用 core dump。

重启php-fmp

php 复制代码
sudo systemctl restart php-fpm

phpredis

Laravel 自己封装了一个 Redis,叫predis,当我们用laravel自带的Redis Facade,那么每次调用redis都需要编译这个Redis组件,而且是不支持连接池的。但是PHP有一个由c编写的一个PHP扩展比它效率更高,保证服务器安装了php的Redis扩展,我们就可以改写predis,修改驱动为phpredis。

框架内的优化

在php-fmp的进程里面,running的进程不超过30个,而sleep的有时候可以达到300多。

于是考虑是发生了系统调用导致部分子进程sleep。找到一个sleep的进程的pid,执行命令sudo strace -p 487143;相关输出如下

php 复制代码
openat(AT_FDCWD, "/data/backend/mapi/vendor/nesbot/carbon/src/Carbon/CarbonTimeZone.php", O_RDONLY) = 11
fstat(11, {st_mode=S_IFREG|0775, st_size=8734, ...}) = 0
fstat(11, {st_mode=S_IFREG|0775, st_size=8734, ...}) = 0
fstat(11, {st_mode=S_IFREG|0775, st_size=8734, ...}) = 0
mmap(NULL, 8734, PROT_READ, MAP_SHARED, 11, 0) = 0x7fe191b5a000
munmap(0x7fe191b5a000, 8734)            = 0
close(11)                               = 0
lstat("/data/backend/mapi/storage/framework/sessions/kIiKqblIWBkWiyyxxCRjHiIEtfr5Q0iId5JYdB3S", 0x7ffc68443b70) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/data/backend/mapi/storage/framework/sessions/kIiKqblIWBkWiyyxxCRjHiIEtfr5Q0iId5JYdB3S", O_WRONLY|O_CREAT, 0666) = 11

关闭session

发现有对session的写操作,打开laravel,发现session的驱动是通过file驱动的,因为我们没有用session ,所以打算关闭session ,设置drive为array,当然,有其他非必要的中间件也可以删除

mapi/config/session.php

php 复制代码
//    'driver' => env('SESSION_DRIVER', 'file'),
    'driver' => 'array',

关闭Http/Kernel.php中的session,包括csrftoken等文件

php 复制代码
//            \Illuminate\Session\Middleware\StartSession::class,
//            \Illuminate\Session\Middleware\AuthenticateSession::class,
//            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
//            \App\Http\Middleware\VerifyCsrfToken::class,

composer

在composer里面,可能有很多没用到的组件,在项目启动时也会被加载,导致项目启动慢,我删除了debug相关的组件。

关闭debug

在env文件中,有的会开启APP_DEBUG=true,

利用laravel的缓存提升效率

我们可以把配置信息,路由信息等缓存起来,artisan提供了相关的方法

php 复制代码
hp artisan config:cache 
配置缓存

php artisan route:cache  
路由缓存

php artisan optimize
缓存优化

composer dumpautoload -o
优化引入文件

执行完毕之后,在api/bootstrap/cache目录下,会生成对应的缓存文件

注意,如果路由和配置等有改动,需要清理缓存

php 复制代码
清理缓存
php artisan config:clear
php artisan route:clear
php artisan clear-compiled

踩坑

php artisan route:clear

php artisan route:clear这个命令会缓存路由信息,但是他只加载一次路由文件,所以当路由文件被分割后,会导致路由缓存失效,访问就会返回404,这种情况下,我们可以通过require命令,把路由文件加载过来,

php 复制代码
// 订单相关
require 'OrderRoute.php';
// 交易相关
require 'TradeRoute.php';

或者也可以注册路由到路由服务提供者里面,在mapi/app/Providers/RouteServiceProvider里面注册

php 复制代码
    /**
     * Define the "backend" routes for the application.
     *
     * These routes all receive session state, CSRF protection, etc.
     *
     * @return void
     */
    protected function mapBackendRoutes()
    {
        Route::middleware('web')
            ->prefix("backend")
            ->namespace($this->namespace)
            ->group(base_path('routes/backend.php'));
    }
  • 另外一个点是路由缓存是不支持闭包的,也就是说每个路由都必须指定到某个controller的action。否则会缓存失败。

更深层次的思考

我们平时分析CPU的使用率,一般使用top或者ps等,top显示了系统总体的CPU和内存使用情况,ps则只显示了每个进程的资源使用情况。但是top并没有细分进程的用户态CPU和内核态CPU。那要怎么查看每个进程的详细情况呢? pidstat,它正是一个专门分析每个进程CPU使用情况的工具。

我们找到比较耗CPU的进程之后,可能还想知道是哪个函数展会用CPU,那么我们就可以使用perf来查找

php 复制代码
 perf top
  • zend_execute_data
    zend_execute_data是执行过程中最核心的一个结构,每次函数的调用、include/require、eval等都会生成一个新的结构,它表示当前的作用域、代码的执行位置
  • libc.so
    PHP 是一种用 C 编写的脚本语言解释器,因此它依赖于 C 语言的标准库。在 Linux 系统上,PHP 的执行环境(包括 PHP 解释器和扩展)会间接依赖 libc.so。例如,当 PHP 脚本执行文件操作、网络请求、字符串处理等操作时,底层实际上调用的是 libc.so 中的相关函数。
  • zend_hash_find
    zend_hash_find 是 Zend 引擎中的一个函数,Zend 引擎是 PHP 的核心部分,负责解析、编译和执行 PHP 代码。
    zend_hash_find 的作用
    zend_hash_find 用于在哈希表中查找一个元素。哈希表在 PHP 内部广泛用于各种数据结构的实现,如数组、符号表、对象属性等。
  • 其他命令
php 复制代码
#利用perf record采集静态样本,并保存到本地
[root@localhost ~]# perf record         #按Ctrl+C 终止采样
[ perf record: Woken up 23 times to write data ]
[ perf record: Captured and wrote 5.787 MB perf.data (121112 samples) ]
[root@localhost ~]# ls
anaconda-ks.cfg  perf.data  sysstat-12.1.5-1.x86_64.rpm
[root@localhost ~]# du -sh perf.data    #这就是采集到的样本
5.8M    perf.data
#对本地的静态样本进行分析
[root@localhost ~]# perf report         #会自动打开当前目录下的perf.data
Samples: 121K of event 'cpu-clock', Event count (approx.): 30278000000                                                                         
Overhead  Command         Shared Object        Symbol                                                                                          
  99.86%  swapper         [kernel.kallsyms]    [k] native_safe_halt
   0.03%  kworker/1:1     [kernel.kallsyms]    [k] _raw_spin_unlock_irqrestore
   0.01%  bash            [kernel.kallsyms]    [k] _raw_spin_unlock_irqrestore
   0.01%  sshd            [kernel.kallsyms]    [k] e1000_xmit_frame
   0.01%  kworker/u256:3  [kernel.kallsyms]    [k] mpt_put_msg_frame
   0.00%  swapper         [kernel.kallsyms]    [k] __do_softirq
   0.00%  sshd            [kernel.kallsyms]    [k] __memcpy
   0.00%  bash            [kernel.kallsyms]    [k] __x2apic_send_IPI_mask
   0.00%  ps              [kernel.kallsyms]    [k] __memcpy
   0.00%  migration/1     [kernel.kallsyms]    [k] migration_cpu_stop
   0.00%  ps              [kernel.kallsyms]    [k] follow_page_mask
   0.00%  ps              [kernel.kallsyms]    [k] vsnprintf
   0.00%  bash            [kernel.kallsyms]    [k] __do_page_fault
   0.00%  kworker/0:1     [kernel.kallsyms]    [k] _raw_spin_unlock_irqrestore
   0.00%  perf            [kernel.kallsyms]    [k] mem_cgroup_update_page_stat
   0.00%  ps              [kernel.kallsyms]    [k] format_decode

相关参考

记一次 PHP 并发性能调优实战 -- 性能提升 104%

记一次 Laravel 应用性能调优经历

某个应用的CPU使用率居然达到100%,我该怎么做?(三)

相关推荐
程序员在囧途18 分钟前
Sora2 25 秒视频 API 国内直连!10 积分/次,稳定秒退任务,支持 avatar & Remix(附 PHP 接入教程)
后端·开源·php
峰顶听歌的鲸鱼32 分钟前
15.docker:网络
运维·网络·docker·容器·云计算·php·学习方法
catchadmin32 分钟前
使用 PHP 和 WebSocket 构建实时聊天应用 完整指南
开发语言·websocket·php
郑州光合科技余经理1 小时前
技术解析:如何打造适应多国市场的海外跑腿平台
java·开发语言·javascript·mysql·spring cloud·uni-app·php
m0_485614672 小时前
Docker基础
docker·容器·php
二等饼干~za8986682 小时前
碰一碰发视频系统源码开发搭建--技术分享
java·运维·服务器·重构·django·php·音视频
小尧嵌入式3 小时前
Linux网络介绍网络编程和数据库
linux·运维·服务器·网络·数据库·qt·php
我要学脑机3 小时前
一个图谱映射到功能网络yeo7或17的解决方案
开发语言·网络·php
JaguarJack4 小时前
使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端
后端·php
BingoGo4 小时前
使用 Laravel Workflow 作为 MCP 工具提供给 AI 客户端
后端·php·laravel