調(diào)優(yōu)成果
遇到問題
單臺服務(wù)并發(fā)20,平均響應(yīng)時間1124ms,通過htop觀察,發(fā)現(xiàn)cpu占用率達(dá)到100%(包括sleep的進(jìn)程),內(nèi)存幾乎沒怎么用。
調(diào)優(yōu)后
單機(jī)最大吞吐量達(dá)到120 響應(yīng)時長不超過1000ms
硬件信息
操作系統(tǒng):Linux 系統(tǒng)版本為 CentOS 8 CPU:4核 3.20GHz 內(nèi)存:8GB
[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
應(yīng)用環(huán)境
PHP:7.3
[work@test-mapi mapi]$ php artisan Laravel Framework 6.20.44
[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 的一個擴(kuò)展,用于加速 PHP 腳本的執(zhí)行。它通過將 PHP 腳本的編譯結(jié)果(即opcode)緩存起來,從而避免每次請求都重新編譯腳本,提高性能。在php.ini內(nèi)開啟opcache,相關(guān)參數(shù)如下:
[dba] ;dba.default_handler= [opcache] opcache.enable=1 ; 啟用 OPCache opcache.enable_cli=1 ; 在命令行模式下也啟用 OPCache opcache.jit=tracing ; 啟用 JIT 跟蹤模式,根據(jù)執(zhí)行情況動態(tài)編譯熱點(diǎn)代碼 opcache.jit_buffer_size=256M ; 為 JIT 編譯保留的內(nèi)存大小 opcache.memory_consumption=512M ; OPCache 可使用的內(nèi)存大小 opcache.interned_strings_buffer=64M ; 用于存儲內(nèi)部字符串的緩沖區(qū)大小 opcache.max_accelerated_files=10000 ; 緩存的最大文件數(shù)量 opcache.revalidate_freq=60 ; OPcache 每隔 60 秒會檢查一次腳本文件是否有修改。默認(rèn)值通常為 2 秒 0則認(rèn)為是每次啟動都檢查文件是否修改,會增加IO操作,影響性能夠 ,這個參數(shù)只有在 opcache.validate_timestamps=1 的情況下才有效 opcache.validate_timestamps=1;啟用文件變更檢查 0禁用文件變更檢查 opcache.fast_shutdown=1 ; 快速關(guān)閉,提高性能 opcache.save_comments=1 ; 保存注釋,某些框架或應(yīng)用可能依賴注釋
開啟了opcache之后,每秒的吞吐量達(dá)到了70。
php-fmp 靜態(tài)模式
通過htop觀察發(fā)現(xiàn),內(nèi)存使用率很少,說明內(nèi)存并不是laravel的瓶頸,考慮增加php-fmp的工作池
emergency_restart_threshold = 30;在60s內(nèi)超過 30 個 PHP-FPM 進(jìn)程因出現(xiàn)異常(如段錯誤)而退出,那么 PHP-FPM 主進(jìn)程會自動重啟 emergency_restart_interval = 60s;配合第一個選項(xiàng)使用 process_control_timeout = 5s;停止php-fmp的時候,如果子進(jìn)程超過5s未響應(yīng),則強(qiáng)制終止 daemonize = yes;后臺運(yùn)行 process.max = 500;限制 PHP-FPM 可以生成的最大進(jìn)程數(shù)。這個配置項(xiàng)定義了整個 PHP-FPM 服務(wù)的上限,而不是單個工作池的限制。 ;;;;;;;;;;;;;;;;;;;; ; Pool Definitions ; ;;;;;;;;;;;;;;;;;;;; [work] pm = static;設(shè)置了 PHP-FPM 的進(jìn)程管理模式為靜態(tài)(static),即總是啟動固定數(shù)量的子進(jìn)程 pm.max_children = 200; 定義了靜態(tài)模式下 PHP-FPM 工作池可以生成的最大子進(jìn)程數(shù),即始終有 200 個子進(jìn)程等待處理請求。 pm.start_servers = 20;這些參數(shù)用于動態(tài)模式下,控制 PHP-FPM 啟動時的初始子進(jìn)程數(shù) pm.min_spare_servers = 20; pm.max_spare_servers = 300 pm.max_requests = 10240;每個子進(jìn)程在處理完 10240 個請求后會自動重啟。這有助于防止內(nèi)存泄漏問題。 pm.process_idle_timeout = 5s;動態(tài)模式下,閑置進(jìn)程在超過 5 秒沒有處理請求后被終止。在靜態(tài)模式下無效。 request_terminate_timeout = 120;強(qiáng)制終止執(zhí)行時間超過 120 秒的請求。用于防止超長時間執(zhí)行的腳本占用系統(tǒng)資源。 request_slowlog_timeout = 2;慢請求的閾值(2 秒)。如果請求執(zhí)行時間超過這個值,PHP-FPM 會將其記錄到慢日志中。 slowlog = /data/logs/php/slow.log rlimit_files = 51200;設(shè)置了 PHP-FPM 子進(jìn)程可以打開的最大文件描述符數(shù)量。這影響 PHP-FPM 可以同時處理的文件數(shù)。 rlimit_core = 0;設(shè)置 PHP-FPM 子進(jìn)程可以生成的 core dump 文件的最大大?。ㄒ宰止?jié)為單位)。0 表示禁用 core dump。
重啟php-fmp
sudo systemctl restart php-fpm
phpredis
Laravel 自己封裝了一個 Redis,叫predis,當(dāng)我們用laravel自帶的Redis Facade,那么每次調(diào)用redis都需要編譯這個Redis組件,而且是不支持連接池的。但是PHP有一個由c編寫的一個PHP擴(kuò)展比它效率更高,保證服務(wù)器安裝了php的Redis擴(kuò)展,我們就可以改寫predis,修改驅(qū)動為phpredis。
框架內(nèi)的優(yōu)化
在php-fmp的進(jìn)程里面,running的進(jìn)程不超過30個,而sleep的有時候可以達(dá)到300多。于是考慮是發(fā)生了系統(tǒng)調(diào)用導(dǎo)致部分子進(jìn)程sleep。找到一個sleep的進(jìn)程的pid,執(zhí)行命令sudo strace -p 487143
;相關(guān)輸出如下
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
關(guān)閉session
發(fā)現(xiàn)有對session的寫操作,打開laravel,發(fā)現(xiàn)session的驅(qū)動是通過file驅(qū)動的,因?yàn)槲覀儧]有用session ,所以打算關(guān)閉session ,設(shè)置drive為array,當(dāng)然,有其他非必要的中間件也可以刪除 mapi/config/session.php
// 'driver' => env('SESSION_DRIVER', 'file'), 'driver' => 'array',
關(guān)閉Http/Kernel.php中的session,包括csrftoken等文件
// \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, // \Illuminate\View\Middleware\ShareErrorsFromSession::class, // \App\Http\Middleware\VerifyCsrfToken::class,
composer
在composer里面,可能有很多沒用到的組件,在項(xiàng)目啟動時也會被加載,導(dǎo)致項(xiàng)目啟動慢,我刪除了debug相關(guān)的組件。
關(guān)閉debug
在env文件中,有的會開啟APP_DEBUG=true,
利用laravel的緩存提升效率
我們可以把配置信息,路由信息等緩存起來,artisan提供了相關(guān)的方法
hp artisan config:cache 配置緩存 php artisan route:cache 路由緩存 php artisan optimize 緩存優(yōu)化 composer dumpautoload -o 優(yōu)化引入文件
執(zhí)行完畢之后,在api/bootstrap/cache目錄下,會生成對應(yīng)的緩存文件
注意,如果路由和配置等有改動,需要清理緩存
清理緩存 php artisan config:clear php artisan route:clear php artisan clear-compiled
踩坑
php artisan route:clear
php artisan route:clear
這個命令會緩存路由信息,但是他只加載一次路由文件,所以當(dāng)路由文件被分割后,會導(dǎo)致路由緩存失效,訪問就會返回404,這種情況下,我們可以通過require命令,把路由文件加載過來,
// 訂單相關(guān) require 'OrderRoute.php'; // 交易相關(guān) require 'TradeRoute.php';
或者也可以注冊路由到路由服務(wù)提供者里面,在mapi/app/Providers/RouteServiceProvider里面注冊
/** * 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')); }
另外一個點(diǎn)是路由緩存是不支持閉包的,也就是說每個路由都必須指定到某個controller的action。否則會緩存失敗。
更深層次的思考
我們平時分析CPU的使用率,一般使用top或者ps等,top顯示了系統(tǒng)總體的CPU和內(nèi)存使用情況,ps則只顯示了每個進(jìn)程的資源使用情況。但是top并沒有細(xì)分進(jìn)程的用戶態(tài)CPU和內(nèi)核態(tài)CPU。那要怎么查看每個進(jìn)程的詳細(xì)情況呢?pidstat,它正是一個專門分析每個進(jìn)程CPU使用情況的工具。
我們找到比較耗CPU的進(jìn)程之后,可能還想知道是哪個函數(shù)展會用CPU,那么我們就可以使用perf來查找
perf top
zend_execute_data zend_execute_data是執(zhí)行過程中最核心的一個結(jié)構(gòu),每次函數(shù)的調(diào)用、include/require、eval等都會生成一個新的結(jié)構(gòu),它表示當(dāng)前的作用域、代碼的執(zhí)行位置 libc.so PHP 是一種用 C 編寫的腳本語言解釋器,因此它依賴于 C 語言的標(biāo)準(zhǔn)庫。在 Linux 系統(tǒng)上,PHP 的執(zhí)行環(huán)境(包括 PHP 解釋器和擴(kuò)展)會間接依賴 libc.so。例如,當(dāng) PHP 腳本執(zhí)行文件操作、網(wǎng)絡(luò)請求、字符串處理等操作時,底層實(shí)際上調(diào)用的是 libc.so 中的相關(guān)函數(shù)。 zend_hash_find zend_hash_find 是 Zend 引擎中的一個函數(shù),Zend 引擎是 PHP 的核心部分,負(fù)責(zé)解析、編譯和執(zhí)行 PHP 代碼。zend_hash_find 的作用 zend_hash_find 用于在哈希表中查找一個元素。哈希表在 PHP 內(nèi)部廣泛用于各種數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn),如數(shù)組、符號表、對象屬性等。 其他命令 #利用perf record采集靜態(tài)樣本,并保存到本地 [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 #對本地的靜態(tài)樣本進(jìn)行分析 [root@localhost ~]# perf report #會自動打開當(dāng)前目錄下的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
做個優(yōu)化記錄。感謝老A技術(shù)聯(lián)盟公眾號。
評論