Swoole 驅(qū)動(dòng)的 Laravel 應(yīng)用優(yōu)化原理

Swoole VS PHP-FPM
我們先來看看傳統(tǒng)的基于 PHP-FPM 的 Laravel 應(yīng)用啟動(dòng)和請(qǐng)求處理流程:

PHP生命周期.png

如上圖所示,PHP-FPM 位于 SAPI 層,PHP 底層在接收到來自 Nginx 轉(zhuǎn)發(fā)過來的 PHP 請(qǐng)求時(shí),會(huì)將其交給某個(gè)空閑的 PHP-FPM 進(jìn)程來處理,PHP-FPM 進(jìn)程會(huì)在啟動(dòng)階段設(shè)置 HTTP 環(huán)境變量,然后通過 PHP 核心代碼初始化所有已經(jīng)啟用的 PHP 模塊(即擴(kuò)展),并對(duì)此次請(qǐng)求上下文進(jìn)行初始化,完成,這些操作后再調(diào)用 Zend 引擎來編譯并執(zhí)行業(yè)務(wù)邏輯代碼(進(jìn)入 Laravel 項(xiàng)目,從入口文件開始執(zhí)行),具體的代碼執(zhí)行流程如下:
執(zhí)行流程.jpg

Zend 引擎會(huì)檢查 OpCode 緩存,如果代碼片段已經(jīng)緩存,則從緩存中讀取并執(zhí)行,否則還要編譯成 OpCode 并緩存后才能執(zhí)行。

代碼執(zhí)行完成后,會(huì)將處理結(jié)果打印或著發(fā)送 HTTP 響應(yīng)給客戶端,然后 PHP 底層代碼會(huì)執(zhí)行請(qǐng)求關(guān)閉及模塊關(guān)閉函數(shù)進(jìn)行后續(xù)清理工作,最后再回到 SAPI 層,調(diào)用 PHP-FPM 對(duì)應(yīng)的關(guān)閉函數(shù),從而完成此次請(qǐng)求的所有流程。

這個(gè)過程周而復(fù)始,每次用戶有新請(qǐng)求過來都會(huì)從頭執(zhí)行一遍,所有的環(huán)境初始化、模塊初始化、請(qǐng)求初始化以及 Laravel 應(yīng)用的啟動(dòng)過程,乃至后續(xù)請(qǐng)求關(guān)閉、模塊關(guān)閉、PHP-FPM 關(guān)閉,如果 Redis、MySQL 之類的網(wǎng)絡(luò)請(qǐng)求沒有連接池,那么每次新請(qǐng)求過來,所有的連接操作也要重新建立,所以傳統(tǒng)模式下的 PHP 應(yīng)用性能表現(xiàn)一直為人所詬病,盡管 Nginx + PHP-FPM 模式已經(jīng)大大優(yōu)于基于 Apache 運(yùn)行的 PHP 應(yīng)用了。

那我們能不能優(yōu)化這個(gè)請(qǐng)求處理流程呢?比如把環(huán)境初始化、模塊初始化、請(qǐng)求初始化、Laravel 應(yīng)用的啟動(dòng)過程只執(zhí)行一次,然后后面過來的請(qǐng)求復(fù)用上一次初始化的 PHP 環(huán)境?此外,對(duì)于 Redis、MySQL 這些耗時(shí)的網(wǎng)絡(luò)連接以連接池的方式管理起來?事實(shí)上,基于 Swoole 就可以完成這些優(yōu)化,并且我們還可以基于其提供的協(xié)程功能實(shí)現(xiàn)并發(fā)編程,使得在 PHP 中也可以輕松實(shí)現(xiàn)異步并發(fā)編程,不過關(guān)于 PHP 動(dòng)態(tài)語言執(zhí)行時(shí)的性能優(yōu)化(邊解釋邊執(zhí)行)這一點(diǎn)需要 PHP 底層開發(fā)組去優(yōu)化,畢竟動(dòng)態(tài)語言有利有弊,不可能又要性能,又要編碼靈活性。

但是 Laravel 官方并沒有實(shí)現(xiàn)對(duì) Swoole 的兼容和集成,所以我們需要自己實(shí)現(xiàn)在 Laravel 中集成 Swoole 進(jìn)行編碼工作,從而充分利用 Swoole 的異步編程、并發(fā)編程特性提升 Laravel 的性能,但是如果想在 Laravel 中充分集成 Swoole 并不是一件輕松的工作,要考慮和測(cè)試的東西很多,好在現(xiàn)在已經(jīng)有了可選的擴(kuò)展包,業(yè)內(nèi)比較有名的是 laravelslaravel-swoole,基于它們提供的功能,我們可以輕松在 Laravel 中基于 Swoole 實(shí)現(xiàn)高性能編程。

為什么基于 Swoole 驅(qū)動(dòng)的 Laravel 應(yīng)用性能更好?
下面我們來看看基于 Swoole 驅(qū)動(dòng)的 Laravel 應(yīng)用從哪些方面對(duì)傳統(tǒng)的 PHP Web 請(qǐng)求處理流程進(jìn)行了優(yōu)化。

