為甚麼 Laravel 會佔用這麼多 CPU?

即使是最簡單的 Laravel “hello world” 應用,大部分 CPU 時間也消耗在應用程式碼之外。透過 OpenResty XRay 進行 Laravel CPU 分析可以發現,Service Provider 的啟動和註冊才是頭號 CPU 消耗者——而不是你的路由處理函式。具體來說:

  • 最熱 PHP 程式碼路徑 #1(36.4% CPU)bootProvider —— Laravel 在每次請求時都會啟動所有已註冊的 Service Provider(Carbon 日期庫、Ignition 等)。
  • 最熱 PHP 程式碼路徑 #2(14.5% CPU)register —— Service Provider 的註冊和解析過程(resolveProviderDatabaseServiceProvider::register 等)同樣在每次新請求時執行。
  • 最熱 PHP 程式碼路徑 #3(12.8% CPU):實際的 “hello world” 響應——只有一小部分 CPU 用在了你自己的程式碼上。

換句話說,在典型的 Laravel 高 CPU 使用率場景中,框架引導開銷佔據了主導地位。下面的教程將詳細介紹如何使用 OpenResty XRay CPU 火焰圖來識別這些熱路徑,幫助你瞭解最佳化精力應該集中在哪裡。

如果你正在排查更廣泛的 PHP 應用高 CPU 問題,許多相同的分析技術同樣適用。


下面的演練展示了我們是如何得出這些結論的——從選擇目標 PHP 程序到解讀 PHP 層的 CPU 火焰圖——以便你在自己的 Laravel 應用上重複同樣的分析。

測試環境:一個跑滿 CPU 的 Hello World 應用

我們用 PHP 的 Laravel 框架搭建了一個簡單的 “hello world” Web 應用。

使用 PHP Laravel 框架搭建的 hello world Web 應用

在這裡定義了一個請求處理函式,它會返回一個 “Hello, world” 的響應。

返回 Hello, world 響應的 Laravel 路由處理函式

使用 curl 命令訪問 Laravel 的 HTTP 介面。響應體確實是 “hello world”。

curl 命令訪問 Laravel HTTP 介面並返回 hello world

執行 top 命令來檢查目標 PHP 程序的 CPU 使用情況。

這是之前展示過的 PHP “hello world” 服務程序。

top 命令顯示 Laravel PHP hello world 服務程序

為了獲得清晰的 CPU 分析資料,我們事先使用客戶端壓測工具將 CPU 使用率壓滿到 100%。

top 顯示壓測下 Laravel PHP 程序 CPU 佔滿 100%

執行 ps 命令確認該程序使用的是 Linux 發行版自帶的標準 php 二進位制可執行檔案。

ps 命令確認 Laravel 應用使用標準 php 二進位制檔案

Laravel CPU 分析:三條最熱程式碼路徑

接下來,我們使用 OpenResty XRay 來檢視 CPU 時間是如何分佈在 PHP 程序內部的各個程式碼路徑上的。在 OpenResty XRay Web 控制檯中,確認正在監控正確的機器,開啟 “Guided Analysis” 頁面,選擇 “High CPU usage” 作為要診斷的問題型別。

OpenResty XRay 引導式分析中選擇 High CPU usage 問題型別

接下來,選擇 PHP 應用並選取佔用近 100% CPU 的工作程序——與之前在 top 中看到的同一個程序(此處為 PID 2092,CPU 佔用 93%)。

選擇佔用 93% CPU 的 PHP 工作程序 PID 2092 進行分析

OpenResty XRay 自動檢測應用型別,可以同時分析多個語言級別,因此保持 PHP 和 C/C++ 同時選中,最長分析時間保持預設的 300 秒。啟動後,系統進行多輪分析,交替取樣 C 層和 PHP 層的 CPU 火焰圖。兩輪即可滿足本例需求,因此在此停止分析。

語言級別設為 PHP 和 C/C++,最大分析時間 300 秒

OpenResty XRay 隨後自動生成了一份分析報告。

OpenResty XRay 自動生成的 Laravel CPU 分析報告

報告標記了我們診斷的問題型別:CPU。

報告標記的問題型別為 CPU

這是 CPU 資源消耗最高的 C 程式碼路徑,佔用了 96.2% 的 CPU 時間。

佔用 96.2% CPU 的最熱 C 程式碼路徑

第一個 zend_execute 函式用於解釋和執行 PHP 操作碼。

火焰圖中解釋執行 PHP 操作碼的 zend_execute 函式

當伺服器收到一個新的 HTTP 請求時,php_cli_server_dispatch_router 函式會被呼叫來讀取和解析請求資料。

讀取並解析 HTTP 請求的 php_cli_server_dispatch_router 函式

main 函式幀表明本演示執行在 PHP CLI 啟動的內建 Web 伺服器上。生產環境部署在 php-fpm 上時,這一層 C 級別的伺服器分派幀會有所不同,但下面的 Laravel 框架層 PHP 熱點才是重點關注物件。

