Swoole VS PHP-FPM
我們先來看看傳統(tǒng)的基于 PHP-FPM 的 Laravel 應用啟動和請求處理流程:
如上圖所示渐裸,PHP-FPM 位于 SAPI 層废恋,PHP 底層在接收到來自 Nginx 轉發(fā)過來的 PHP 請求時汁政,會將其交給某個空閑的 PHP-FPM 進程來處理,PHP-FPM 進程會在啟動階段設置 HTTP 環(huán)境變量,然后通過 PHP 核心代碼初始化所有已經啟用的 PHP 模塊(即擴展),并對此次請求上下文進行初始化觉鼻,完成,這些操作后再調用 Zend 引擎來編譯并執(zhí)行業(yè)務邏輯代碼(進入 Laravel 項目队橙,從入口文件開始執(zhí)行)坠陈,具體的代碼執(zhí)行流程如下:
Zend 引擎會檢查 OpCode 緩存,如果代碼片段已經緩存捐康,則從緩存中讀取并執(zhí)行仇矾,否則還要編譯成 OpCode 并緩存后才能執(zhí)行。
代碼執(zhí)行完成后解总,會將處理結果打印或著發(fā)送 HTTP 響應給客戶端贮匕,然后 PHP 底層代碼會執(zhí)行請求關閉及模塊關閉函數(shù)進行后續(xù)清理工作,最后再回到 SAPI 層花枫,調用 PHP-FPM 對應的關閉函數(shù)刻盐,從而完成此次請求的所有流程。
這個過程周而復始劳翰,每次用戶有新請求過來都會從頭執(zhí)行一遍敦锌,所有的環(huán)境初始化、模塊初始化佳簸、請求初始化以及 Laravel 應用的啟動過程乙墙,乃至后續(xù)請求關閉、模塊關閉生均、PHP-FPM 關閉听想,如果 Redis、MySQL 之類的網(wǎng)絡請求沒有連接池马胧,那么每次新請求過來汉买,所有的連接操作也要重新建立,所以傳統(tǒng)模式下的 PHP 應用性能表現(xiàn)一直為人所詬病佩脊,盡管 Nginx + PHP-FPM 模式已經大大優(yōu)于基于 Apache 運行的 PHP 應用了录别。
那我們能不能優(yōu)化這個請求處理流程呢?比如把環(huán)境初始化邻吞、模塊初始化组题、請求初始化、Laravel 應用的啟動過程只執(zhí)行一次抱冷,然后后面過來的請求復用上一次初始化的 PHP 環(huán)境崔列?此外,對于 Redis、MySQL 這些耗時的網(wǎng)絡連接以連接池的方式管理起來赵讯?事實上盈咳,基于 Swoole 就可以完成這些優(yōu)化,并且我們還可以基于其提供的協(xié)程功能實現(xiàn)并發(fā)編程边翼,使得在 PHP 中也可以輕松實現(xiàn)異步并發(fā)編程鱼响,不過關于 PHP 動態(tài)語言執(zhí)行時的性能優(yōu)化(邊解釋邊執(zhí)行)這一點需要 PHP 底層開發(fā)組去優(yōu)化,畢竟動態(tài)語言有利有弊组底,不可能又要性能丈积,又要編碼靈活性。
但是 Laravel 官方并沒有實現(xiàn)對 Swoole 的兼容和集成债鸡,所以我們需要自己實現(xiàn)在 Laravel 中集成 Swoole 進行編碼工作江滨,從而充分利用 Swoole 的異步編程、并發(fā)編程特性提升 Laravel 的性能厌均,但是如果想在 Laravel 中充分集成 Swoole 并不是一件輕松的工作唬滑,要考慮和測試的東西很多,好在現(xiàn)在已經有了可選的擴展包棺弊,業(yè)內比較有名的是 laravels 和 laravel-swoole晶密,基于它們提供的功能,我們可以輕松在 Laravel 中基于 Swoole 實現(xiàn)高性能編程模她。
為什么基于 Swoole 驅動的 Laravel 應用性能更好惹挟?
下面我們來看看基于 Swoole 驅動的 Laravel 應用從哪些方面對傳統(tǒng)的 PHP Web 請求處理流程進行了優(yōu)化。
以 laravels
擴展包為例缝驳,它為我們提供了一個內置的基于 Swoole 的 HTTP 服務器,通過php bin/laravels start
命令啟動归苍,Nginx 會將 PHP 請求都發(fā)到這個服務器進行處理用狱,與 PHP-FPM 不同的是,這個 Swoole 服務器啟動后拼弃,會開啟多個 Worker 進程夏伊,在每個 Worker 進程中,Laravel 應用啟動及之前的環(huán)境初始化工作只執(zhí)行一次吻氧,請求結束后溺忧,Laravel 應用實例不會回收,后續(xù)發(fā)給該 Worker 進程處理的請求會復用之前已經啟動的 Laravel 應用實例盯孙,再結合 MySQL鲁森、Redis 長連接,從而極大提高了 Laravel 應用的性能振惰。
在 Laravel 中使用 Swoole 的注意事項
單例模式
如上所述歌溉,Laravel 應用實例位于 Swoole 的 Worker 進程中,并且常駐內存,這種模式提升了應用性能的同時痛垛,也引入了新的復雜性草慧,因為 Laravel 底層的核心是一個Application IoC
容器,所有服務都綁定在這個容器里匙头,然后在應用的時候從里面解析漫谷,包括通過 singleton
方法以單例模式綁定的服務。
這在傳統(tǒng)的每次請求重新初始化新的 Application
容器的開發(fā)模式中當然沒有什么問題蹂析,但是現(xiàn)在問題來了舔示,單例模式綁定的服務在整個應用生命周期內解析時返回的都是同一個對象實例,現(xiàn)在這個生命周期延生到整個 Worker 進程的生命周期识窿,只要 Worker 進程還在斩郎,那么多個請求使用的可能都是同一個單例服務,這對不同請求可以復用單例的場景來說是好事喻频,比如數(shù)據(jù)庫連接缩宜,但是對另一些場景,不同請求不能復用同一個實例甥温,比如用戶認證锻煌,則是災難了,所以在這種場景下姻蚓,需要在一次請求完成后手動注銷這些單例服務(或者在下次實例化先判斷單例是否存在宋梧,如果存在將其銷毀)。
還是以laravels
擴展包為例狰挡,它為我們提供的針對這種場景的解決方案是在每次請求處理完成后調用清理器對這些單例服務進行請求捂龄,你可以通過在 laravels
配置文件中注釋 cleaners
配置項來啟用這些清理器:
'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
// ...
],
上面三個都是用戶認證相關的清理器,除此之外加叁,該擴展包還提供了針對 Request 和 Cookie 的清理器倦沧,可以去源碼中查看,如果你想要自定義清理器它匕,也可以仿照這些自帶的清理器實現(xiàn)來編寫實現(xiàn)了 Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface
接口的清理器類并將其配置到cleaners
配置項展融。
除了清理類之外,還可以像上面介紹的那樣豫柬,在中間件或者服務提供者中處理新請求時銷毀已存在的單例服務(laravels
配置文件中包含一個 register_providers
配置項告希,用于在每次請求處理時重新初始化服務綁定設置)。
同理烧给,通過static
定義的靜態(tài)變量也要在必要的時候進行清理燕偶,通過 global
定義的全局變量則要慎用,因為它會在同一個 Worker 進程處理的多個請求中復用础嫡。
exit/die 相關
由于 Swoole 中禁用 exit/die
函數(shù)杭跪,所以在 Laravel 中也不能使用它們,以及與之相關的 dd
函數(shù)。