laravels 擴(kuò)展包為例,它為我們提供了一個(gè)內(nèi)置的基于 Swoole 的 HTTP 服務(wù)器,通過php bin/laravels start 命令啟動(dòng),Nginx 會(huì)將 PHP 請(qǐng)求都發(fā)到這個(gè)服務(wù)器進(jìn)行處理,與 PHP-FPM 不同的是,這個(gè) Swoole 服務(wù)器啟動(dòng)后,會(huì)開啟多個(gè) Worker 進(jìn)程,在每個(gè) Worker 進(jìn)程中,Laravel 應(yīng)用啟動(dòng)及之前的環(huán)境初始化工作只執(zhí)行一次,請(qǐng)求結(jié)束后,Laravel 應(yīng)用實(shí)例不會(huì)回收,后續(xù)發(fā)給該 Worker 進(jìn)程處理的請(qǐng)求會(huì)復(fù)用之前已經(jīng)啟動(dòng)的 Laravel 應(yīng)用實(shí)例,再結(jié)合 MySQL、Redis 長(zhǎng)連接,從而極大提高了 Laravel 應(yīng)用的性能。

在 Laravel 中使用 Swoole 的注意事項(xiàng)
單例模式
如上所述,Laravel 應(yīng)用實(shí)例位于 Swoole 的 Worker 進(jìn)程中,并且常駐內(nèi)存,這種模式提升了應(yīng)用性能的同時(shí),也引入了新的復(fù)雜性,因?yàn)?Laravel 底層的核心是一個(gè)Application IoC 容器,所有服務(wù)都綁定在這個(gè)容器里,然后在應(yīng)用的時(shí)候從里面解析,包括通過 singleton 方法以單例模式綁定的服務(wù)。

這在傳統(tǒng)的每次請(qǐng)求重新初始化新的 Application 容器的開發(fā)模式中當(dāng)然沒有什么問題,但是現(xiàn)在問題來了,單例模式綁定的服務(wù)在整個(gè)應(yīng)用生命周期內(nèi)解析時(shí)返回的都是同一個(gè)對(duì)象實(shí)例,現(xiàn)在這個(gè)生命周期延生到整個(gè) Worker 進(jìn)程的生命周期,只要 Worker 進(jìn)程還在,那么多個(gè)請(qǐng)求使用的可能都是同一個(gè)單例服務(wù),這對(duì)不同請(qǐng)求可以復(fù)用單例的場(chǎng)景來說是好事,比如數(shù)據(jù)庫(kù)連接,但是對(duì)另一些場(chǎng)景,不同請(qǐng)求不能復(fù)用同一個(gè)實(shí)例,比如用戶認(rèn)證,則是災(zāi)難了,所以在這種場(chǎng)景下,需要在一次請(qǐng)求完成后手動(dòng)注銷這些單例服務(wù)(或者在下次實(shí)例化先判斷單例是否存在,如果存在將其銷毀)。

還是以laravels擴(kuò)展包為例,它為我們提供的針對(duì)這種場(chǎng)景的解決方案是在每次請(qǐng)求處理完成后調(diào)用清理器對(duì)這些單例服務(wù)進(jìn)行請(qǐng)求,你可以通過在 laravels 配置文件中注釋 cleaners 配置項(xiàng)來啟用這些清理器:

'cleaners' => [
    Hhxsv5\LaravelS\Illuminate\Cleaners\SessionCleaner::class, // If you use the session/authentication in your project, please uncomment this line
    Hhxsv5\LaravelS\Illuminate\Cleaners\AuthCleaner::class,    // If you use the authentication/passport in your project, please uncomment this line
    Hhxsv5\LaravelS\Illuminate\Cleaners\JWTCleaner::class,     // If you use the package "tymon/jwt-auth" in your project, please uncomment this line
    // ...
],

上面三個(gè)都是用戶認(rèn)證相關(guān)的清理器,除此之外,該擴(kuò)展包還提供了針對(duì) Request 和 Cookie 的清理器,可以去源碼中查看,如果你想要自定義清理器,也可以仿照這些自帶的清理器實(shí)現(xiàn)來編寫實(shí)現(xiàn)了 Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface 接口的清理器類并將其配置到cleaners 配置項(xiàng)。

除了清理類之外,還可以像上面介紹的那樣,在中間件或者服務(wù)提供者中處理新請(qǐng)求時(shí)銷毀已存在的單例服務(wù)(laravels 配置文件中包含一個(gè) register_providers 配置項(xiàng),用于在每次請(qǐng)求處理時(shí)重新初始化服務(wù)綁定設(shè)置)。

同理,通過static 定義的靜態(tài)變量也要在必要的時(shí)候進(jìn)行清理,通過 global 定義的全局變量則要慎用,因?yàn)樗鼤?huì)在同一個(gè) Worker 進(jìn)程處理的多個(gè)請(qǐng)求中復(fù)用。

exit/die 相關(guān)
由于 Swoole 中禁用 exit/die 函數(shù),所以在 Laravel 中也不能使用它們,以及與之相關(guān)的 dd 函數(shù)。

from: https://laravelacademy.org/post/19832.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。