顯示 PHP CLI 內建 Web 伺服器的 main 函式幀

展開該條目可以檢視完整的 C 層呼叫路徑,從 _start 程序入口點,經過伺服器事件迴圈,一直到 PHP 操作碼直譯器。

從 _start 到 PHP 操作碼直譯器的完整 C 層呼叫路徑

這條熱程式碼路徑是由這個 C 語言級別的 CPU 火焰圖自動推匯出來的。

OpenResty XRay 生成的 C 層 CPU 火焰圖

下面是對當前問題更詳細的解釋和建議。

其中提到了我們之前看到的 zend_execute 函式。

報告解釋中提及 zend_execute 函式

現在來看 PHP 層的結果。最熱 PHP 程式碼路徑 #1 單獨就消耗了 36.4% 的 CPU 時間。

佔用 36.4% CPU 的最熱 PHP 程式碼路徑

bootProvider 函式是 Laravel 框架的一部分,該函式負責啟動應用註冊的 Service Provider。

啟動已註冊 Service Provider 的 Laravel bootProvider 函式

完整路徑顯示請求從 public/index.php 和 HTTP 核心進入,然後透過 array_walk 遍歷所有已註冊的 Provider,展開到 Service Provider 的啟動過程。

從 public/index.php 經 HTTP 核心進入 Service Provider 啟動的呼叫路徑

報告還包含對該程式碼路徑的自動生成解釋:bootProvider 啟動應用註冊的每個 Service Provider,而 Service Provider 是 Laravel 應用配置的核心位置。

bootProvider 啟動 Service Provider 路徑的自動生成解釋

放大 PHP 層 CPU 火焰圖,可以看到 bootProvider 幀以及其下方正在啟動的各個 Service Provider。

放大到 bootProvider 幀及其下方 Service Provider 的 PHP 層 CPU 火焰圖

Laravel 的 ServiceProvider 程式中,ServiceProvider::boot 方法用於為 Carbon 日期庫註冊宏和配置。相應的 boot 方法用於初始化時區等設定。

為 Carbon 日期庫初始化時區設定的 ServiceProvider boot 方法

IgnitionServiceProvider::boot 方法負責啟動所有的 Service Provider。

啟動所有 Service Provider 的 IgnitionServiceProvider boot 方法

再來看第二熱 PHP 程式碼路徑 #2,它消耗了 14.5% 的 CPU 時間。

佔用 14.5% CPU 的第二熱 PHP 程式碼路徑

這個 register 函式在應用引導過程中執行,負責解析和註冊 Service Provider。由於 Laravel 在每次請求時都會建立新的應用例項(除非使用 Laravel Octane 等常駐記憶體方案),這一開銷會反覆產生。

在應用引導中解析並註冊 Service Provider 的 Laravel register 函式

完整路徑與上面的啟動路徑類似:請求從 public/index.php 和 HTTP 核心進入,然後深入到 registerConfiguredProviders

從 public/index.php 深入 registerConfiguredProviders 的呼叫路徑

自動生成的解釋分解了同樣的呼叫序列,從 index.php 中的應用入口點開始。

從 index.php 入口開始的 register 路徑自動生成解釋

在放大的 PHP 層 CPU 火焰圖中,Application::register 幀在周圍的引導幀中被高亮顯示。

PHP 層 CPU 火焰圖中高亮的 Application::register 幀

resolveProvider 是 Laravel 框架中的一個方法,用於解析和註冊 Service Provider。

解析並註冊 Service Provider 的 Laravel resolveProvider 方法

DatabaseServiceProvider::register 方法在 Laravel 中負責向服務容器註冊資料庫服務及其相關元件。

註冊 Laravel 資料庫服務的 DatabaseServiceProvider register 方法

第三熱的程式碼路徑消耗了 12.8% 的 CPU 時間。

佔用 12.8% CPU 的第三熱 PHP 程式碼路徑

其呼叫鏈經過 Laravel 的路由管道——Router::dispatch、中介軟體棧和 ControllerDispatcher——最終到達控制器。

經過 Router::dispatch、中介軟體棧和 ControllerDispatcher 的呼叫鏈

這就是實際實現 “hello world” 響應的路徑:我們自己的處理函式程式碼,加上圍繞它的路由和響應機制。

實現 Laravel hello world 響應的程式碼路徑

注意,#1 和 #2 路徑都屬於同一個請求級別的引導過程:一個負責啟動 Service Provider,另一個負責註冊它們。兩者合計佔了超過一半的 CPU 時間——還未執行任何應用邏輯。

Service Provider 啟動與註冊路徑合計超過一半 CPU 時間

作為參考,這裡提供了 Laravel “hello world” 應用與等效 OpenResty 處理函式的吞吐量對比:371 請求/秒 vs 28,000 請求/秒——大約 75 倍的差距。這一對比並非完全對等,因為 Laravel 是一個全棧框架,而 OpenResty 的每請求抽象層要輕量得多,但它說明了上面測量到的框架開銷在實際中的代價。如果你的 Laravel 應用還存在高記憶體消耗問題,OpenResty XRay 同樣可以進行分析。

吞吐量對比:Laravel 371 請求/秒 對比 OpenResty 28,000 請求/秒

自動 CPU 使用率分析與報告

OpenResty XRay 還可以自動監控線上程序,無需任何手動步驟。“Insights” 頁面收集每個應用的日報和週報——包含上述相同的 CPU 分析結果和熱程式碼路徑分解——因此日常執行中無需使用 “Guided Analysis”。引導式分析在開發階段和按需深入分析(如本教程)時仍然很實用。

Insights 頁面顯示 Laravel CPU 的自動日報和週報

常見問題:Laravel 高 CPU 使用率

Laravel 的 Service Provider 每次請求都會執行嗎?

會。在標準的 Laravel 部署中,每次請求都會建立一個新的應用例項,因此所有 Service Provider 每次都會被註冊和啟動。在上面的分析中,register(14.5% CPU)和 bootProvider(36.4% CPU)都是逐請求執行的——兩者合計超過一半的 CPU,而這些都發生在你的路由邏輯執行之前。這種逐請求的引導過程正是 Laravel 高 CPU 使用率場景中的主導開銷。

Laravel Octane 能降低這種 CPU 開銷嗎?

本文測到的高開銷來自每次請求都重複進行 Service Provider 的註冊和啟動。像 Laravel Octane 這樣的常駐記憶體方案會讓應用例項常駐記憶體,而不是每請求重建,因此這部分引導工作不會被反覆執行。如果你的 Laravel 高 CPU 使用率主要由 registerbootProvider 路徑主導,這正是此類方案要消除的開銷。

如何定位 Laravel 應用中最熱的程式碼路徑?

使用 OpenResty XRay 的引導式分析剖析執行中的 PHP 程序。它會同時生成 C 層(zend_execute 和 PHP 虛擬機器)和 PHP 層(Laravel 框架函式)的 CPU 火焰圖,並按 CPU 時間佔比對最熱的路徑排序——本例中為 bootProvider(36.4%)、register(14.5%)和路由響應(12.8%)。這樣就能明確看出是哪些框架函式在主導 CPU,而不必靠猜測。

處理 hello world 響應時 OpenResty 比 Laravel 快多少?

在上面的吞吐量對比中,這個 Laravel “hello world” 應用為 371 請求/秒,而等效的 OpenResty 處理函式約為 28,000 請求/秒——大約 75 倍的差距。這一對比並非完全對等,因為 Laravel 是全棧框架,而 OpenResty 每請求的抽象層要輕量得多,但它說明了框架引導開銷在實際中的代價。

關於 OpenResty XRay

OpenResty XRay 是一個動態追蹤產品,它可以自動分析執行中的應用,以解決效能問題、行為問題和安全漏洞,並提供可行的建議。在底層實現上,OpenResty XRay 由我們的 Y 語言 驅動,可以在不同環境下支援多種不同的執行時,如 Stap+、eBPF+、GDB 和 ODB。

關於作者

章亦春是開源 OpenResty® 專案創始人兼 OpenResty Inc. 公司 CEO 和創始人。

章亦春(Github ID: agentzh),生於中國江蘇,現定居美國灣區。他是中國早期開源技術和文化的倡導者和領軍人物,曾供職於多家國際知名的高科技企業,如 Cloudflare、雅虎、阿里巴巴, 是 “邊緣計算”、“動態追蹤 “和 “機器程式設計 “的先驅,擁有超過 22 年的程式設計及 16 年的開源經驗。作為擁有超過 4000 萬全球域名使用者的開源專案的領導者。他基於其 OpenResty® 開源專案打造的高科技企業 OpenResty Inc. 位於美國矽谷中心。其主打的兩個產品 OpenResty XRay(利用動態追蹤技術的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最適合微服務和分散式流量的全能型閘道器軟體),廣受全球眾多上市及大型企業青睞。在 OpenResty 以外,章亦春為多個開源專案貢獻了累計超過百萬行程式碼,其中包括,Linux 核心、Nginx、LuaJITGDBSystemTapLLVM、Perl 等,並編寫過 60 多個開源軟體庫。

關注我們

如果您喜歡本文,歡迎關注我們 OpenResty Inc. 公司的部落格網站 。也歡迎掃碼關注我們的微信公眾號:

我們的微信公眾號

翻譯

我們提供了英文版原文和中譯版(本文)。我們也歡迎讀者提供其他語言的翻譯版本,只要是全文翻譯不帶省略,我們都將會考慮採用,非常感謝!